diff --git a/qcom/opensource/touch-drivers/Android.bp b/qcom/opensource/touch-drivers/Android.bp new file mode 100644 index 0000000000..75d9b80723 --- /dev/null +++ b/qcom/opensource/touch-drivers/Android.bp @@ -0,0 +1,7 @@ +cc_library_headers { + name: "qti_glink_touch_kernel_headers", + export_include_dirs: [ + "glink_interface_ts", + ], + vendor_available: true, +} diff --git a/qcom/opensource/touch-drivers/Android.mk b/qcom/opensource/touch-drivers/Android.mk new file mode 100644 index 0000000000..8fd19235ec --- /dev/null +++ b/qcom/opensource/touch-drivers/Android.mk @@ -0,0 +1,403 @@ +# Android makefile for display kernel modules + +TOUCH_DLKM_ENABLE := true +ifeq ($(TARGET_KERNEL_DLKM_DISABLE), true) + ifeq ($(TARGET_KERNEL_DLKM_TOUCH_OVERRIDE), false) + TOUCH_DLKM_ENABLE := false + endif +endif + +ifeq ($(TOUCH_DLKM_ENABLE), true) + TOUCH_SELECT := CONFIG_MSM_TOUCH=m + BOARD_OPENSOURCE_DIR ?= vendor/qcom/opensource + BOARD_COMMON_DIR ?= device/qcom/common + + LOCAL_PATH := $(call my-dir) + ifeq ($(TARGET_BOARD_PLATFORM), pineapple) + LOCAL_MODULE_DDK_BUILD := true + endif + + ifeq ($(TARGET_BOARD_PLATFORM), blair) + LOCAL_MODULE_DDK_BUILD := true + endif + + ifeq ($(TARGET_BOARD_PLATFORM), pitti) + LOCAL_MODULE_DDK_BUILD := true + endif + + ifeq ($(TARGET_BOARD_PLATFORM), monaco) + LOCAL_MODULE_DDK_BUILD := true + endif + + ifeq ($(TARGET_BOARD_PLATFORM), volcano) + LOCAL_MODULE_DDK_BUILD := true + endif + + include $(CLEAR_VARS) + + # This makefile is only for DLKM + ifneq ($(findstring vendor,$(LOCAL_PATH)),) + + ifneq ($(findstring opensource,$(LOCAL_PATH)),) + TOUCH_BLD_DIR := $(shell pwd)/$(BOARD_OPENSOURCE_DIR)/touch-drivers + endif # opensource + + DLKM_DIR := $(TOP)/$(BOARD_COMMON_DIR)/dlkm + + LOCAL_ADDITIONAL_DEPENDENCIES := $(wildcard $(LOCAL_PATH)/**/*) $(wildcard $(LOCAL_PATH)/*) + + # Build + ########################################################### + # This is set once per LOCAL_PATH, not per (kernel) module + KBUILD_OPTIONS := TOUCH_ROOT=$(TOUCH_BLD_DIR) + + KBUILD_OPTIONS += MODNAME=touch_dlkm + KBUILD_OPTIONS += BOARD_PLATFORM=$(TARGET_BOARD_PLATFORM) + KBUILD_OPTIONS += $(TOUCH_SELECT) + + ########################################################### + +ifeq ($(TARGET_BOARD_PLATFORM), monaco) + + ########################################################### + include $(CLEAR_VARS) + LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/**/*) $(wildcard $(LOCAL_PATH)/*) + LOCAL_MODULE := pt_ts.ko + LOCAL_MODULE_KBUILD_NAME := pt_ts.ko + LOCAL_MODULE_TAGS := optional + #LOCAL_MODULE_DEBUG_ENABLE := true + LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) + include $(DLKM_DIR)/Build_external_kernelmodule.mk + ########################################################### + + ########################################################### + include $(CLEAR_VARS) + LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/**/*) $(wildcard $(LOCAL_PATH)/*) + LOCAL_MODULE := pt_i2c.ko + LOCAL_MODULE_KBUILD_NAME := pt_i2c.ko + LOCAL_MODULE_TAGS := optional + #LOCAL_MODULE_DEBUG_ENABLE := true + LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) + include $(DLKM_DIR)/Build_external_kernelmodule.mk + ########################################################### + + ########################################################### + include $(CLEAR_VARS) + LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/**/*) $(wildcard $(LOCAL_PATH)/*) + LOCAL_MODULE := pt_device_access.ko + LOCAL_MODULE_KBUILD_NAME := pt_device_access.ko + LOCAL_MODULE_TAGS := optional + #LOCAL_MODULE_DEBUG_ENABLE := true + LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) + include $(DLKM_DIR)/Build_external_kernelmodule.mk + ########################################################### + + ########################################################### + include $(CLEAR_VARS) + LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/**/*) $(wildcard $(LOCAL_PATH)/*) + LOCAL_MODULE := glink_comm.ko + LOCAL_MODULE_KBUILD_NAME := glink_comm.ko + LOCAL_MODULE_TAGS := optional + #LOCAL_MODULE_DEBUG_ENABLE := true + LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) + include $(DLKM_DIR)/Build_external_kernelmodule.mk + ########################################################### + + ########################################################### + include $(CLEAR_VARS) + LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/**/*) $(wildcard $(LOCAL_PATH)/*) + LOCAL_MODULE := raydium_ts.ko + LOCAL_MODULE_KBUILD_NAME := raydium_ts.ko + LOCAL_MODULE_TAGS := optional + #LOCAL_MODULE_DEBUG_ENABLE := true + LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) + include $(DLKM_DIR)/Build_external_kernelmodule.mk + ########################################################### + +else ifeq ($(TARGET_BOARD_PLATFORM), kona) + + ########################################################### + include $(CLEAR_VARS) + LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/**/*) $(wildcard $(LOCAL_PATH)/*) + LOCAL_MODULE := focaltech_fts.ko + LOCAL_MODULE_KBUILD_NAME := focaltech_fts.ko + LOCAL_MODULE_TAGS := optional + #LOCAL_MODULE_DEBUG_ENABLE := true + LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) + include $(DLKM_DIR)/Build_external_kernelmodule.mk + ########################################################### + +else ifeq ($(TARGET_BOARD_PLATFORM), pineapple) + + ########################################################### + include $(CLEAR_VARS) + LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/**/*) $(wildcard $(LOCAL_PATH)/*) + LOCAL_MODULE := nt36xxx-i2c.ko + LOCAL_MODULE_KBUILD_NAME := nt36xxx-i2c.ko + LOCAL_MODULE_TAGS := optional + #LOCAL_MODULE_DEBUG_ENABLE := true + LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) + include $(DLKM_DIR)/Build_external_kernelmodule.mk + ########################################################### + + ########################################################### + include $(CLEAR_VARS) + LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/**/*) $(wildcard $(LOCAL_PATH)/*) + LOCAL_MODULE := goodix_ts.ko + LOCAL_MODULE_KBUILD_NAME := goodix_ts.ko + LOCAL_MODULE_TAGS := optional + #LOCAL_MODULE_DEBUG_ENABLE := true + LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) + include $(DLKM_DIR)/Build_external_kernelmodule.mk + ########################################################### + + ########################################################### + include $(CLEAR_VARS) + LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/**/*) $(wildcard $(LOCAL_PATH)/*) + LOCAL_MODULE := atmel_mxt_ts.ko + LOCAL_MODULE_KBUILD_NAME := atmel_mxt_ts.ko + LOCAL_MODULE_TAGS := optional + #LOCAL_MODULE_DEBUG_ENABLE := true + LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) + include $(DLKM_DIR)/Build_external_kernelmodule.mk + ########################################################### + +else ifeq ($(TARGET_BOARD_PLATFORM), kalama) + + ########################################################### + include $(CLEAR_VARS) + LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/**/*) $(wildcard $(LOCAL_PATH)/*) + LOCAL_MODULE := nt36xxx-i2c.ko + LOCAL_MODULE_KBUILD_NAME := nt36xxx-i2c.ko + LOCAL_MODULE_TAGS := optional + #LOCAL_MODULE_DEBUG_ENABLE := true + LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) + include $(DLKM_DIR)/Build_external_kernelmodule.mk + ########################################################### + + ########################################################### + include $(CLEAR_VARS) + LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/**/*) $(wildcard $(LOCAL_PATH)/*) + LOCAL_MODULE := goodix_ts.ko + LOCAL_MODULE_KBUILD_NAME := goodix_ts.ko + LOCAL_MODULE_TAGS := optional + #LOCAL_MODULE_DEBUG_ENABLE := true + LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) + include $(DLKM_DIR)/Build_external_kernelmodule.mk + ########################################################### + + ########################################################### + include $(CLEAR_VARS) + LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/**/*) $(wildcard $(LOCAL_PATH)/*) + LOCAL_MODULE := atmel_mxt_ts.ko + LOCAL_MODULE_KBUILD_NAME := atmel_mxt_ts.ko + LOCAL_MODULE_TAGS := optional + #LOCAL_MODULE_DEBUG_ENABLE := true + LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) + include $(DLKM_DIR)/Build_external_kernelmodule.mk + ########################################################### + +else ifeq ($(TARGET_BOARD_PLATFORM), blair) + + ########################################################### + include $(CLEAR_VARS) + LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/**/*) $(wildcard $(LOCAL_PATH)/*) + LOCAL_MODULE := focaltech_fts.ko + LOCAL_MODULE_KBUILD_NAME := focaltech_fts.ko + LOCAL_MODULE_TAGS := optional + #LOCAL_MODULE_DEBUG_ENABLE := true + LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) + include $(DLKM_DIR)/Build_external_kernelmodule.mk + ########################################################### + + ########################################################### + include $(CLEAR_VARS) + LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/**/*) $(wildcard $(LOCAL_PATH)/*) + LOCAL_MODULE := nt36xxx-i2c.ko + LOCAL_MODULE_KBUILD_NAME := nt36xxx-i2c.ko + LOCAL_MODULE_TAGS := optional + #LOCAL_MODULE_DEBUG_ENABLE := true + LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) + include $(DLKM_DIR)/Build_external_kernelmodule.mk + ########################################################### + + ########################################################### + include $(CLEAR_VARS) + LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/**/*) $(wildcard $(LOCAL_PATH)/*) + LOCAL_MODULE := synaptics_tcm_ts.ko + LOCAL_MODULE_KBUILD_NAME := synaptics_tcm_ts.ko + LOCAL_MODULE_TAGS := optional + #LOCAL_MODULE_DEBUG_ENABLE := true + LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) + include $(DLKM_DIR)/Build_external_kernelmodule.mk + ########################################################### + + ########################################################### + include $(CLEAR_VARS) + LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/**/*) $(wildcard $(LOCAL_PATH)/*) + LOCAL_MODULE := goodix_ts.ko + LOCAL_MODULE_KBUILD_NAME := goodix_ts.ko + LOCAL_MODULE_TAGS := optional + #LOCAL_MODULE_DEBUG_ENABLE := true + LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) + include $(DLKM_DIR)/Build_external_kernelmodule.mk + ########################################################### + +else ifeq ($(TARGET_BOARD_PLATFORM), crow) + + ########################################################### + include $(CLEAR_VARS) + LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/**/*) $(wildcard $(LOCAL_PATH)/*) + LOCAL_MODULE := goodix_ts.ko + LOCAL_MODULE_KBUILD_NAME := goodix_ts.ko + LOCAL_MODULE_TAGS := optional + #LOCAL_MODULE_DEBUG_ENABLE := true + LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) + include $(DLKM_DIR)/Build_external_kernelmodule.mk + ########################################################### + +else ifeq ($(TARGET_BOARD_PLATFORM), bengal) + + ########################################################### + include $(CLEAR_VARS) + LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/**/*) $(wildcard $(LOCAL_PATH)/*) + LOCAL_MODULE := synaptics_tcm_ts.ko + LOCAL_MODULE_KBUILD_NAME := synaptics_tcm_ts.ko + LOCAL_MODULE_TAGS := optional + #LOCAL_MODULE_DEBUG_ENABLE := true + LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) + include $(DLKM_DIR)/Build_external_kernelmodule.mk + ########################################################### + + ########################################################### + include $(CLEAR_VARS) + LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/**/*) $(wildcard $(LOCAL_PATH)/*) + LOCAL_MODULE := nt36xxx-i2c.ko + LOCAL_MODULE_KBUILD_NAME := nt36xxx-i2c.ko + LOCAL_MODULE_TAGS := optional + #LOCAL_MODULE_DEBUG_ENABLE := true + LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) + include $(DLKM_DIR)/Build_external_kernelmodule.mk + ########################################################### + + ########################################################### + include $(CLEAR_VARS) + LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/**/*) $(wildcard $(LOCAL_PATH)/*) + LOCAL_MODULE := focaltech_fts.ko + LOCAL_MODULE_KBUILD_NAME := focaltech_fts.ko + LOCAL_MODULE_TAGS := optional + #LOCAL_MODULE_DEBUG_ENABLE := true + LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) + include $(DLKM_DIR)/Build_external_kernelmodule.mk + ########################################################### + +else ifeq ($(TARGET_BOARD_PLATFORM), trinket) + + ########################################################### + include $(CLEAR_VARS) + LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/**/*) $(wildcard $(LOCAL_PATH)/*) + LOCAL_MODULE := synaptics_tcm_ts.ko + LOCAL_MODULE_KBUILD_NAME := synaptics_tcm_ts.ko + LOCAL_MODULE_TAGS := optional + #LOCAL_MODULE_DEBUG_ENABLE := true + LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) + include $(DLKM_DIR)/Build_external_kernelmodule.mk + ########################################################### + +else ifeq ($(TARGET_BOARD_PLATFORM), pitti) + + ########################################################### + include $(CLEAR_VARS) + LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/**/*) $(wildcard $(LOCAL_PATH)/*) + LOCAL_MODULE := focaltech_fts.ko + LOCAL_MODULE_KBUILD_NAME := focaltech_fts.ko + LOCAL_MODULE_TAGS := optional + #LOCAL_MODULE_DEBUG_ENABLE := true + LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) + include $(DLKM_DIR)/Build_external_kernelmodule.mk + ########################################################### + + ########################################################### + include $(CLEAR_VARS) + LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/**/*) $(wildcard $(LOCAL_PATH)/*) + LOCAL_MODULE := goodix_ts.ko + LOCAL_MODULE_KBUILD_NAME := goodix_ts.ko + LOCAL_MODULE_TAGS := optional + #LOCAL_MODULE_DEBUG_ENABLE := true + LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) + include $(DLKM_DIR)/Build_external_kernelmodule.mk + ########################################################### + +else ifeq ($(TARGET_BOARD_PLATFORM), volcano) + + ########################################################### + include $(CLEAR_VARS) + LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/**/*) $(wildcard $(LOCAL_PATH)/*) + LOCAL_MODULE := goodix_ts.ko + LOCAL_MODULE_KBUILD_NAME := goodix_ts.ko + LOCAL_MODULE_TAGS := optional + #LOCAL_MODULE_DEBUG_ENABLE := true + LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) + include $(DLKM_DIR)/Build_external_kernelmodule.mk + ########################################################### + + ########################################################### + include $(CLEAR_VARS) + LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/**/*) $(wildcard $(LOCAL_PATH)/*) + LOCAL_MODULE := focaltech_fts.ko + LOCAL_MODULE_KBUILD_NAME := focaltech_fts.ko + LOCAL_MODULE_TAGS := optional + #LOCAL_MODULE_DEBUG_ENABLE := true + LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) + include $(DLKM_DIR)/Build_external_kernelmodule.mk + ########################################################### + +else + + ########################################################### + include $(CLEAR_VARS) + LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/**/*) $(wildcard $(LOCAL_PATH)/*) + LOCAL_MODULE := nt36xxx-i2c.ko + LOCAL_MODULE_KBUILD_NAME := nt36xxx-i2c.ko + LOCAL_MODULE_TAGS := optional + #LOCAL_MODULE_DEBUG_ENABLE := true + LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) + include $(DLKM_DIR)/Build_external_kernelmodule.mk + ########################################################### + + ########################################################### + include $(CLEAR_VARS) + LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/**/*) $(wildcard $(LOCAL_PATH)/*) + LOCAL_MODULE := goodix_ts.ko + LOCAL_MODULE_KBUILD_NAME := goodix_ts.ko + LOCAL_MODULE_TAGS := optional + #LOCAL_MODULE_DEBUG_ENABLE := true + LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) + include $(DLKM_DIR)/Build_external_kernelmodule.mk + ########################################################### + + ########################################################### + include $(CLEAR_VARS) + LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/**/*) $(wildcard $(LOCAL_PATH)/*) + LOCAL_MODULE := atmel_mxt_ts.ko + LOCAL_MODULE_KBUILD_NAME := atmel_mxt_ts.ko + LOCAL_MODULE_TAGS := optional + #LOCAL_MODULE_DEBUG_ENABLE := true + LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) + include $(DLKM_DIR)/Build_external_kernelmodule.mk + ########################################################### + + ########################################################### + include $(CLEAR_VARS) + LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/**/*) $(wildcard $(LOCAL_PATH)/*) + LOCAL_MODULE := synaptics_tcm_ts.ko + LOCAL_MODULE_KBUILD_NAME := synaptics_tcm_ts.ko + LOCAL_MODULE_TAGS := optional + #LOCAL_MODULE_DEBUG_ENABLE := true + LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) + include $(DLKM_DIR)/Build_external_kernelmodule.mk + ########################################################### + +endif #kona + endif # DLKM check +endif diff --git a/qcom/opensource/touch-drivers/BUILD.bazel b/qcom/opensource/touch-drivers/BUILD.bazel new file mode 100644 index 0000000000..304267ae29 --- /dev/null +++ b/qcom/opensource/touch-drivers/BUILD.bazel @@ -0,0 +1,82 @@ +load("//build/kernel/kleaf:kernel.bzl", "ddk_headers") + +package( + default_visibility = [ + "//visibility:public"], +) + +ddk_headers( + name = "goodix_ts_headers", + hdrs = glob([ + "goodix_berlin_driver/*.h", + "qts/*.h" + ] + ) +) + +ddk_headers( + name = "nt36xxx_headers", + hdrs = glob([ + "nt36xxx/*.h" + ] + ) +) + +ddk_headers( + name = "focaltech_headers", + hdrs = glob([ + "focaltech_touch/*.h" + ] + ) +) + +ddk_headers( + name = "synaptics_tcm_headers", + hdrs = glob([ + "synaptics_tcm/*.h" + ] + ) +) + +ddk_headers( + name = "config_headers", + hdrs = glob([ + "config/*.h" + ] + ), + includes = ["config"] +) + +ddk_headers( + name = "glink_interface_ts_headers", + hdrs = glob([ + "glink_interface_ts/*.h" + ] + ), + includes = ["glink_interface_ts"] +) + +ddk_headers( + name = "pt_headers", + hdrs = glob([ + "pt/*.h" + ] + ) +) + +ddk_headers( + name = "raydium_headers", + hdrs = glob([ + "raydium/*.h", + "raydium/chip_raydium/*.h" + ] + ) +) + +ddk_headers( + name = "touch_drivers_headers", + hdrs = [":goodix_ts_headers", ":nt36xxx_headers", ":focaltech_headers", ":synaptics_tcm_headers", ":glink_interface_ts_headers", ":pt_headers", ":raydium_headers", ":config_headers"] +) + +load(":target.bzl", "define_touch_target") +define_touch_target() diff --git a/qcom/opensource/touch-drivers/Kbuild b/qcom/opensource/touch-drivers/Kbuild new file mode 100644 index 0000000000..79fde92fc5 --- /dev/null +++ b/qcom/opensource/touch-drivers/Kbuild @@ -0,0 +1,247 @@ + +KDIR := $(TOP)/kernel_platform/common + +ifeq ($(CONFIG_ARCH_WAIPIO), y) + include $(TOUCH_ROOT)/config/gki_waipiotouch.conf + LINUX_INC += -include $(TOUCH_ROOT)/config/gki_waipiotouchconf.h +endif + +ifeq ($(CONFIG_ARCH_KALAMA), y) + include $(TOUCH_ROOT)/config/gki_kalamatouch.conf + LINUX_INC += -include $(TOUCH_ROOT)/config/gki_kalamatouchconf.h +endif + +ifeq ($(CONFIG_ARCH_KHAJE), y) + include $(TOUCH_ROOT)/config/gki_khajetouch.conf + LINUX_INC += -include $(TOUCH_ROOT)/config/gki_khajetouchconf.h +endif + +ifeq ($(CONFIG_ARCH_PINEAPPLE), y) + include $(TOUCH_ROOT)/config/gki_pineappletouch.conf + LINUX_INC += -include $(TOUCH_ROOT)/config/gki_pineappletouchconf.h +endif + +ifeq ($(CONFIG_ARCH_MONACO), y) + include $(TOUCH_ROOT)/config/gki_monacotouch.conf + LINUX_INC += -include $(TOUCH_ROOT)/config/gki_monacotouchconf.h +endif + +ifeq ($(CONFIG_ARCH_KONA), y) + include $(TOUCH_ROOT)/config/gki_konatouch.conf + LINUX_INC += -include $(TOUCH_ROOT)/config/gki_konatouchconf.h +endif + +ifeq ($(CONFIG_ARCH_BLAIR), y) + include $(TOUCH_ROOT)/config/gki_blairtouch.conf + LINUX_INC += -include $(TOUCH_ROOT)/config/gki_blairtouchconf.h +endif + +ifeq ($(CONFIG_ARCH_CROW), y) + include $(TOUCH_ROOT)/config/gki_crowtouch.conf + LINUX_INC += -include $(TOUCH_ROOT)/config/gki_crowtouchconf.h +endif + +ifeq ($(CONFIG_ARCH_TRINKET), y) + include $(TOUCH_ROOT)/config/gki_trinkettouch.conf + LINUX_INC += -include $(TOUCH_ROOT)/config/gki_trinkettouchconf.h +endif + +ifeq ($(CONFIG_ARCH_BENGAL), y) + include $(TOUCH_ROOT)/config/gki_bengaltouch.conf + LINUX_INC += -include $(TOUCH_ROOT)/config/gki_bengaltouchconf.h +endif + +LINUX_INC += -Iinclude/linux \ + -Iinclude/linux/drm \ + -Iinclude/linux/gunyah \ + -Iinclude/linux/input + +CDEFINES += -DANI_LITTLE_BYTE_ENDIAN \ + -DANI_LITTLE_BIT_ENDIAN \ + -DDOT11F_LITTLE_ENDIAN_HOST \ + -DANI_COMPILER_TYPE_GCC \ + -DANI_OS_TYPE_ANDROID=6 \ + -DPTT_SOCK_SVC_ENABLE \ + -Wall\ + -Werror\ + -D__linux__ + +KBUILD_CPPFLAGS += $(CDEFINES) + +ccflags-y += $(LINUX_INC) + +ifeq ($(call cc-option-yn, -Wmaybe-uninitialized),y) +EXTRA_CFLAGS += -Wmaybe-uninitialized +endif + +ifeq ($(call cc-option-yn, -Wheader-guard),y) +EXTRA_CFLAGS += -Wheader-guard +endif + +######### CONFIG_MSM_TOUCH ######## + +ifeq ($(CONFIG_TOUCHSCREEN_SYNAPTICS_DSX), y) + + LINUX_INC += -include $(TOUCH_ROOT)/synaptics_dsx/synaptics_dsx.h + LINUX_INC += -include $(TOUCH_ROOT)/synaptics_dsx/synaptics_dsx_core.h + + synaptics_dsx-y := \ + ./synaptics_dsx/synaptics_dsx_core.o \ + ./synaptics_dsx/synaptics_dsx_i2c.o + + obj-$(CONFIG_MSM_TOUCH) += synaptics_dsx.o +endif + +ifeq ($(CONFIG_TOUCH_FOCALTECH), y) + LINUX_INC += -include $(TOUCH_ROOT)/focaltech_touch/focaltech_common.h + LINUX_INC += -include $(TOUCH_ROOT)/focaltech_touch/focaltech_config.h + LINUX_INC += -include $(TOUCH_ROOT)/focaltech_touch/focaltech_core.h + LINUX_INC += -include $(TOUCH_ROOT)/focaltech_touch/focaltech_flash.h + + focaltech_fts-y := \ + ./focaltech_touch/focaltech_core.o \ + ./focaltech_touch/focaltech_ex_fun.o \ + ./focaltech_touch/focaltech_ex_mode.o \ + ./focaltech_touch/focaltech_gesture.o \ + ./focaltech_touch/focaltech_esdcheck.o \ + ./focaltech_touch/focaltech_point_report_check.o \ + ./focaltech_touch/focaltech_i2c.o \ + ./focaltech_touch/focaltech_flash.o \ + ./focaltech_touch/focaltech_flash/focaltech_upgrade_ft3518.o + + obj-$(CONFIG_MSM_TOUCH) += focaltech_fts.o +endif + +ifeq ($(CONFIG_TOUCHSCREEN_NT36XXX_I2C), y) + LINUX_INC += -include $(TOUCH_ROOT)/nt36xxx/nt36xxx.h + LINUX_INC += -include $(TOUCH_ROOT)/nt36xxx/nt36xxx_mem_map.h + LINUX_INC += -include $(TOUCH_ROOT)/nt36xxx/nt36xxx_mp_ctrlram.h + + nt36xxx-i2c-y := \ + ./nt36xxx/nt36xxx.o \ + ./nt36xxx/nt36xxx_fw_update.o \ + ./nt36xxx/nt36xxx_ext_proc.o \ + ./nt36xxx/nt36xxx_mp_ctrlram.o + + obj-$(CONFIG_MSM_TOUCH) += nt36xxx-i2c.o +endif + +ifeq ($(CONFIG_TOUCHSCREEN_GOODIX_BRL), y) + LINUX_INC += -include $(TOUCH_ROOT)/goodix_berlin_driver/goodix_ts_core.h + LINUX_INC += -include $(TOUCH_ROOT)/qts/qts_core.h + LINUX_INC += -include $(TOUCH_ROOT)/qts/qts_core_common.h + + goodix_ts-y := \ + ./goodix_berlin_driver/goodix_ts_core.o \ + ./goodix_berlin_driver/goodix_brl_hw.o \ + ./goodix_berlin_driver/goodix_cfg_bin.o \ + ./goodix_berlin_driver/goodix_ts_utils.o \ + ./goodix_berlin_driver/goodix_brl_fwupdate.o \ + ./goodix_berlin_driver/goodix_ts_tools.o \ + ./goodix_berlin_driver/goodix_ts_gesture.o \ + ./goodix_berlin_driver/goodix_ts_inspect.o \ + ./goodix_berlin_driver/goodix_brl_spi.o \ + ./goodix_berlin_driver/goodix_brl_i2c.o \ + ./qts/qts_core.o + + obj-$(CONFIG_MSM_TOUCH) += goodix_ts.o +endif + +ifeq ($(CONFIG_TOUCHSCREEN_ATMEL_MXT), y) + + atmel_mxt_ts-y := \ + ./atmel_mxt/atmel_mxt_ts.o + + obj-$(CONFIG_MSM_TOUCH) += atmel_mxt_ts.o +endif + +ifeq ($(CONFIG_TOUCHSCREEN_DUMMY), y) + dummy_ts-y := ./dummy_touch/dummy_touch.o + + obj-$(CONFIG_MSM_TOUCH) += dummy_ts.o +endif + +ifeq ($(CONFIG_TOUCHSCREEN_MSM_GLINK), y) + + LINUXINCLUDE += -I$(TOUCH_ROOT)/glink_interface_ts + + glink_comm-y := ./glink_interface_ts/glink_interface.o + + obj-$(CONFIG_MSM_TOUCH) += glink_comm.o +endif + +ifeq ($(CONFIG_TOUCHSCREEN_SYNAPTICS_TCM), y) + synaptics_tcm_ts-y := \ + ./synaptics_tcm/synaptics_tcm_core.o \ + ./synaptics_tcm/synaptics_tcm_i2c.o \ + ./synaptics_tcm/synaptics_tcm_touch.o + + obj-$(CONFIG_MSM_TOUCH) += synaptics_tcm_ts.o + +endif + +ifneq ($(CONFIG_ARCH_PINEAPPLE), y) + ifeq ($(CONFIG_TOUCHSCREEN_PARADE), y) + LINUX_INC += -include $(TOUCH_ROOT)/pt/pt_regs.h + LINUX_INC += -include $(TOUCH_ROOT)/pt/pt_core.h + LINUX_INC += -include $(TOUCH_ROOT)/pt/pt_platform.h + + pt_ts-y := \ + ./pt/pt_core.o \ + ./pt/pt_mt_common.o \ + ./pt/pt_platform.o \ + ./pt/pt_devtree.o \ + ./pt/pt_btn.o \ + ./pt/pt_mtb.o \ + ./pt/pt_proximity.o + + obj-$(CONFIG_MSM_TOUCH) += pt_ts.o + endif + + ifeq ($(CONFIG_TOUCHSCREEN_PARADE_I2C), y) + LINUX_INC += -include $(TOUCH_ROOT)/pt/pt_regs.h + + pt_i2c-y := \ + ./pt/pt_i2c.o + + obj-$(CONFIG_MSM_TOUCH) += pt_i2c.o + endif + + ifeq ($(CONFIG_TOUCHSCREEN_PARADE_DEVICE_ACCESS), y) + LINUX_INC += -include $(TOUCH_ROOT)/pt/pt_regs.h + + pt_device_access-y := \ + ./pt/pt_device_access.o + + obj-$(CONFIG_MSM_TOUCH) += pt_device_access.o + endif + + ifeq ($(CONFIG_TOUCHSCREEN_RM_TS), y) + LINUX_INC += -include $(TOUCH_ROOT)/raydium/Config.h + LINUX_INC += -include $(TOUCH_ROOT)/raydium/drv_interface.h + LINUX_INC += -include $(TOUCH_ROOT)/raydium/rad_fw_image_30.h + LINUX_INC += -include $(TOUCH_ROOT)/raydium/raydium_driver.h + LINUX_INC += -include $(TOUCH_ROOT)/raydium/raydium_selftest.h + LINUX_INC += -include $(TOUCH_ROOT)/raydium/tpselftest_30.h + LINUX_INC += -include $(TOUCH_ROOT)/raydium/chip_raydium/f303_ic_control.h + LINUX_INC += -include $(TOUCH_ROOT)/raydium/chip_raydium/f303_ic_reg.h + LINUX_INC += -include $(TOUCH_ROOT)/raydium/chip_raydium/f303_ic_test.h + LINUX_INC += -include $(TOUCH_ROOT)/raydium/chip_raydium/ic_drv_global.h + LINUX_INC += -include $(TOUCH_ROOT)/raydium/chip_raydium/ic_drv_interface.h + + raydium_ts-y := \ + ./raydium/drv_interface.o \ + ./raydium/raydium_driver.o \ + ./raydium/raydium_fw_update.o \ + ./raydium/raydium_selftest.o \ + ./raydium/raydium_sysfs.o \ + ./raydium/chip_raydium/f303_ic_control.o \ + ./raydium/chip_raydium/f303_ic_test.o \ + ./raydium/chip_raydium/ic_drv_global.o \ + ./raydium/chip_raydium/ic_drv_interface.o + + obj-$(CONFIG_MSM_TOUCH) += raydium_ts.o + endif +endif # pineapple + +CDEFINES += -DBUILD_TIMESTAMP=\"$(shell date -u +'%Y-%m-%dT%H:%M:%SZ')\" diff --git a/qcom/opensource/touch-drivers/Makefile b/qcom/opensource/touch-drivers/Makefile new file mode 100644 index 0000000000..d86c29c187 --- /dev/null +++ b/qcom/opensource/touch-drivers/Makefile @@ -0,0 +1,16 @@ + +KBUILD_OPTIONS+= TOUCH_ROOT=$(KERNEL_SRC)/$(M) +KBUILD_OPTIONS += MODNAME?=touch_dlkm + +all: + $(MAKE) -C $(KERNEL_SRC) M=$(M) modules $(KBUILD_OPTIONS) + +modules_install: + $(MAKE) INSTALL_MOD_STRIP=1 -C $(KERNEL_SRC) M=$(M) modules_install + +%: + $(MAKE) -C $(KERNEL_SRC) M=$(M) $@ $(KBUILD_OPTIONS) + +clean: + rm -f *.o *.ko *.mod.c *.mod.o *~ .*.cmd Module.symvers + rm -rf .tmp_versions diff --git a/qcom/opensource/touch-drivers/Makefile.am b/qcom/opensource/touch-drivers/Makefile.am new file mode 100644 index 0000000000..17208741c0 --- /dev/null +++ b/qcom/opensource/touch-drivers/Makefile.am @@ -0,0 +1,24 @@ + +TOUCH_ROOT=$(ROOTDIR)vendor/qcom/opensource/touch-drivers +KBUILD_OPTIONS := TOUCH_ROOT=$(TOUCH_ROOT) CONFIG_MSM_TOUCH=m + +ifeq ($(TARGET_SUPPORT),genericarmv8) + KBUILD_OPTIONS += CONFIG_ARCH_WAIPIO=y +endif + +ifeq ($(TARGET_SUPPORT),genericarmv8) + KBUILD_OPTIONS += CONFIG_ARCH_PINEAPPLE=y +endif + +all: + $(MAKE) -C $(KERNEL_SRC) M=$(M) modules $(KBUILD_OPTIONS) + +modules_install: + $(MAKE) INSTALL_MOD_STRIP=1 -C $(KERNEL_SRC) M=$(M) modules_install + +%: + $(MAKE) -C $(KERNEL_SRC) M=$(M) $@ $(KBUILD_OPTIONS) + +clean: + rm -f *.o *.ko *.mod.c *.mod.o *~ .*.cmd Module.symvers + rm -rf .tmp_versions diff --git a/qcom/opensource/touch-drivers/NOTICE b/qcom/opensource/touch-drivers/NOTICE new file mode 100644 index 0000000000..4c2fafdd89 --- /dev/null +++ b/qcom/opensource/touch-drivers/NOTICE @@ -0,0 +1,191 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2012-2019, The Linux Foundation. All rights reserved. +*/ + +/* + * + * Raydium TouchScreen driver. + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * Qualcomm Innovation Center, Inc. chooses to use it under GPLv2 + * Copyright (c) 2021 Raydium tech Ltd. + * + * 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. + * + * BSD LICENSE + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. or Linaro Ltd. nor the names of + * its contributors may be used to endorse or promote products + * derived from this software without specific prior written + * permission. + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * + * Parade TouchScreen driver. + * + * Copyright (C) 2015-2020 Parade Technologies + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * 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. + * + * Contact Parade Technologies at www.paradetech.com + */ + +/* + * + * FocalTech fts TouchScreen driver. + * + * Copyright (c) 2012-2019, Focaltech Ltd. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* + * Synaptics DSX touchscreen driver + * + * Copyright (C) 2012-2016 Synaptics Incorporated. All rights reserved. + * + * Copyright (c) 2018-2021 The Linux Foundation. All rights reserved. + * Copyright (C) 2012 Alexandra Chin + * Copyright (C) 2012 Scott Lin + * + * 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. + */ + +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2010 - 2018 Novatek, Inc. + * + * $Revision: 47247 $ + * $Date: 2019-07-10 10:41:36 +0800 (Wed, 10 Jul 2019) $ + * + * 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. + * + */ + +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * FTS Capacitive touch screen controller (FingerTipS) + * + * Copyright (C) 2016-2019, STMicroelectronics Limited. + * Authors: AMG(Analog Mems Group) + * + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +/* + * Goodix Touchscreen Driver + * Copyright (C) 2020 - 2021 Goodix, Inc. + * + * 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 a reference + * to you, when you are integrating the GOODiX's CTP IC into your system, + * 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. + * + */ + +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Atmel maXTouch Touchscreen driver + * + * Copyright (C) 2010 Samsung Electronics Co.Ltd + * Copyright (C) 2011-2014 Atmel Corporation + * Copyright (C) 2012 Google, Inc. + * Copyright (C) 2016 Zodiac Inflight Innovations + * + * Author: Joonyoung Shim + */ diff --git a/qcom/opensource/touch-drivers/atmel_mxt/atmel_mxt_ts.c b/qcom/opensource/touch-drivers/atmel_mxt/atmel_mxt_ts.c new file mode 100644 index 0000000000..7ad2743864 --- /dev/null +++ b/qcom/opensource/touch-drivers/atmel_mxt/atmel_mxt_ts.c @@ -0,0 +1,4014 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Atmel maXTouch Touchscreen driver + * + * Copyright (C) 2010 Samsung Electronics Co.Ltd + * Copyright (C) 2011-2014 Atmel Corporation + * Copyright (C) 2012 Google, Inc. + * Copyright (C) 2016 Zodiac Inflight Innovations + * + * Author: Joonyoung Shim + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_DRM +#include +#endif + +/* Firmware files */ +#define MXT_FW_NAME "maxtouch.fw" +#define MXT_CFG_NAME "maxtouch.cfg" +#define MXT_CFG_MAGIC "OBP_RAW V1" + +/* Registers */ +#define MXT_OBJECT_START 0x07 +#define MXT_OBJECT_SIZE 6 +#define MXT_INFO_CHECKSUM_SIZE 3 +#define MXT_MAX_BLOCK_WRITE 256 + +/* Object types */ +#define MXT_DEBUG_DIAGNOSTIC_T37 37 +#define MXT_GEN_MESSAGE_T5 5 +#define MXT_GEN_COMMAND_T6 6 +#define MXT_GEN_POWER_T7 7 +#define MXT_GEN_ACQUIRE_T8 8 +#define MXT_GEN_DATASOURCE_T53 53 +#define MXT_TOUCH_MULTI_T9 9 +#define MXT_TOUCH_KEYARRAY_T15 15 +#define MXT_TOUCH_PROXIMITY_T23 23 +#define MXT_TOUCH_PROXKEY_T52 52 +#define MXT_PROCI_GRIPFACE_T20 20 +#define MXT_PROCG_NOISE_T22 22 +#define MXT_PROCI_ONETOUCH_T24 24 +#define MXT_PROCI_TWOTOUCH_T27 27 +#define MXT_PROCI_GRIP_T40 40 +#define MXT_PROCI_PALM_T41 41 +#define MXT_PROCI_TOUCHSUPPRESSION_T42 42 +#define MXT_PROCI_STYLUS_T47 47 +#define MXT_PROCG_NOISESUPPRESSION_T48 48 +#define MXT_SPT_COMMSCONFIG_T18 18 +#define MXT_SPT_GPIOPWM_T19 19 +#define MXT_SPT_SELFTEST_T25 25 +#define MXT_SPT_CTECONFIG_T28 28 +#define MXT_SPT_USERDATA_T38 38 +#define MXT_SPT_DIGITIZER_T43 43 +#define MXT_SPT_MESSAGECOUNT_T44 44 +#define MXT_SPT_CTECONFIG_T46 46 +#define MXT_SPT_DYNAMICCONFIGURATIONCONTAINER_T71 71 +#define MXT_TOUCH_MULTITOUCHSCREEN_T100 100 + +/* Delay times */ +#define MXT_BACKUP_TIME 50 /* msec */ +#define MXT_RESET_TIME 200 /* msec */ +#define MXT_RESET_TIMEOUT 3000 /* msec */ +#define MXT_CRC_TIMEOUT 1000 /* msec */ +#define MXT_FW_RESET_TIME 3000 /* msec */ +#define MXT_FW_CHG_TIMEOUT 300 /* msec */ +#define MXT_WAKEUP_TIME 25 /* msec */ +#define MXT_REGULATOR_DELAY 150 /* msec */ +#define MXT_POWERON_DELAY 150 /* msec */ + +/* recommended voltage specifications */ +#define MXT_VDD_VTG_MIN_UV 1800000 +#define MXT_VDD_VTG_MAX_UV 1800000 +#define MXT_AVDD_VTG_MIN_UV 3000000 +#define MXT_AVDD_VTG_MAX_UV 3300000 +#define MXT_XVDD_VTG_MIN_UV 2700000 +#define MXT_XVDD_VTG_MAX_UV 10000000 + +/* recommended load specifications */ +#define MXT_ENABLE_LOAD 10000 +#define MXT_DISABLE_LOAD 0 + +/* MXT_GEN_MESSAGE_T5 object */ +#define MXT_RPTID_NOMSG 0xff + +/* MXT_GEN_COMMAND_T6 field */ +#define MXT_COMMAND_RESET 0 +#define MXT_COMMAND_BACKUPNV 1 +#define MXT_COMMAND_CALIBRATE 2 +#define MXT_COMMAND_REPORTALL 3 +#define MXT_COMMAND_DIAGNOSTIC 5 + +/* Define for T6 status byte */ +#define MXT_T6_STATUS_RESET BIT(7) +#define MXT_T6_STATUS_OFL BIT(6) +#define MXT_T6_STATUS_SIGERR BIT(5) +#define MXT_T6_STATUS_CAL BIT(4) +#define MXT_T6_STATUS_CFGERR BIT(3) +#define MXT_T6_STATUS_COMSERR BIT(2) + +/* MXT_GEN_POWER_T7 field */ +struct t7_config { + u8 idle; + u8 active; +} __packed; + +#define MXT_POWER_CFG_RUN 0 +#define MXT_POWER_CFG_DEEPSLEEP 1 + +/* MXT_TOUCH_MULTI_T9 field */ +#define MXT_T9_CTRL 0 +#define MXT_T9_XSIZE 3 +#define MXT_T9_YSIZE 4 +#define MXT_T9_ORIENT 9 +#define MXT_T9_RANGE 18 + +/* MXT_TOUCH_MULTI_T9 status */ +#define MXT_T9_UNGRIP BIT(0) +#define MXT_T9_SUPPRESS BIT(1) +#define MXT_T9_AMP BIT(2) +#define MXT_T9_VECTOR BIT(3) +#define MXT_T9_MOVE BIT(4) +#define MXT_T9_RELEASE BIT(5) +#define MXT_T9_PRESS BIT(6) +#define MXT_T9_DETECT BIT(7) + +struct t9_range { + __le16 x; + __le16 y; +} __packed; + +/* MXT_TOUCH_MULTI_T9 orient */ +#define MXT_T9_ORIENT_SWITCH BIT(0) +#define MXT_T9_ORIENT_INVERTX BIT(1) +#define MXT_T9_ORIENT_INVERTY BIT(2) + +/* MXT_SPT_COMMSCONFIG_T18 */ +#define MXT_COMMS_CTRL 0 +#define MXT_COMMS_CMD 1 +#define MXT_COMMS_RETRIGEN BIT(6) + +/* MXT_DEBUG_DIAGNOSTIC_T37 */ +#define MXT_DIAGNOSTIC_PAGEUP 0x01 +#define MXT_DIAGNOSTIC_DELTAS 0x10 +#define MXT_DIAGNOSTIC_REFS 0x11 +#define MXT_DIAGNOSTIC_SIZE 128 + +#define MXT_FAMILY_1386 160 +#define MXT1386_COLUMNS 3 +#define MXT1386_PAGES_PER_COLUMN 8 + +struct t37_debug { +#ifdef CONFIG_TOUCHSCREEN_ATMEL_MXT_T37 + u8 mode; + u8 page; + u8 data[MXT_DIAGNOSTIC_SIZE]; +#endif +}; + +/* Define for MXT_GEN_COMMAND_T6 */ +#define MXT_BOOT_VALUE 0xa5 +#define MXT_RESET_VALUE 0x01 +#define MXT_BACKUP_VALUE 0x55 + +/* T100 Multiple Touch Touchscreen */ +#define MXT_T100_CTRL 0 +#define MXT_T100_CFG1 1 +#define MXT_T100_TCHAUX 3 +#define MXT_T100_XSIZE 9 +#define MXT_T100_XRANGE 13 +#define MXT_T100_YSIZE 20 +#define MXT_T100_YRANGE 24 + +#define MXT_T100_CFG_SWITCHXY BIT(5) +#define MXT_T100_CFG_INVERTY BIT(6) +#define MXT_T100_CFG_INVERTX BIT(7) + +#define MXT_T100_TCHAUX_VECT BIT(0) +#define MXT_T100_TCHAUX_AMPL BIT(1) +#define MXT_T100_TCHAUX_AREA BIT(2) + +#define MXT_T100_DETECT BIT(7) +#define MXT_T100_TYPE_MASK 0x70 + +enum t100_type { + MXT_T100_TYPE_FINGER = 1, + MXT_T100_TYPE_PASSIVE_STYLUS = 2, + MXT_T100_TYPE_HOVERING_FINGER = 4, + MXT_T100_TYPE_GLOVE = 5, + MXT_T100_TYPE_LARGE_TOUCH = 6, +}; + +#define MXT_DISTANCE_ACTIVE_TOUCH 0 +#define MXT_DISTANCE_HOVERING 1 + +#define MXT_TOUCH_MAJOR_DEFAULT 1 +#define MXT_PRESSURE_DEFAULT 1 + +/* Delay times */ +#define MXT_BACKUP_TIME 50 /* msec */ +#define MXT_RESET_GPIO_TIME 20 /* msec */ +#define MXT_RESET_INVALID_CHG 100 /* msec */ +#define MXT_RESET_TIME 200 /* msec */ +#define MXT_RESET_TIMEOUT 3000 /* msec */ +#define MXT_CRC_TIMEOUT 1000 /* msec */ +#define MXT_FW_RESET_TIME 3000 /* msec */ +#define MXT_FW_CHG_TIMEOUT 300 /* msec */ + +/* Command to unlock bootloader */ +#define MXT_UNLOCK_CMD_MSB 0xaa +#define MXT_UNLOCK_CMD_LSB 0xdc + +/* Bootloader mode status */ +#define MXT_WAITING_BOOTLOAD_CMD 0xc0 /* valid 7 6 bit only */ +#define MXT_WAITING_FRAME_DATA 0x80 /* valid 7 6 bit only */ +#define MXT_FRAME_CRC_CHECK 0x02 +#define MXT_FRAME_CRC_FAIL 0x03 +#define MXT_FRAME_CRC_PASS 0x04 +#define MXT_APP_CRC_FAIL 0x40 /* valid 7 8 bit only */ +#define MXT_BOOT_STATUS_MASK 0x3f +#define MXT_BOOT_EXTENDED_ID BIT(5) +#define MXT_BOOT_ID_MASK 0x1f + +/* Touchscreen absolute values */ +#define MXT_MAX_AREA 0xff + +#define MXT_PIXELS_PER_MM 20 + +#define MXT_COORDS_ARR_SIZE 4 + +struct mxt_info { + u8 family_id; + u8 variant_id; + u8 version; + u8 build; + u8 matrix_xsize; + u8 matrix_ysize; + u8 object_num; +}; + +struct mxt_object { + u8 type; + u16 start_address; + u8 size_minus_one; + u8 instances_minus_one; + u8 num_report_ids; +} __packed; + +struct mxt_dbg { + u16 t37_address; + u16 diag_cmd_address; + struct t37_debug *t37_buf; + unsigned int t37_pages; + unsigned int t37_nodes; + + struct v4l2_device v4l2; + struct v4l2_pix_format format; + struct video_device vdev; + struct vb2_queue queue; + struct mutex lock; + int input; +}; + +enum v4l_dbg_inputs { + MXT_V4L_INPUT_DELTAS, + MXT_V4L_INPUT_REFS, + MXT_V4L_INPUT_MAX, +}; + +enum mxt_suspend_mode { + MXT_SUSPEND_DEEP_SLEEP = 0, + MXT_SUSPEND_T9_CTRL = 1, +}; + +/* Config update context */ +struct mxt_cfg { + u8 *raw; + size_t raw_size; + off_t raw_pos; + + u8 *mem; + size_t mem_size; + int start_ofs; + + struct mxt_info info; +}; + +/* Each client has this additional data */ +struct mxt_data { + struct i2c_client *client; + struct input_dev *input_dev; + char phys[64]; /* device physical location */ + struct mxt_object *object_table; + struct mxt_info *info; + void *raw_info_block; + unsigned int irq; + unsigned int max_x; + unsigned int max_y; + bool invertx; + bool inverty; + bool xy_switch; + u8 xsize; + u8 ysize; + bool in_bootloader; + u16 mem_size; + u8 t100_aux_ampl; + u8 t100_aux_area; + u8 t100_aux_vect; + u8 max_reportid; + u32 config_crc; + u32 info_crc; + u8 bootloader_addr; + u8 *msg_buf; + u8 *cmd_buf; + u8 *read_buf; + u8 t6_status; + bool update_input; + u8 last_message_count; + u8 num_touchids; + u8 multitouch; + struct t7_config t7_cfg; + struct mxt_dbg dbg; + struct gpio_desc *reset_gpio; + struct gpio_desc *irq_gpio; + bool use_retrigen_workaround; + + /* Cached parameters from object table */ + u16 T5_address; + u8 T5_msg_size; + u8 T6_reportid; + u16 T6_address; + u16 T7_address; + u16 T71_address; + u8 T9_reportid_min; + u8 T9_reportid_max; + u16 T18_address; + u8 T19_reportid; + u16 T44_address; + u8 T100_reportid_min; + u8 T100_reportid_max; + + /* for fw update in bootloader */ + struct completion bl_completion; + + /* for reset handling */ + struct completion reset_completion; + + /* for config update handling */ + struct completion crc_completion; + + /* Enable reporting of input events */ + bool enable_reporting; + + /* Indicates whether device is in suspend */ + bool suspended; + + u32 *t19_keymap; + unsigned int t19_num_keys; + + enum mxt_suspend_mode suspend_mode; + + bool use_regulator; + struct regulator *reg_vdd; + struct regulator *reg_avdd; + struct regulator *reg_xvdd; + struct pinctrl *ts_pinctrl; + struct pinctrl_state *gpio_state_active; + struct pinctrl_state *gpio_state_suspend; + + u32 panel_minx, panel_miny, panel_maxx, panel_maxy; + u32 disp_minx, disp_miny, disp_maxx, disp_maxy; + void *notifier_cookie; +}; + +struct mxt_vb2_buffer { + struct vb2_buffer vb; + struct list_head list; +}; + +static size_t mxt_obj_size(const struct mxt_object *obj) +{ + return obj->size_minus_one + 1; +} + +static size_t mxt_obj_instances(const struct mxt_object *obj) +{ + return obj->instances_minus_one + 1; +} + +static int mxt_regulator_configure(struct mxt_data *data, bool enable); + +static bool mxt_object_readable(unsigned int type) +{ + switch (type) { + case MXT_GEN_COMMAND_T6: + case MXT_GEN_POWER_T7: + case MXT_GEN_ACQUIRE_T8: + case MXT_GEN_DATASOURCE_T53: + case MXT_TOUCH_MULTI_T9: + case MXT_TOUCH_KEYARRAY_T15: + case MXT_TOUCH_PROXIMITY_T23: + case MXT_TOUCH_PROXKEY_T52: + case MXT_TOUCH_MULTITOUCHSCREEN_T100: + case MXT_PROCI_GRIPFACE_T20: + case MXT_PROCG_NOISE_T22: + case MXT_PROCI_ONETOUCH_T24: + case MXT_PROCI_TWOTOUCH_T27: + case MXT_PROCI_GRIP_T40: + case MXT_PROCI_PALM_T41: + case MXT_PROCI_TOUCHSUPPRESSION_T42: + case MXT_PROCI_STYLUS_T47: + case MXT_PROCG_NOISESUPPRESSION_T48: + case MXT_SPT_COMMSCONFIG_T18: + case MXT_SPT_GPIOPWM_T19: + case MXT_SPT_SELFTEST_T25: + case MXT_SPT_CTECONFIG_T28: + case MXT_SPT_USERDATA_T38: + case MXT_SPT_DIGITIZER_T43: + case MXT_SPT_CTECONFIG_T46: + case MXT_SPT_DYNAMICCONFIGURATIONCONTAINER_T71: + return true; + default: + return false; + } +} + +static void mxt_dump_message(struct mxt_data *data, u8 *message) +{ + dev_dbg(&data->client->dev, "message: %*ph\n", + data->T5_msg_size, message); +} + +static int mxt_wait_for_completion(struct mxt_data *data, + struct completion *comp, + unsigned int timeout_ms) +{ + struct device *dev = &data->client->dev; + unsigned long timeout = msecs_to_jiffies(timeout_ms); + long ret; + + ret = wait_for_completion_interruptible_timeout(comp, timeout); + if (ret < 0) { + return ret; + } else if (ret == 0) { + dev_err(dev, "Wait for completion timed out.\n"); + return -ETIMEDOUT; + } + return 0; +} + +static int mxt_bootloader_read(struct mxt_data *data, + u8 *val, unsigned int count) +{ + int ret; + struct i2c_msg msg; + + msg.addr = data->bootloader_addr; + msg.flags = data->client->flags & I2C_M_TEN; + msg.flags |= I2C_M_RD; + msg.len = count; + msg.buf = data->read_buf; + + ret = i2c_transfer(data->client->adapter, &msg, 1); + if (ret == 1) { + memcpy(val, msg.buf, count); + ret = 0; + } else { + ret = ret < 0 ? ret : -EIO; + dev_err(&data->client->dev, "%s: i2c recv failed (%d)\n", + __func__, ret); + } + + return ret; +} + +static int mxt_bootloader_write(struct mxt_data *data, + const u8 * const val, unsigned int count) +{ + int ret; + struct i2c_msg msg; + + msg.addr = data->bootloader_addr; + msg.flags = data->client->flags & I2C_M_TEN; + msg.len = count; + msg.buf = (u8 *)val; + + ret = i2c_transfer(data->client->adapter, &msg, 1); + if (ret == 1) { + ret = 0; + } else { + ret = ret < 0 ? ret : -EIO; + dev_err(&data->client->dev, "%s: i2c send failed (%d)\n", + __func__, ret); + } + + return ret; +} + +static int mxt_lookup_bootloader_address(struct mxt_data *data, bool retry) +{ + u8 appmode = data->client->addr; + u8 bootloader; + u8 family_id = data->info ? data->info->family_id : 0; + + switch (appmode) { + case 0x4a: + case 0x4b: + /* Chips after 1664S use different scheme */ + if (retry || family_id >= 0xa2) { + bootloader = appmode - 0x24; + break; + } + fallthrough; /* for normal case */ + case 0x4c: + case 0x4d: + case 0x5a: + case 0x5b: + bootloader = appmode - 0x26; + break; + + default: + dev_err(&data->client->dev, + "Appmode i2c address 0x%02x not found\n", + appmode); + return -EINVAL; + } + + data->bootloader_addr = bootloader; + return 0; +} + +static int mxt_probe_bootloader(struct mxt_data *data, bool alt_address) +{ + struct device *dev = &data->client->dev; + int error; + u8 val; + bool crc_failure; + + error = mxt_lookup_bootloader_address(data, alt_address); + if (error) + return error; + + error = mxt_bootloader_read(data, &val, 1); + if (error) + return error; + + /* Check app crc fail mode */ + crc_failure = (val & ~MXT_BOOT_STATUS_MASK) == MXT_APP_CRC_FAIL; + + dev_err(dev, "Detected bootloader, status:%02X%s\n", + val, crc_failure ? ", APP_CRC_FAIL" : ""); + + return 0; +} + +static u8 mxt_get_bootloader_version(struct mxt_data *data, u8 val) +{ + struct device *dev = &data->client->dev; + u8 ver = 0; + u8 *buf = kzalloc(4, GFP_KERNEL); + + if ((val & MXT_BOOT_EXTENDED_ID) && buf) { + if (mxt_bootloader_read(data, buf, 3) != 0) { + dev_err(dev, "%s: i2c failure\n", __func__); + return val; + } + + dev_dbg(dev, "Bootloader ID:%d Version:%d\n", + *(buf + 1), *(buf + 2)); + ver = *(buf + 0); + kfree(buf); + return ver; + } else { + dev_dbg(dev, "Bootloader ID:%d\n", val & MXT_BOOT_ID_MASK); + + return val; + } +} + +static int mxt_check_bootloader(struct mxt_data *data, unsigned int state, + bool wait) +{ + struct device *dev = &data->client->dev; + u8 val; + int ret; + +recheck: + if (wait) { + /* + * In application update mode, the interrupt + * line signals state transitions. We must wait for the + * CHG assertion before reading the status byte. + * Once the status byte has been read, the line is deasserted. + */ + ret = mxt_wait_for_completion(data, &data->bl_completion, + MXT_FW_CHG_TIMEOUT); + if (ret) { + /* + * TODO: handle -ERESTARTSYS better by terminating + * fw update process before returning to userspace + * by writing length 0x000 to device (iff we are in + * WAITING_FRAME_DATA state). + */ + dev_err(dev, "Update wait error %d\n", ret); + return ret; + } + } + + ret = mxt_bootloader_read(data, &val, 1); + if (ret) + return ret; + + if (state == MXT_WAITING_BOOTLOAD_CMD) + val = mxt_get_bootloader_version(data, val); + + switch (state) { + case MXT_WAITING_BOOTLOAD_CMD: + case MXT_WAITING_FRAME_DATA: + case MXT_APP_CRC_FAIL: + val &= ~MXT_BOOT_STATUS_MASK; + break; + case MXT_FRAME_CRC_PASS: + if (val == MXT_FRAME_CRC_CHECK) { + goto recheck; + } else if (val == MXT_FRAME_CRC_FAIL) { + dev_err(dev, "Bootloader CRC fail\n"); + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + if (val != state) { + dev_err(dev, "Invalid bootloader state %02X != %02X\n", + val, state); + return -EINVAL; + } + + return 0; +} + +static int mxt_send_bootloader_cmd(struct mxt_data *data, bool unlock) +{ + int ret; + u8 *buf = data->cmd_buf; + + if (unlock) { + *buf = MXT_UNLOCK_CMD_LSB; + *(buf + 1) = MXT_UNLOCK_CMD_MSB; + } else { + *buf = 0x01; + *(buf + 1) = 0x01; + } + + ret = mxt_bootloader_write(data, buf, 2); + if (ret) + return ret; + + return 0; +} + +static int __mxt_read_reg(struct mxt_data *data, u16 reg, u16 len, void *val) +{ + struct i2c_msg xfer[2]; + int ret; + u8 *buf = data->cmd_buf; + struct i2c_client *client = data->client; + + *buf = reg & 0xff; + *(buf + 1) = (reg >> 8) & 0xff; + + /* Write register */ + xfer[0].addr = client->addr; + xfer[0].flags = 0; + xfer[0].len = 2; + xfer[0].buf = buf; + + /* Read data */ + xfer[1].addr = client->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = len; + xfer[1].buf = val; + + ret = i2c_transfer(client->adapter, xfer, 2); + if (ret == 2) { + ret = 0; + } else { + if (ret >= 0) + ret = -EIO; + dev_err(&client->dev, "%s: i2c transfer failed (%d)\n", + __func__, ret); + } + + return ret; +} + +static int __mxt_write_reg(struct i2c_client *client, u16 reg, u16 len, + const void *val) +{ + u8 *buf; + size_t count; + int ret; + + count = len + 2; + buf = kmalloc(count, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + buf[0] = reg & 0xff; + buf[1] = (reg >> 8) & 0xff; + memcpy(&buf[2], val, len); + + ret = i2c_master_send(client, buf, count); + if (ret == count) { + ret = 0; + } else { + if (ret >= 0) + ret = -EIO; + dev_dbg(&client->dev, "%s: i2c send failed (%d)\n", + __func__, ret); + } + + kfree(buf); + return ret; +} + +static int mxt_write_reg(struct i2c_client *client, u16 reg, u8 val) +{ + return __mxt_write_reg(client, reg, 1, &val); +} + +static struct mxt_object * +mxt_get_object(struct mxt_data *data, u8 type) +{ + struct mxt_object *object; + int i; + + for (i = 0; i < data->info->object_num; i++) { + object = data->object_table + i; + if (object->type == type) + return object; + } + + dev_warn(&data->client->dev, "Invalid object type T%u\n", type); + return NULL; +} + +static void mxt_proc_t6_messages(struct mxt_data *data, u8 *msg) +{ + struct device *dev = &data->client->dev; + u8 status = msg[1]; + u32 crc = msg[2] | (msg[3] << 8) | (msg[4] << 16); + + if (crc != data->config_crc) { + data->config_crc = crc; + dev_dbg(dev, "T6 Config Checksum: 0x%06X\n", crc); + } + + complete(&data->crc_completion); + + /* Detect reset */ + if (status & MXT_T6_STATUS_RESET) + complete(&data->reset_completion); + + /* Output debug if status has changed */ + if (status != data->t6_status) + dev_dbg(dev, "T6 Status 0x%02X%s%s%s%s%s%s%s\n", + status, + status == 0 ? " OK" : "", + status & MXT_T6_STATUS_RESET ? " RESET" : "", + status & MXT_T6_STATUS_OFL ? " OFL" : "", + status & MXT_T6_STATUS_SIGERR ? " SIGERR" : "", + status & MXT_T6_STATUS_CAL ? " CAL" : "", + status & MXT_T6_STATUS_CFGERR ? " CFGERR" : "", + status & MXT_T6_STATUS_COMSERR ? " COMSERR" : ""); + + /* Save current status */ + data->t6_status = status; +} + +static int mxt_write_object(struct mxt_data *data, + u8 type, u8 offset, u8 val) +{ + struct mxt_object *object; + u16 reg; + + object = mxt_get_object(data, type); + if (!object || offset >= mxt_obj_size(object)) + return -EINVAL; + + reg = object->start_address; + return mxt_write_reg(data->client, reg + offset, val); +} + +static void mxt_input_button(struct mxt_data *data, u8 *message) +{ + struct input_dev *input = data->input_dev; + int i; + + for (i = 0; i < data->t19_num_keys; i++) { + if (data->t19_keymap[i] == KEY_RESERVED) + continue; + + /* Active-low switch */ + input_report_key(input, data->t19_keymap[i], + !(message[1] & BIT(i))); + } +} + +static void mxt_input_sync(struct mxt_data *data) +{ + input_mt_report_pointer_emulation(data->input_dev, + data->t19_num_keys); + input_sync(data->input_dev); +} + +static void mxt_proc_t9_message(struct mxt_data *data, u8 *message) +{ + struct device *dev = &data->client->dev; + struct input_dev *input_dev = data->input_dev; + int id; + u8 status; + int x; + int y; + int area; + int amplitude; + + id = message[0] - data->T9_reportid_min; + status = message[1]; + x = (message[2] << 4) | ((message[4] >> 4) & 0xf); + y = (message[3] << 4) | ((message[4] & 0xf)); + + /* Handle 10/12 bit switching */ + if (data->max_x < 1024) + x >>= 2; + if (data->max_y < 1024) + y >>= 2; + + area = message[5]; + amplitude = message[6]; + + dev_dbg(dev, + "[%u] %c%c%c%c%c%c%c%c x: %5u y: %5u area: %3u amp: %3u\n", + id, + (status & MXT_T9_DETECT) ? 'D' : '.', + (status & MXT_T9_PRESS) ? 'P' : '.', + (status & MXT_T9_RELEASE) ? 'R' : '.', + (status & MXT_T9_MOVE) ? 'M' : '.', + (status & MXT_T9_VECTOR) ? 'V' : '.', + (status & MXT_T9_AMP) ? 'A' : '.', + (status & MXT_T9_SUPPRESS) ? 'S' : '.', + (status & MXT_T9_UNGRIP) ? 'U' : '.', + x, y, area, amplitude); + + input_mt_slot(input_dev, id); + + if (status & MXT_T9_DETECT) { + /* + * Multiple bits may be set if the host is slow to read + * the status messages, indicating all the events that + * have happened. + */ + if (status & MXT_T9_RELEASE) { + input_mt_report_slot_inactive(input_dev); + mxt_input_sync(data); + } + + /* if active, pressure must be non-zero */ + if (!amplitude) + amplitude = MXT_PRESSURE_DEFAULT; + + /* Handle orientation */ + if (data->xy_switch) + swap(x, y); + if (data->invertx) + x = data->max_x - x; + if (data->inverty) + y = data->max_y - y; + + /* Touch active */ + input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, 1); + input_report_abs(input_dev, ABS_MT_POSITION_X, + x - data->disp_minx); + input_report_abs(input_dev, ABS_MT_POSITION_Y, + y - data->disp_miny); + input_report_abs(input_dev, ABS_MT_PRESSURE, amplitude); + input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, area); + } else { + /* Touch no longer active, close out slot */ + input_mt_report_slot_inactive(input_dev); + } + + data->update_input = true; +} + +static void mxt_proc_t100_message(struct mxt_data *data, u8 *message) +{ + struct device *dev = &data->client->dev; + struct input_dev *input_dev = data->input_dev; + int id; + u8 status; + u8 type = 0; + u16 x; + u16 y; + int distance = 0; + int tool = 0; + u8 major = 0; + u8 pressure = 0; + u8 orientation = 0; + + id = message[0] - data->T100_reportid_min - 2; + + /* ignore SCRSTATUS events */ + if (id < 0) + return; + + status = message[1]; + x = get_unaligned_le16(&message[2]); + y = get_unaligned_le16(&message[4]); + + if (status & MXT_T100_DETECT) { + type = (status & MXT_T100_TYPE_MASK) >> 4; + + switch (type) { + case MXT_T100_TYPE_HOVERING_FINGER: + tool = MT_TOOL_FINGER; + distance = MXT_DISTANCE_HOVERING; + + if (data->t100_aux_vect) + orientation = message[data->t100_aux_vect]; + + break; + + case MXT_T100_TYPE_FINGER: + case MXT_T100_TYPE_GLOVE: + tool = MT_TOOL_FINGER; + distance = MXT_DISTANCE_ACTIVE_TOUCH; + + if (data->t100_aux_area) + major = message[data->t100_aux_area]; + + if (data->t100_aux_ampl) + pressure = message[data->t100_aux_ampl]; + + if (data->t100_aux_vect) + orientation = message[data->t100_aux_vect]; + + break; + + case MXT_T100_TYPE_PASSIVE_STYLUS: + tool = MT_TOOL_PEN; + + /* + * Passive stylus is reported with size zero so + * hardcode. + */ + major = MXT_TOUCH_MAJOR_DEFAULT; + + if (data->t100_aux_ampl) + pressure = message[data->t100_aux_ampl]; + + break; + + case MXT_T100_TYPE_LARGE_TOUCH: + /* Ignore suppressed touch */ + break; + + default: + dev_dbg(dev, "Unexpected T100 type\n"); + return; + } + } + + /* + * Values reported should be non-zero if tool is touching the + * device + */ + if (!pressure && type != MXT_T100_TYPE_HOVERING_FINGER) + pressure = MXT_PRESSURE_DEFAULT; + + input_mt_slot(input_dev, id); + + if (status & MXT_T100_DETECT) { + dev_dbg(dev, "[%u] type:%u x:%u y:%u a:%02X p:%02X v:%02X\n", + id, type, x, y, major, pressure, orientation); + + /* Handle orientation */ + if (data->xy_switch) + swap(x, y); + if (data->invertx) + x = data->max_x - x; + if (data->inverty) + y = data->max_y - y; + + input_mt_report_slot_state(input_dev, tool, 1); + input_report_abs(input_dev, ABS_MT_POSITION_X, + x - data->disp_minx); + input_report_abs(input_dev, ABS_MT_POSITION_Y, + y - data->disp_miny); + input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, major); + input_report_abs(input_dev, ABS_MT_PRESSURE, pressure); + input_report_abs(input_dev, ABS_MT_DISTANCE, distance); + input_report_abs(input_dev, ABS_MT_ORIENTATION, orientation); + } else { + dev_dbg(dev, "[%u] release\n", id); + + /* close out slot */ + input_mt_report_slot_inactive(input_dev); + } + + data->update_input = true; +} + +static int mxt_proc_message(struct mxt_data *data, u8 *message) +{ + u8 report_id = message[0]; + + if (report_id == MXT_RPTID_NOMSG) + return 0; + + if (report_id == data->T6_reportid) { + mxt_proc_t6_messages(data, message); + } else if (!data->input_dev) { + /* + * Do not report events if input device + * is not yet registered. + */ + mxt_dump_message(data, message); + } else if (report_id >= data->T9_reportid_min && + report_id <= data->T9_reportid_max) { + mxt_proc_t9_message(data, message); + } else if (report_id >= data->T100_reportid_min && + report_id <= data->T100_reportid_max) { + mxt_proc_t100_message(data, message); + } else if (report_id == data->T19_reportid) { + mxt_input_button(data, message); + data->update_input = true; + } else { + mxt_dump_message(data, message); + } + + return 1; +} + +static int mxt_read_and_process_messages(struct mxt_data *data, u8 count) +{ + struct device *dev = &data->client->dev; + int ret; + int i; + u8 num_valid = 0; + + /* Safety check for msg_buf */ + if (count > data->max_reportid) + return -EINVAL; + + /* Process remaining messages if necessary */ + ret = __mxt_read_reg(data, data->T5_address, + data->T5_msg_size * count, data->msg_buf); + if (ret) { + dev_err(dev, "Failed to read %u messages (%d)\n", count, ret); + return ret; + } + + for (i = 0; i < count; i++) { + ret = mxt_proc_message(data, + data->msg_buf + data->T5_msg_size * i); + + if (ret == 1) + num_valid++; + } + + /* return number of messages read */ + return num_valid; +} + +static irqreturn_t mxt_process_messages_t44(struct mxt_data *data) +{ + struct device *dev = &data->client->dev; + int ret; + u8 count, num_left; + + /* Read T44 and T5 together */ + ret = __mxt_read_reg(data, data->T44_address, + data->T5_msg_size + 1, data->msg_buf); + if (ret) { + dev_err(dev, "Failed to read T44 and T5 (%d)\n", ret); + return IRQ_NONE; + } + + count = data->msg_buf[0]; + + /* + * This condition may be caused by the CHG line being configured in + * Mode 0. It results in unnecessary I2C operations but it is benign. + */ + if (count == 0) + return IRQ_NONE; + + if (count > data->max_reportid) { + dev_warn(dev, "T44 count %d exceeded max report id\n", count); + count = data->max_reportid; + } + + /* Process first message */ + ret = mxt_proc_message(data, data->msg_buf + 1); + if (ret < 0) { + dev_warn(dev, "Unexpected invalid message\n"); + return IRQ_NONE; + } + + num_left = count - 1; + + /* Process remaining messages if necessary */ + if (num_left) { + ret = mxt_read_and_process_messages(data, num_left); + if (ret < 0) + goto end; + else if (ret != num_left) + dev_warn(dev, "Unexpected invalid message\n"); + } + +end: + if (data->update_input) { + mxt_input_sync(data); + data->update_input = false; + } + + return IRQ_HANDLED; +} + +static int mxt_process_messages_until_invalid(struct mxt_data *data) +{ + struct device *dev = &data->client->dev; + int count, read; + u8 tries = 2; + + count = data->max_reportid; + + /* Read messages until we force an invalid */ + do { + read = mxt_read_and_process_messages(data, count); + if (read < count) + return 0; + } while (--tries); + + if (data->update_input) { + mxt_input_sync(data); + data->update_input = false; + } + + dev_err(dev, "CHG pin isn't cleared\n"); + return -EBUSY; +} + +static irqreturn_t mxt_process_messages(struct mxt_data *data) +{ + int total_handled, num_handled; + u8 count = data->last_message_count; + + if (count < 1 || count > data->max_reportid) + count = 1; + + /* include final invalid message */ + total_handled = mxt_read_and_process_messages(data, count + 1); + if (total_handled < 0) + return IRQ_NONE; + /* if there were invalid messages, then we are done */ + else if (total_handled <= count) + goto update_count; + + /* keep reading two msgs until one is invalid or reportid limit */ + do { + num_handled = mxt_read_and_process_messages(data, 2); + if (num_handled < 0) + return IRQ_NONE; + + total_handled += num_handled; + + if (num_handled < 2) + break; + } while (total_handled < data->num_touchids); + +update_count: + data->last_message_count = total_handled; + + if (data->update_input) { + mxt_input_sync(data); + data->update_input = false; + } + + return IRQ_HANDLED; +} + +static irqreturn_t mxt_interrupt(int irq, void *dev_id) +{ + struct mxt_data *data = dev_id; + + if (data->in_bootloader) { + /* bootloader state transition completion */ + complete(&data->bl_completion); + return IRQ_HANDLED; + } + + if (!data->object_table) + return IRQ_HANDLED; + + if (data->T44_address) { + return mxt_process_messages_t44(data); + } else { + return mxt_process_messages(data); + } +} + +static int mxt_t6_command(struct mxt_data *data, u16 cmd_offset, + u8 value, bool wait) +{ + u16 reg; + u8 command_register; + int timeout_counter = 0; + int ret; + + reg = data->T6_address + cmd_offset; + + ret = mxt_write_reg(data->client, reg, value); + if (ret) + return ret; + + if (!wait) + return 0; + + do { + msleep(20); + ret = __mxt_read_reg(data, reg, 1, data->read_buf); + if (ret) + return ret; + command_register = *(data->read_buf); + } while (command_register != 0 && timeout_counter++ <= 100); + + if (timeout_counter > 100) { + dev_err(&data->client->dev, "Command failed!\n"); + return -EIO; + } + + return 0; +} + +static int mxt_acquire_irq(struct mxt_data *data) +{ + int error; + + enable_irq(data->irq); + + if (data->use_retrigen_workaround) { + error = mxt_process_messages_until_invalid(data); + if (error) + return error; + } + + return 0; +} + +static int mxt_soft_reset(struct mxt_data *data) +{ + struct device *dev = &data->client->dev; + int ret = 0; + + dev_dbg(dev, "Resetting device\n"); + + disable_irq(data->irq); + + reinit_completion(&data->reset_completion); + + ret = mxt_t6_command(data, MXT_COMMAND_RESET, MXT_RESET_VALUE, false); + if (ret) + return ret; + + /* Ignore CHG line for 100ms after reset */ + msleep(MXT_RESET_INVALID_CHG); + + mxt_acquire_irq(data); + + ret = mxt_wait_for_completion(data, &data->reset_completion, + MXT_RESET_TIMEOUT); + if (ret) + return ret; + + return 0; +} + +static void mxt_update_crc(struct mxt_data *data, u8 cmd, u8 value) +{ + /* + * On failure, CRC is set to 0 and config will always be + * downloaded. + */ + data->config_crc = 0; + reinit_completion(&data->crc_completion); + + mxt_t6_command(data, cmd, value, true); + + /* + * Wait for crc message. On failure, CRC is set to 0 and config will + * always be downloaded. + */ + mxt_wait_for_completion(data, &data->crc_completion, MXT_CRC_TIMEOUT); +} + +static void mxt_calc_crc24(u32 *crc, u8 firstbyte, u8 secondbyte) +{ + static const unsigned int crcpoly = 0x80001B; + u32 result; + u32 data_word; + + data_word = (secondbyte << 8) | firstbyte; + result = ((*crc << 1) ^ data_word); + + if (result & 0x1000000) + result ^= crcpoly; + + *crc = result; +} + +static u32 mxt_calculate_crc(u8 *base, off_t start_off, off_t end_off) +{ + u32 crc = 0; + u8 *ptr = base + start_off; + u8 *last_val = base + end_off - 1; + + if (end_off < start_off) + return -EINVAL; + + while (ptr < last_val) { + mxt_calc_crc24(&crc, *ptr, *(ptr + 1)); + ptr += 2; + } + + /* if len is odd, fill the last byte with 0 */ + if (ptr == last_val) + mxt_calc_crc24(&crc, *ptr, 0); + + /* Mask to 24-bit */ + crc &= 0x00FFFFFF; + + return crc; +} + +static int mxt_check_retrigen(struct mxt_data *data) +{ + struct i2c_client *client = data->client; + int error; + int val = 0; + struct irq_data *irqd; + + data->use_retrigen_workaround = false; + + irqd = irq_get_irq_data(data->irq); + if (!irqd) + return -EINVAL; + + if (irqd_is_level_type(irqd)) + return 0; + + if (data->T18_address) { + error = __mxt_read_reg(data, + data->T18_address + MXT_COMMS_CTRL, + 1, &val); + if (error) + return error; + + if (val & MXT_COMMS_RETRIGEN) + return 0; + } + + dev_warn(&client->dev, "Enabling RETRIGEN workaround\n"); + data->use_retrigen_workaround = true; + return 0; +} + +static int mxt_prepare_cfg_mem(struct mxt_data *data, struct mxt_cfg *cfg) +{ + struct device *dev = &data->client->dev; + struct mxt_object *object; + unsigned int type, instance, size, byte_offset; + int offset; + int ret; + int i; + u16 reg; + u8 val; + + while (cfg->raw_pos < cfg->raw_size) { + /* Read type, instance, length */ + ret = sscanf(cfg->raw + cfg->raw_pos, "%x %x %x%n", + &type, &instance, &size, &offset); + if (ret == 0) { + /* EOF */ + break; + } else if (ret != 3) { + dev_err(dev, "Bad format: failed to parse object\n"); + return -EINVAL; + } + + if (cfg->raw_pos + offset >= cfg->raw_size) + return -EINVAL; + + cfg->raw_pos += offset; + + object = mxt_get_object(data, type); + if (!object) { + /* Skip object */ + for (i = 0; i < size; i++) { + ret = sscanf(cfg->raw + cfg->raw_pos, "%hhx%n", + &val, &offset); + if (ret != 1 || (cfg->raw_pos + offset >= + cfg->raw_size)) { + dev_err(dev, "Bad format in T%d at %d\n", + type, i); + return -EINVAL; + } + cfg->raw_pos += offset; + + } + continue; + } + + if (size > mxt_obj_size(object)) { + /* + * Either we are in fallback mode due to wrong + * config or config from a later fw version, + * or the file is corrupt or hand-edited. + */ + dev_warn(dev, "Discarding %zu byte(s) in T%u\n", + size - mxt_obj_size(object), type); + } else if (mxt_obj_size(object) > size) { + /* + * If firmware is upgraded, new bytes may be added to + * end of objects. It is generally forward compatible + * to zero these bytes - previous behaviour will be + * retained. However this does invalidate the CRC and + * will force fallback mode until the configuration is + * updated. We warn here but do nothing else - the + * malloc has zeroed the entire configuration. + */ + dev_warn(dev, "Zeroing %zu byte(s) in T%d\n", + mxt_obj_size(object) - size, type); + } + + if (instance >= mxt_obj_instances(object)) { + dev_err(dev, "Object instances exceeded!\n"); + return -EINVAL; + } + + reg = object->start_address + mxt_obj_size(object) * instance; + + for (i = 0; i < size; i++) { + ret = sscanf(cfg->raw + cfg->raw_pos, "%hhx%n", + &val, + &offset); + if (ret != 1 || + (cfg->raw_pos + offset >= cfg->raw_size)) { + dev_err(dev, "Bad format in T%d at %d\n", + type, i); + return -EINVAL; + } + cfg->raw_pos += offset; + + if (i > mxt_obj_size(object)) + continue; + + byte_offset = reg + i - cfg->start_ofs; + + if (byte_offset >= 0 && byte_offset < cfg->mem_size) { + *(cfg->mem + byte_offset) = val; + } else { + dev_err(dev, "Bad object: reg:%d, T%d, ofs=%d\n", + reg, object->type, byte_offset); + return -EINVAL; + } + } + } + + return 0; +} + +static int mxt_upload_cfg_mem(struct mxt_data *data, struct mxt_cfg *cfg) +{ + unsigned int byte_offset = 0; + int error; + + /* Write configuration as blocks */ + while (byte_offset < cfg->mem_size) { + unsigned int size = cfg->mem_size - byte_offset; + + if (size > MXT_MAX_BLOCK_WRITE) + size = MXT_MAX_BLOCK_WRITE; + + error = __mxt_write_reg(data->client, + cfg->start_ofs + byte_offset, + size, cfg->mem + byte_offset); + if (error) { + dev_err(&data->client->dev, + "Config write error, ret=%d\n", error); + return error; + } + + byte_offset += size; + } + + return 0; +} + +static int mxt_init_t7_power_cfg(struct mxt_data *data); + +/* + * mxt_update_cfg - download configuration to chip + * + * Atmel Raw Config File Format + * + * The first four lines of the raw config file contain: + * 1) Version + * 2) Chip ID Information (first 7 bytes of device memory) + * 3) Chip Information Block 24-bit CRC Checksum + * 4) Chip Configuration 24-bit CRC Checksum + * + * The rest of the file consists of one line per object instance: + * + * + * - 2-byte object type as hex + * - 2-byte object instance number as hex + * - 2-byte object size as hex + * - array of 1-byte hex values + */ +static int mxt_update_cfg(struct mxt_data *data, const struct firmware *fw) +{ + struct device *dev = &data->client->dev; + struct mxt_cfg cfg; + int ret; + int offset; + int i; + u32 info_crc, config_crc, calculated_crc; + u16 crc_start = 0; + + /* Make zero terminated copy of the OBP_RAW file */ + cfg.raw = kmemdup_nul(fw->data, fw->size, GFP_KERNEL); + if (!cfg.raw) + return -ENOMEM; + + cfg.raw_size = fw->size; + + mxt_update_crc(data, MXT_COMMAND_REPORTALL, 1); + + if (memcmp(cfg.raw, MXT_CFG_MAGIC, strlen(MXT_CFG_MAGIC))) { + dev_err(dev, "Unrecognised config file\n"); + ret = -EINVAL; + goto release_raw; + } + + cfg.raw_pos = strlen(MXT_CFG_MAGIC); + + /* Load information block and check */ + for (i = 0; i < sizeof(struct mxt_info); i++) { + ret = sscanf(cfg.raw + cfg.raw_pos, "%hhx%n", + (unsigned char *)&cfg.info + i, + &offset); + if (ret != 1 || (cfg.raw_pos + offset >= cfg.raw_size)) { + dev_err(dev, "Bad format\n"); + ret = -EINVAL; + goto release_raw; + } + + cfg.raw_pos += offset; + } + + if (cfg.info.family_id != data->info->family_id) { + dev_err(dev, "Family ID mismatch!\n"); + ret = -EINVAL; + goto release_raw; + } + + if (cfg.info.variant_id != data->info->variant_id) { + dev_err(dev, "Variant ID mismatch!\n"); + ret = -EINVAL; + goto release_raw; + } + + /* Read CRCs */ + ret = sscanf(cfg.raw + cfg.raw_pos, "%x%n", &info_crc, &offset); + if (ret != 1 || (cfg.raw_pos + offset >= cfg.raw_size)) { + dev_err(dev, "Bad format: failed to parse Info CRC\n"); + ret = -EINVAL; + goto release_raw; + } + cfg.raw_pos += offset; + + ret = sscanf(cfg.raw + cfg.raw_pos, "%x%n", &config_crc, &offset); + if (ret != 1 || (cfg.raw_pos + offset >= cfg.raw_size)) { + dev_err(dev, "Bad format: failed to parse Config CRC\n"); + ret = -EINVAL; + goto release_raw; + } + cfg.raw_pos += offset; + + /* + * The Info Block CRC is calculated over mxt_info and the object + * table. If it does not match then we are trying to load the + * configuration from a different chip or firmware version, so + * the configuration CRC is invalid anyway. + */ + if (info_crc == data->info_crc) { + if (config_crc == 0 || data->config_crc == 0) { + dev_info(dev, "CRC zero, attempting to apply config\n"); + } else if (config_crc == data->config_crc) { + dev_dbg(dev, "Config CRC 0x%06X: OK\n", + data->config_crc); + ret = 0; + goto release_raw; + } else { + dev_info(dev, "Config CRC 0x%06X: does not match file 0x%06X\n", + data->config_crc, config_crc); + } + } else { + dev_warn(dev, + "Warning: Info CRC error - device=0x%06X file=0x%06X\n", + data->info_crc, info_crc); + } + + /* Malloc memory to store configuration */ + cfg.start_ofs = MXT_OBJECT_START + + data->info->object_num * sizeof(struct mxt_object) + + MXT_INFO_CHECKSUM_SIZE; + cfg.mem_size = data->mem_size - cfg.start_ofs; + cfg.mem = kzalloc(cfg.mem_size, GFP_KERNEL); + if (!cfg.mem) { + ret = -ENOMEM; + goto release_raw; + } + + ret = mxt_prepare_cfg_mem(data, &cfg); + if (ret) + goto release_mem; + + /* Calculate crc of the received configs (not the raw config file) */ + if (data->T71_address) + crc_start = data->T71_address; + else if (data->T7_address) + crc_start = data->T7_address; + else + dev_warn(dev, "Could not find CRC start\n"); + + if (crc_start > cfg.start_ofs) { + calculated_crc = mxt_calculate_crc(cfg.mem, + crc_start - cfg.start_ofs, + cfg.mem_size); + + if (config_crc > 0 && config_crc != calculated_crc) + dev_warn(dev, "Config CRC in file inconsistent, calculated=%06X, file=%06X\n", + calculated_crc, config_crc); + } + + ret = mxt_upload_cfg_mem(data, &cfg); + if (ret) + goto release_mem; + + mxt_update_crc(data, MXT_COMMAND_BACKUPNV, MXT_BACKUP_VALUE); + + ret = mxt_check_retrigen(data); + if (ret) + goto release_mem; + + ret = mxt_soft_reset(data); + if (ret) + goto release_mem; + + dev_info(dev, "Config successfully updated\n"); + + /* T7 config may have changed */ + mxt_init_t7_power_cfg(data); + +release_mem: + kfree(cfg.mem); +release_raw: + kfree(cfg.raw); + return ret; +} + +static void mxt_free_input_device(struct mxt_data *data) +{ + if (data->input_dev) { + input_unregister_device(data->input_dev); + data->input_dev = NULL; + } +} + +static void mxt_free_object_table(struct mxt_data *data) +{ +#ifdef CONFIG_TOUCHSCREEN_ATMEL_MXT_T37 + video_unregister_device(&data->dbg.vdev); + v4l2_device_unregister(&data->dbg.v4l2); +#endif + data->object_table = NULL; + data->info = NULL; + kfree(data->raw_info_block); + data->raw_info_block = NULL; + kfree(data->msg_buf); + data->msg_buf = NULL; + data->T5_address = 0; + data->T5_msg_size = 0; + data->T6_reportid = 0; + data->T7_address = 0; + data->T71_address = 0; + data->T9_reportid_min = 0; + data->T9_reportid_max = 0; + data->T18_address = 0; + data->T19_reportid = 0; + data->T44_address = 0; + data->T100_reportid_min = 0; + data->T100_reportid_max = 0; + data->max_reportid = 0; +} + +static int mxt_parse_object_table(struct mxt_data *data, + struct mxt_object *object_table) +{ + struct i2c_client *client = data->client; + int i; + u8 reportid; + u16 end_address; + + /* Valid Report IDs start counting from 1 */ + reportid = 1; + data->mem_size = 0; + for (i = 0; i < data->info->object_num; i++) { + struct mxt_object *object = object_table + i; + u8 min_id, max_id; + + le16_to_cpus(&object->start_address); + + if (object->num_report_ids) { + min_id = reportid; + reportid += object->num_report_ids * + mxt_obj_instances(object); + max_id = reportid - 1; + } else { + min_id = 0; + max_id = 0; + } + + dev_dbg(&data->client->dev, + "T%u Start:%u Size:%zu Instances:%zu Report IDs:%u-%u\n", + object->type, object->start_address, + mxt_obj_size(object), mxt_obj_instances(object), + min_id, max_id); + + switch (object->type) { + case MXT_GEN_MESSAGE_T5: + if (data->info->family_id == 0x80 && + data->info->version < 0x20) { + /* + * On mXT224 firmware versions prior to V2.0 + * read and discard unused CRC byte otherwise + * DMA reads are misaligned. + */ + data->T5_msg_size = mxt_obj_size(object); + } else { + /* CRC not enabled, so skip last byte */ + data->T5_msg_size = mxt_obj_size(object) - 1; + } + data->T5_address = object->start_address; + break; + case MXT_GEN_COMMAND_T6: + data->T6_reportid = min_id; + data->T6_address = object->start_address; + break; + case MXT_GEN_POWER_T7: + data->T7_address = object->start_address; + break; + case MXT_SPT_DYNAMICCONFIGURATIONCONTAINER_T71: + data->T71_address = object->start_address; + break; + case MXT_TOUCH_MULTI_T9: + data->multitouch = MXT_TOUCH_MULTI_T9; + /* Only handle messages from first T9 instance */ + data->T9_reportid_min = min_id; + data->T9_reportid_max = min_id + + object->num_report_ids - 1; + data->num_touchids = object->num_report_ids; + break; + case MXT_SPT_COMMSCONFIG_T18: + data->T18_address = object->start_address; + break; + case MXT_SPT_MESSAGECOUNT_T44: + data->T44_address = object->start_address; + break; + case MXT_SPT_GPIOPWM_T19: + data->T19_reportid = min_id; + break; + case MXT_TOUCH_MULTITOUCHSCREEN_T100: + data->multitouch = MXT_TOUCH_MULTITOUCHSCREEN_T100; + data->T100_reportid_min = min_id; + data->T100_reportid_max = max_id; + /* first two report IDs reserved */ + data->num_touchids = object->num_report_ids - 2; + break; + } + + end_address = object->start_address + + mxt_obj_size(object) * mxt_obj_instances(object) - 1; + + if (end_address >= data->mem_size) + data->mem_size = end_address + 1; + } + + /* Store maximum reportid */ + data->max_reportid = reportid; + + /* If T44 exists, T5 position has to be directly after */ + if (data->T44_address && (data->T5_address != data->T44_address + 1)) { + dev_err(&client->dev, "Invalid T44 position\n"); + return -EINVAL; + } + + data->msg_buf = kcalloc(data->max_reportid, + data->T5_msg_size, GFP_KERNEL); + if (!data->msg_buf) + return -ENOMEM; + + return 0; +} + +static int mxt_read_info_block(struct mxt_data *data) +{ + struct i2c_client *client = data->client; + int error; + size_t size; + void *id_buf, *buf; + uint8_t num_objects; + u32 calculated_crc; + u8 *crc_ptr; + + /* If info block already allocated, free it */ + if (data->raw_info_block) + mxt_free_object_table(data); + + /* Read 7-byte ID information block starting at address 0 */ + size = sizeof(struct mxt_info); + id_buf = kzalloc(size, GFP_KERNEL); + if (!id_buf) + return -ENOMEM; + + error = __mxt_read_reg(data, 0, size, id_buf); + if (error) + goto err_free_mem; + + /* Resize buffer to give space for rest of info block */ + num_objects = ((struct mxt_info *)id_buf)->object_num; + size += (num_objects * sizeof(struct mxt_object)) + + MXT_INFO_CHECKSUM_SIZE; + + buf = krealloc(id_buf, size, GFP_KERNEL); + if (!buf) { + error = -ENOMEM; + goto err_free_mem; + } + id_buf = buf; + + /* Read rest of info block */ + error = __mxt_read_reg(data, MXT_OBJECT_START, + size - MXT_OBJECT_START, + id_buf + MXT_OBJECT_START); + if (error) + goto err_free_mem; + + /* Extract & calculate checksum */ + crc_ptr = id_buf + size - MXT_INFO_CHECKSUM_SIZE; + data->info_crc = crc_ptr[0] | (crc_ptr[1] << 8) | (crc_ptr[2] << 16); + + calculated_crc = mxt_calculate_crc(id_buf, 0, + size - MXT_INFO_CHECKSUM_SIZE); + + /* + * CRC mismatch can be caused by data corruption due to I2C comms + * issue or else device is not using Object Based Protocol (eg i2c-hid) + */ + if ((data->info_crc == 0) || (data->info_crc != calculated_crc)) { + dev_err(&client->dev, + "Info Block CRC error calculated=0x%06X read=0x%06X\n", + calculated_crc, data->info_crc); + error = -EIO; + goto err_free_mem; + } + + data->raw_info_block = id_buf; + data->info = (struct mxt_info *)id_buf; + + dev_info(&client->dev, + "Family: %u Variant: %u Firmware V%u.%u.%02X Objects: %u\n", + data->info->family_id, data->info->variant_id, + data->info->version >> 4, data->info->version & 0xf, + data->info->build, data->info->object_num); + + /* Parse object table information */ + error = mxt_parse_object_table(data, id_buf + MXT_OBJECT_START); + if (error) { + dev_err(&client->dev, "Error %d parsing object table\n", error); + mxt_free_object_table(data); + goto err_free_mem; + } + + data->object_table = (struct mxt_object *)(id_buf + MXT_OBJECT_START); + + return 0; + +err_free_mem: + kfree(id_buf); + return error; +} + +static int mxt_pinctrl_init(struct mxt_data *data) +{ + int error; + + /* Get pinctrl if target uses pinctrl */ + data->ts_pinctrl = devm_pinctrl_get((&data->client->dev)); + if (IS_ERR_OR_NULL(data->ts_pinctrl)) { + dev_dbg(&data->client->dev, + "Device does not use pinctrl\n"); + error = PTR_ERR(data->ts_pinctrl); + data->ts_pinctrl = NULL; + return error; + } + + data->gpio_state_active + = pinctrl_lookup_state(data->ts_pinctrl, "pmx_ts_active"); + if (IS_ERR_OR_NULL(data->gpio_state_active)) { + dev_dbg(&data->client->dev, + "Can not get ts default pinstate\n"); + error = PTR_ERR(data->gpio_state_active); + data->ts_pinctrl = NULL; + return error; + } + + data->gpio_state_suspend + = pinctrl_lookup_state(data->ts_pinctrl, "pmx_ts_suspend"); + if (IS_ERR_OR_NULL(data->gpio_state_suspend)) { + dev_dbg(&data->client->dev, + "Can not get ts sleep pinstate\n"); + error = PTR_ERR(data->gpio_state_suspend); + data->ts_pinctrl = NULL; + return error; + } + + return 0; +} + +static int mxt_pinctrl_select(struct mxt_data *data, bool on) +{ + struct pinctrl_state *pins_state; + int error; + + pins_state = on ? data->gpio_state_active + : data->gpio_state_suspend; + if (!IS_ERR_OR_NULL(pins_state)) { + error = pinctrl_select_state(data->ts_pinctrl, pins_state); + if (error) { + dev_err(&data->client->dev, + "can not set %s pins\n", + on ? "pmx_ts_active" : "pmx_ts_suspend"); + return error; + } + } else { + dev_err(&data->client->dev, + "not a valid '%s' pinstate\n", + on ? "pmx_ts_active" : "pmx_ts_suspend"); + } + + return 0; +} + +static int mxt_gpio_enable(struct mxt_data *data, bool enable) +{ + int error; + + if (data->ts_pinctrl) { + error = mxt_pinctrl_select(data, enable); + if (error < 0) + return error; + } + + if (enable) { + error = gpiod_direction_input(data->irq_gpio); + if (error) + dev_err(&data->client->dev, + "unable to set dir for %d gpio(%d)\n", + desc_to_gpio(data->irq_gpio), error); + if (data->reset_gpio) + gpiod_set_value(data->reset_gpio, 1); + } else { + if (data->reset_gpio) + gpiod_set_value(data->reset_gpio, 0); + } + + return 0; +} + +static int mxt_regulator_enable(struct mxt_data *data) +{ + int error; + + if (!data->use_regulator) + return 0; + + gpiod_set_value(data->reset_gpio, 0); + + error = mxt_regulator_configure(data, true); + if (error) { + dev_err(&data->client->dev, "Failed to configure regulators on\n"); + return error; + } + + error = regulator_enable(data->reg_avdd); + if (error) { + dev_err(&data->client->dev, + "avdd enable failed, error=%d\n", error); + goto err_dis_configure; + } + usleep_range(10000, 15000); + + error = regulator_enable(data->reg_vdd); + if (error) { + dev_err(&data->client->dev, + "vdd enable failed, error=%d\n", error); + goto err_dis_avdd; + } + + if (!IS_ERR(data->reg_xvdd)) { + usleep_range(10000, 15000); + error = regulator_enable(data->reg_xvdd); + if (error) { + dev_err(&data->client->dev, + "xvdd enable failed, error=%d\n", error); + goto err_dis_vdd; + } + } + msleep(MXT_REGULATOR_DELAY); + + reinit_completion(&data->bl_completion); + gpiod_set_value(data->reset_gpio, 1); + mxt_wait_for_completion(data, &data->bl_completion, MXT_POWERON_DELAY); + + return 0; + +err_dis_vdd: + regulator_disable(data->reg_vdd); +err_dis_avdd: + regulator_disable(data->reg_avdd); +err_dis_configure: + mxt_regulator_configure(data, false); + return error; +} + +static void mxt_regulator_disable(struct mxt_data *data) +{ + int error; + + regulator_disable(data->reg_vdd); + regulator_disable(data->reg_avdd); + if (!IS_ERR(data->reg_xvdd)) + regulator_disable(data->reg_xvdd); + + error = mxt_regulator_configure(data, false); + if (error) + dev_err(&data->client->dev, "Failed to configure regulators off\n"); + +} + +static int mxt_regulator_parse(struct mxt_data *data) +{ + struct device *dev = &data->client->dev; + struct device_node *np = dev->of_node; + struct property *prop; + int error = 0; + + if (!data->reset_gpio) { + dev_warn(dev, "Must have reset GPIO to use regulator support\n"); + return 0; + } + + data->reg_vdd = regulator_get(dev, "vdd"); + if (IS_ERR(data->reg_vdd)) { + error = PTR_ERR(data->reg_vdd); + dev_err(dev, "Error %d getting vdd regulator\n", error); + return error; + } + + data->reg_avdd = regulator_get(dev, "avdd"); + if (IS_ERR(data->reg_avdd)) { + error = PTR_ERR(data->reg_avdd); + dev_err(dev, "Error %d getting avdd regulator\n", error); + goto fail_put_vdd; + } + + data->reg_xvdd = regulator_get(dev, "xvdd"); + if (IS_ERR(data->reg_xvdd)) { + error = PTR_ERR(data->reg_xvdd); + prop = of_find_property(np, "xvdd-supply", NULL); + if (prop && (error == -EPROBE_DEFER)) + return -EPROBE_DEFER; + dev_dbg(dev, "xvdd regulator is not used\n"); + } + + data->use_regulator = true; + + dev_err(dev, "Initialised regulators\n"); + return 0; + +fail_put_vdd: + regulator_put(data->reg_vdd); + return error; +} + +static int mxt_regulator_configure(struct mxt_data *data, bool enable) +{ + struct device *dev = &data->client->dev; + int error = 0; + + /* According to maXTouch power sequencing specification, RESET line + * must be kept low until some time after regulators come up to + * voltage + */ + if (!data->reset_gpio) { + dev_warn(dev, "Must have reset GPIO to use regulator support\n"); + return 0; + } + + if (!enable) { + if (regulator_count_voltages(data->reg_avdd) > 0) { + error = regulator_set_load(data->reg_avdd, MXT_DISABLE_LOAD); + if (error) { + dev_err(&data->client->dev, + "avdd set_load failed err=%d\n", error); + return error; + } + } + + if (regulator_count_voltages(data->reg_vdd) > 0) { + error = regulator_set_load(data->reg_vdd, MXT_DISABLE_LOAD); + if (error) { + dev_err(&data->client->dev, + "vdd set_load failed err=%d\n", error); + return error; + } + } + + if (!IS_ERR(data->reg_xvdd)) { + if (regulator_count_voltages(data->reg_xvdd) > 0) { + error = regulator_set_load(data->reg_xvdd, MXT_DISABLE_LOAD); + if (error) + dev_err(&data->client->dev, + "xvdd set_load failed err=%d\n", error); + } + } + } else { + if (regulator_count_voltages(data->reg_vdd) > 0) { + error = regulator_set_load(data->reg_vdd, MXT_ENABLE_LOAD); + if (error) { + dev_err(&data->client->dev, + "vdd set_load failed err=%d\n", error); + return error; + } + error = regulator_set_voltage(data->reg_vdd, + MXT_VDD_VTG_MIN_UV, MXT_VDD_VTG_MAX_UV); + if (error) { + dev_err(&data->client->dev, + "vdd set_vtg failed err=%d\n", error); + goto err_vdd_load; + } + } + + if (regulator_count_voltages(data->reg_avdd) > 0) { + error = regulator_set_load(data->reg_avdd, MXT_ENABLE_LOAD); + if (error) { + dev_err(&data->client->dev, + "avdd set_load failed err=%d\n", error); + goto err_vdd_load; + } + error = regulator_set_voltage(data->reg_avdd, + MXT_AVDD_VTG_MIN_UV, MXT_AVDD_VTG_MAX_UV); + if (error) { + dev_err(&data->client->dev, + "avdd set_vtg failed err=%d\n", error); + goto err_avdd_load; + } + } + + if (!IS_ERR(data->reg_xvdd)) { + if (regulator_count_voltages(data->reg_xvdd) > 0) { + error = regulator_set_load(data->reg_xvdd, MXT_ENABLE_LOAD); + if (error) { + dev_err(&data->client->dev, + "xvdd set_load failed err=%d\n", error); + goto err_avdd_load; + } + error = regulator_set_voltage(data->reg_xvdd, + MXT_XVDD_VTG_MIN_UV, MXT_XVDD_VTG_MAX_UV); + if (error) { + dev_err(&data->client->dev, + "xvdd set_vtg failed err=%d\n", error); + goto err_xvdd_load; + } + } + } + } + return error; + +err_xvdd_load: + regulator_set_load(data->reg_xvdd, MXT_DISABLE_LOAD); +err_avdd_load: + regulator_set_load(data->reg_avdd, MXT_DISABLE_LOAD); +err_vdd_load: + regulator_set_load(data->reg_vdd, MXT_DISABLE_LOAD); + return error; +} + +static int mxt_read_t9_resolution(struct mxt_data *data) +{ + int error; + unsigned char orient; + struct mxt_object *object; + + object = mxt_get_object(data, MXT_TOUCH_MULTI_T9); + if (!object) + return -EINVAL; + + error = __mxt_read_reg(data, + object->start_address + MXT_T9_XSIZE, + sizeof(data->xsize), &data->xsize); + if (error) + return error; + + error = __mxt_read_reg(data, + object->start_address + MXT_T9_YSIZE, + sizeof(data->ysize), &data->ysize); + if (error) + return error; + + error = __mxt_read_reg(data, + object->start_address + MXT_T9_RANGE, + sizeof(struct t9_range), data->read_buf); + if (error) + return error; + + data->max_x = get_unaligned_le16(data->read_buf); + data->max_y = get_unaligned_le16(data->read_buf + sizeof(__le16)); + + error = __mxt_read_reg(data, + object->start_address + MXT_T9_ORIENT, + 1, data->read_buf); + if (error) + return error; + orient = *(data->read_buf); + + if (data->xy_switch != (orient & MXT_T9_ORIENT_SWITCH)) + dev_warn(&data->client->dev, + "T9 data->xy_switch:%d\n", + data->xy_switch); + + if (data->invertx != (orient & MXT_T9_ORIENT_INVERTX)) + dev_warn(&data->client->dev, + "T9 data->data->invertx:%d\n", + data->invertx); + + if (data->inverty != (orient & MXT_T9_ORIENT_INVERTY)) + dev_warn(&data->client->dev, + "T9 data->data->inverty:%d\n", + data->inverty); + + return 0; +} + +static int mxt_read_t100_config(struct mxt_data *data) +{ + int error; + struct mxt_object *object; + u8 cfg, tchaux; + u8 aux; + + object = mxt_get_object(data, MXT_TOUCH_MULTITOUCHSCREEN_T100); + if (!object) + return -EINVAL; + + /* read touchscreen dimensions */ + error = __mxt_read_reg(data, + object->start_address + MXT_T100_XRANGE, + sizeof(__le16), data->read_buf); + if (error) + return error; + + data->max_x = get_unaligned_le16(data->read_buf); + + error = __mxt_read_reg(data, + object->start_address + MXT_T100_YRANGE, + sizeof(__le16), data->read_buf); + + if (error) + return error; + + data->max_y = get_unaligned_le16(data->read_buf); + + error = __mxt_read_reg(data, + object->start_address + MXT_T100_XSIZE, + sizeof(data->xsize), &data->xsize); + if (error) + return error; + + error = __mxt_read_reg(data, + object->start_address + MXT_T100_YSIZE, + sizeof(data->ysize), &data->ysize); + if (error) + return error; + + /* read orientation config */ + error = __mxt_read_reg(data, + object->start_address + MXT_T100_CFG1, + 1, data->read_buf); + if (error) + return error; + cfg = *(data->read_buf); + + if (data->xy_switch != (cfg & MXT_T100_CFG_SWITCHXY)) + dev_warn(&data->client->dev, + "T100 data->xy_switch:%d\n", + data->xy_switch); + + if (data->invertx != (cfg & MXT_T100_CFG_INVERTX)) + dev_warn(&data->client->dev, + "T100 data->data->invertx:%d\n", + data->invertx); + + if (data->inverty != (cfg & MXT_T100_CFG_INVERTY)) + dev_warn(&data->client->dev, + "T100 data->data->inverty:%d\n", + data->inverty); + + /* allocate aux bytes */ + error = __mxt_read_reg(data, + object->start_address + MXT_T100_TCHAUX, + 1, data->read_buf); + if (error) + return error; + tchaux = *(data->read_buf); + + aux = 6; + + if (tchaux & MXT_T100_TCHAUX_VECT) + data->t100_aux_vect = aux++; + + if (tchaux & MXT_T100_TCHAUX_AMPL) + data->t100_aux_ampl = aux++; + + if (tchaux & MXT_T100_TCHAUX_AREA) + data->t100_aux_area = aux++; + + dev_dbg(&data->client->dev, + "T100 aux mappings vect:%u ampl:%u area:%u\n", + data->t100_aux_vect, data->t100_aux_ampl, data->t100_aux_area); + + return 0; +} + +static int mxt_input_open(struct input_dev *dev); +static void mxt_input_close(struct input_dev *dev); + +static void mxt_set_up_as_touchpad(struct input_dev *input_dev, + struct mxt_data *data) +{ + int i; + + input_dev->name = "Atmel maXTouch Touchpad"; + + __set_bit(INPUT_PROP_BUTTONPAD, input_dev->propbit); + + input_abs_set_res(input_dev, ABS_X, MXT_PIXELS_PER_MM); + input_abs_set_res(input_dev, ABS_Y, MXT_PIXELS_PER_MM); + input_abs_set_res(input_dev, ABS_MT_POSITION_X, + MXT_PIXELS_PER_MM); + input_abs_set_res(input_dev, ABS_MT_POSITION_Y, + MXT_PIXELS_PER_MM); + + for (i = 0; i < data->t19_num_keys; i++) + if (data->t19_keymap[i] != KEY_RESERVED) + input_set_capability(input_dev, EV_KEY, + data->t19_keymap[i]); +} + +static int mxt_initialize_input_device(struct mxt_data *data) +{ + struct device *dev = &data->client->dev; + struct input_dev *input_dev; + int error; + unsigned int num_mt_slots; + unsigned int mt_flags = 0; + + switch (data->multitouch) { + case MXT_TOUCH_MULTI_T9: + num_mt_slots = data->T9_reportid_max - data->T9_reportid_min + 1; + error = mxt_read_t9_resolution(data); + if (error) + dev_warn(dev, "Failed to initialize T9 resolution\n"); + break; + + case MXT_TOUCH_MULTITOUCHSCREEN_T100: + num_mt_slots = data->num_touchids; + error = mxt_read_t100_config(data); + if (error) + dev_warn(dev, "Failed to read T100 config\n"); + break; + + default: + dev_err(dev, "Invalid multitouch object\n"); + return -EINVAL; + } + + /* Handle default values and orientation switch */ + if (data->max_x == 0) + data->max_x = 1023; + + if (data->max_y == 0) + data->max_y = 1023; + + if (data->xy_switch) + swap(data->max_x, data->max_y); + + dev_info(dev, "Touchscreen size X%uY%u\n", data->max_x, data->max_y); + + /* Register input device */ + input_dev = input_allocate_device(); + if (!input_dev) + return -ENOMEM; + + input_dev->name = "Atmel maXTouch Touchscreen"; + input_dev->phys = data->phys; + input_dev->id.bustype = BUS_I2C; + input_dev->dev.parent = dev; + input_dev->open = mxt_input_open; + input_dev->close = mxt_input_close; + + input_set_capability(input_dev, EV_KEY, BTN_TOUCH); + + if (data->disp_maxx == 0 || data->disp_maxx > data->max_x) + data->disp_maxx = data->max_x; + if (data->disp_maxy == 0 || data->disp_maxy > data->max_y) + data->disp_maxy = data->max_y; + + if (data->multitouch == MXT_TOUCH_MULTI_T9 || + (data->multitouch == MXT_TOUCH_MULTITOUCHSCREEN_T100 && + data->t100_aux_ampl)) { + input_set_abs_params(input_dev, ABS_PRESSURE, 0, 255, 0, 0); + } + + /* If device has buttons we assume it is a touchpad */ + if (data->t19_num_keys) { + mxt_set_up_as_touchpad(input_dev, data); + mt_flags |= INPUT_MT_POINTER; + } else { + mt_flags |= INPUT_MT_DIRECT; + } + + /* For multi touch */ + error = input_mt_init_slots(input_dev, num_mt_slots, mt_flags); + if (error) { + dev_err(dev, "Error %d initialising slots\n", error); + goto err_free_mem; + } + + if (data->multitouch == MXT_TOUCH_MULTITOUCHSCREEN_T100) { + input_set_abs_params(input_dev, ABS_MT_TOOL_TYPE, + 0, MT_TOOL_MAX, 0, 0); + input_set_abs_params(input_dev, ABS_MT_DISTANCE, + MXT_DISTANCE_ACTIVE_TOUCH, + MXT_DISTANCE_HOVERING, + 0, 0); + } + + input_set_abs_params(input_dev, ABS_MT_POSITION_X, + 0, data->disp_maxx - data->disp_minx, 0, 0); + input_set_abs_params(input_dev, ABS_MT_POSITION_Y, + 0, data->disp_maxy - data->disp_miny, 0, 0); + + if (data->multitouch == MXT_TOUCH_MULTI_T9 || + (data->multitouch == MXT_TOUCH_MULTITOUCHSCREEN_T100 && + data->t100_aux_area)) { + input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, + 0, MXT_MAX_AREA, 0, 0); + } + + if (data->multitouch == MXT_TOUCH_MULTI_T9 || + (data->multitouch == MXT_TOUCH_MULTITOUCHSCREEN_T100 && + data->t100_aux_ampl)) { + input_set_abs_params(input_dev, ABS_MT_PRESSURE, + 0, 255, 0, 0); + } + + if (data->multitouch == MXT_TOUCH_MULTITOUCHSCREEN_T100 && + data->t100_aux_vect) { + input_set_abs_params(input_dev, ABS_MT_ORIENTATION, + 0, 255, 0, 0); + } + + if (data->multitouch == MXT_TOUCH_MULTITOUCHSCREEN_T100 && + data->t100_aux_vect) { + input_set_abs_params(input_dev, ABS_MT_ORIENTATION, + 0, 255, 0, 0); + } + + input_set_drvdata(input_dev, data); + + error = input_register_device(input_dev); + if (error) { + dev_err(dev, "Error %d registering input device\n", error); + goto err_free_mem; + } + + data->input_dev = input_dev; + + return 0; + +err_free_mem: + input_free_device(input_dev); + return error; +} + +static int mxt_configure_objects(struct mxt_data *data, + const struct firmware *cfg); + +static void mxt_config_cb(const struct firmware *cfg, void *ctx) +{ + mxt_configure_objects(ctx, cfg); + release_firmware(cfg); +} + +static int mxt_initialize(struct mxt_data *data) +{ + struct i2c_client *client = data->client; + int recovery_attempts = 0; + int error; + + while (1) { + error = mxt_read_info_block(data); + if (!error) + break; + + /* Check bootloader state */ + error = mxt_probe_bootloader(data, false); + if (error) { + dev_info(&client->dev, "Trying alternate bootloader address\n"); + mxt_soft_reset(data); + error = mxt_probe_bootloader(data, true); + if (error) { + /* Chip is not in appmode or bootloader mode */ + dev_err(&client->dev, "Fail probe boot, continue\n"); + } + } + + /* OK, we are in bootloader, see if we can recover */ + if (++recovery_attempts > 10) { + dev_err(&client->dev, "Could not recover from bootloader mode\n"); + /* + * We can reflash from this state, so do not + * abort initialization. + */ + data->in_bootloader = true; + return 0; + } + + /* Attempt to exit bootloader into app mode */ + mxt_send_bootloader_cmd(data, false); + msleep(MXT_FW_RESET_TIME); + } + + error = mxt_check_retrigen(data); + if (error) + return error; + + error = mxt_acquire_irq(data); + if (error) + return error; + + error = request_firmware_nowait(THIS_MODULE, true, MXT_CFG_NAME, + &client->dev, GFP_KERNEL, data, + mxt_config_cb); + if (error) { + dev_err(&client->dev, "Failed to invoke firmware loader: %d\n", + error); + return error; + } + + return 0; +} + +static int mxt_set_t7_power_cfg(struct mxt_data *data, u8 sleep) +{ + struct device *dev = &data->client->dev; + int error; + struct t7_config *new_config; + struct t7_config deepsleep = { .active = 0, .idle = 0 }; + + if (sleep == MXT_POWER_CFG_DEEPSLEEP) + new_config = &deepsleep; + else + new_config = &data->t7_cfg; + + error = __mxt_write_reg(data->client, data->T7_address, + sizeof(data->t7_cfg), new_config); + if (error) + return error; + + dev_dbg(dev, "Set T7 ACTV:%d IDLE:%d\n", + new_config->active, new_config->idle); + + return 0; +} + +static int mxt_init_t7_power_cfg(struct mxt_data *data) +{ + struct device *dev = &data->client->dev; + int error; + bool retry = false; + +recheck: + error = __mxt_read_reg(data, data->T7_address, + sizeof(data->t7_cfg), &data->t7_cfg); + if (error) + return error; + + if (data->t7_cfg.active == 0 || data->t7_cfg.idle == 0) { + if (!retry) { + dev_dbg(dev, "T7 cfg zero, resetting\n"); + mxt_soft_reset(data); + retry = true; + goto recheck; + } else { + dev_dbg(dev, "T7 cfg zero after reset, overriding\n"); + data->t7_cfg.active = 20; + data->t7_cfg.idle = 100; + return mxt_set_t7_power_cfg(data, MXT_POWER_CFG_RUN); + } + } + + dev_dbg(dev, "Initialized power cfg: ACTV %d, IDLE %d\n", + data->t7_cfg.active, data->t7_cfg.idle); + return 0; +} + +#ifdef CONFIG_TOUCHSCREEN_ATMEL_MXT_T37 +static const struct v4l2_file_operations mxt_video_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .unlocked_ioctl = video_ioctl2, + .read = vb2_fop_read, + .mmap = vb2_fop_mmap, + .poll = vb2_fop_poll, +}; + +static u16 mxt_get_debug_value(struct mxt_data *data, unsigned int x, + unsigned int y) +{ + struct mxt_info *info = data->info; + struct mxt_dbg *dbg = &data->dbg; + unsigned int ofs, page; + unsigned int col = 0; + unsigned int col_width; + + if (info->family_id == MXT_FAMILY_1386) { + col_width = info->matrix_ysize / MXT1386_COLUMNS; + col = y / col_width; + y = y % col_width; + } else { + col_width = info->matrix_ysize; + } + + ofs = (y + (x * col_width)) * sizeof(u16); + page = ofs / MXT_DIAGNOSTIC_SIZE; + ofs %= MXT_DIAGNOSTIC_SIZE; + + if (info->family_id == MXT_FAMILY_1386) + page += col * MXT1386_PAGES_PER_COLUMN; + + return get_unaligned_le16(&dbg->t37_buf[page].data[ofs]); +} + +static int mxt_convert_debug_pages(struct mxt_data *data, u16 *outbuf) +{ + struct mxt_dbg *dbg = &data->dbg; + unsigned int x = 0; + unsigned int y = 0; + unsigned int i, rx, ry; + + for (i = 0; i < dbg->t37_nodes; i++) { + /* Handle orientation */ + rx = data->xy_switch ? y : x; + ry = data->xy_switch ? x : y; + rx = data->invertx ? (data->xsize - 1 - rx) : rx; + ry = data->inverty ? (data->ysize - 1 - ry) : ry; + + outbuf[i] = mxt_get_debug_value(data, rx, ry); + + /* Next value */ + if (++x >= (data->xy_switch ? data->ysize : data->xsize)) { + x = 0; + y++; + } + } + + return 0; +} + +static int mxt_read_diagnostic_debug(struct mxt_data *data, u8 mode, + u16 *outbuf) +{ + struct mxt_dbg *dbg = &data->dbg; + int retries = 0; + int page; + int ret; + u8 cmd = mode; + struct t37_debug *p; + u8 cmd_poll; + + for (page = 0; page < dbg->t37_pages; page++) { + p = dbg->t37_buf + page; + + ret = mxt_write_reg(data->client, dbg->diag_cmd_address, + cmd); + if (ret) + return ret; + + retries = 0; + msleep(20); +wait_cmd: + /* Read back command byte */ + ret = __mxt_read_reg(data, dbg->diag_cmd_address, + sizeof(cmd_poll), data->read_buf); + if (ret) + return ret; + + cmd_poll = *(data->read_buf); + /* Field is cleared once the command has been processed */ + if (cmd_poll) { + if (retries++ > 100) + return -EINVAL; + + msleep(20); + goto wait_cmd; + } + + /* Read T37 page */ + ret = __mxt_read_reg(data, dbg->t37_address, + sizeof(struct t37_debug), p); + if (ret) + return ret; + + if (p->mode != mode || p->page != page) { + dev_err(&data->client->dev, "T37 page mismatch\n"); + return -EINVAL; + } + + dev_dbg(&data->client->dev, "%s page:%d retries:%d\n", + __func__, page, retries); + + /* For remaining pages, write PAGEUP rather than mode */ + cmd = MXT_DIAGNOSTIC_PAGEUP; + } + + return mxt_convert_debug_pages(data, outbuf); +} + +static int mxt_queue_setup(struct vb2_queue *q, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct mxt_data *data = q->drv_priv; + size_t size = data->dbg.t37_nodes * sizeof(u16); + + if (*nplanes) + return sizes[0] < size ? -EINVAL : 0; + + *nplanes = 1; + sizes[0] = size; + + return 0; +} + +static void mxt_buffer_queue(struct vb2_buffer *vb) +{ + struct mxt_data *data = vb2_get_drv_priv(vb->vb2_queue); + u16 *ptr; + int ret; + u8 mode; + + ptr = vb2_plane_vaddr(vb, 0); + if (!ptr) { + dev_err(&data->client->dev, "Error acquiring frame ptr\n"); + goto fault; + } + + switch (data->dbg.input) { + case MXT_V4L_INPUT_DELTAS: + default: + mode = MXT_DIAGNOSTIC_DELTAS; + break; + + case MXT_V4L_INPUT_REFS: + mode = MXT_DIAGNOSTIC_REFS; + break; + } + + ret = mxt_read_diagnostic_debug(data, mode, ptr); + if (ret) + goto fault; + + vb2_set_plane_payload(vb, 0, data->dbg.t37_nodes * sizeof(u16)); + vb2_buffer_done(vb, VB2_BUF_STATE_DONE); + return; + +fault: + vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); +} + +/* V4L2 structures */ +static const struct vb2_ops mxt_queue_ops = { + .queue_setup = mxt_queue_setup, + .buf_queue = mxt_buffer_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static const struct vb2_queue mxt_queue = { + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ, + .buf_struct_size = sizeof(struct mxt_vb2_buffer), + .ops = &mxt_queue_ops, + .mem_ops = &vb2_vmalloc_memops, + .timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC, + .min_buffers_needed = 1, +}; + +static int mxt_vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct mxt_data *data = video_drvdata(file); + + strlcpy(cap->driver, "atmel_mxt_ts", sizeof(cap->driver)); + strlcpy(cap->card, "atmel_mxt_ts touch", sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), + "I2C:%s", dev_name(&data->client->dev)); + return 0; +} + +static int mxt_vidioc_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + if (i->index >= MXT_V4L_INPUT_MAX) + return -EINVAL; + + i->type = V4L2_INPUT_TYPE_TOUCH; + + switch (i->index) { + case MXT_V4L_INPUT_REFS: + strlcpy(i->name, "Mutual Capacitance References", + sizeof(i->name)); + break; + case MXT_V4L_INPUT_DELTAS: + strlcpy(i->name, "Mutual Capacitance Deltas", sizeof(i->name)); + break; + } + + return 0; +} + +static int mxt_set_input(struct mxt_data *data, unsigned int i) +{ + struct v4l2_pix_format *f = &data->dbg.format; + + if (i >= MXT_V4L_INPUT_MAX) + return -EINVAL; + + if (i == MXT_V4L_INPUT_DELTAS) + f->pixelformat = V4L2_TCH_FMT_DELTA_TD16; + else + f->pixelformat = V4L2_TCH_FMT_TU16; + + f->width = data->xy_switch ? data->ysize : data->xsize; + f->height = data->xy_switch ? data->xsize : data->ysize; + f->field = V4L2_FIELD_NONE; + f->colorspace = V4L2_COLORSPACE_RAW; + f->bytesperline = f->width * sizeof(u16); + f->sizeimage = f->width * f->height * sizeof(u16); + + data->dbg.input = i; + + return 0; +} + +static int mxt_vidioc_s_input(struct file *file, void *priv, unsigned int i) +{ + return mxt_set_input(video_drvdata(file), i); +} + +static int mxt_vidioc_g_input(struct file *file, void *priv, unsigned int *i) +{ + struct mxt_data *data = video_drvdata(file); + + *i = data->dbg.input; + + return 0; +} + +static int mxt_vidioc_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct mxt_data *data = video_drvdata(file); + + f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + f->fmt.pix = data->dbg.format; + + return 0; +} + +static int mxt_vidioc_enum_fmt(struct file *file, void *priv, + struct v4l2_fmtdesc *fmt) +{ + if (fmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + switch (fmt->index) { + case 0: + fmt->pixelformat = V4L2_TCH_FMT_TU16; + break; + + case 1: + fmt->pixelformat = V4L2_TCH_FMT_DELTA_TD16; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int mxt_vidioc_g_parm(struct file *file, void *fh, + struct v4l2_streamparm *a) +{ + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + a->parm.capture.readbuffers = 1; + a->parm.capture.timeperframe.numerator = 1; + a->parm.capture.timeperframe.denominator = 10; + return 0; +} + +static const struct v4l2_ioctl_ops mxt_video_ioctl_ops = { + .vidioc_querycap = mxt_vidioc_querycap, + + .vidioc_enum_fmt_vid_cap = mxt_vidioc_enum_fmt, + .vidioc_s_fmt_vid_cap = mxt_vidioc_fmt, + .vidioc_g_fmt_vid_cap = mxt_vidioc_fmt, + .vidioc_try_fmt_vid_cap = mxt_vidioc_fmt, + .vidioc_g_parm = mxt_vidioc_g_parm, + + .vidioc_enum_input = mxt_vidioc_enum_input, + .vidioc_g_input = mxt_vidioc_g_input, + .vidioc_s_input = mxt_vidioc_s_input, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, +}; + +static const struct video_device mxt_video_device = { + .name = "Atmel maxTouch", + .fops = &mxt_video_fops, + .ioctl_ops = &mxt_video_ioctl_ops, + .release = video_device_release_empty, + .device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_TOUCH | + V4L2_CAP_READWRITE | V4L2_CAP_STREAMING, +}; + +static void mxt_debug_init(struct mxt_data *data) +{ + struct mxt_info *info = data->info; + struct mxt_dbg *dbg = &data->dbg; + struct mxt_object *object; + int error; + + object = mxt_get_object(data, MXT_GEN_COMMAND_T6); + if (!object) + goto error; + + dbg->diag_cmd_address = object->start_address + MXT_COMMAND_DIAGNOSTIC; + + object = mxt_get_object(data, MXT_DEBUG_DIAGNOSTIC_T37); + if (!object) + goto error; + + if (mxt_obj_size(object) != sizeof(struct t37_debug)) { + dev_warn(&data->client->dev, "Bad T37 size"); + goto error; + } + + dbg->t37_address = object->start_address; + + /* Calculate size of data and allocate buffer */ + dbg->t37_nodes = data->xsize * data->ysize; + + if (info->family_id == MXT_FAMILY_1386) + dbg->t37_pages = MXT1386_COLUMNS * MXT1386_PAGES_PER_COLUMN; + else + dbg->t37_pages = DIV_ROUND_UP(data->xsize * + info->matrix_ysize * + sizeof(u16), + sizeof(dbg->t37_buf->data)); + + dbg->t37_buf = devm_kmalloc_array(&data->client->dev, dbg->t37_pages, + sizeof(struct t37_debug), GFP_KERNEL); + if (!dbg->t37_buf) + goto error; + + /* init channel to zero */ + mxt_set_input(data, 0); + + /* register video device */ + snprintf(dbg->v4l2.name, sizeof(dbg->v4l2.name), "%s", "atmel_mxt_ts"); + error = v4l2_device_register(&data->client->dev, &dbg->v4l2); + if (error) + goto error; + + /* initialize the queue */ + mutex_init(&dbg->lock); + dbg->queue = mxt_queue; + dbg->queue.drv_priv = data; + dbg->queue.lock = &dbg->lock; + dbg->queue.dev = &data->client->dev; + + error = vb2_queue_init(&dbg->queue); + if (error) + goto error_unreg_v4l2; + + dbg->vdev = mxt_video_device; + dbg->vdev.v4l2_dev = &dbg->v4l2; + dbg->vdev.lock = &dbg->lock; + dbg->vdev.vfl_dir = VFL_DIR_RX; + dbg->vdev.queue = &dbg->queue; + video_set_drvdata(&dbg->vdev, data); + + error = video_register_device(&dbg->vdev, VFL_TYPE_TOUCH, -1); + if (error) + goto error_unreg_v4l2; + + return; + +error_unreg_v4l2: + v4l2_device_unregister(&dbg->v4l2); +error: + dev_warn(&data->client->dev, "Error initializing T37\n"); +} +#else +static void mxt_debug_init(struct mxt_data *data) +{ +} +#endif + +static int mxt_configure_objects(struct mxt_data *data, + const struct firmware *cfg) +{ + struct device *dev = &data->client->dev; + int error; + + error = mxt_init_t7_power_cfg(data); + if (error) { + dev_err(dev, "Failed to initialize power cfg\n"); + return error; + } + + if (cfg) { + error = mxt_update_cfg(data, cfg); + if (error) + dev_warn(dev, "Error %d updating config\n", error); + } + + if (data->multitouch) { + error = mxt_initialize_input_device(data); + if (error) + return error; + } else { + dev_warn(dev, "No touch object detected\n"); + } + + mxt_debug_init(data); + + enable_irq(data->irq); + return 0; +} + +/* Firmware Version is returned as Major.Minor.Build */ +static ssize_t mxt_fw_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mxt_data *data = dev_get_drvdata(dev); + struct mxt_info *info = data->info; + return scnprintf(buf, PAGE_SIZE, "%u.%u.%02X\n", + info->version >> 4, info->version & 0xf, info->build); +} + +/* Hardware Version is returned as FamilyID.VariantID */ +static ssize_t mxt_hw_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mxt_data *data = dev_get_drvdata(dev); + struct mxt_info *info = data->info; + return scnprintf(buf, PAGE_SIZE, "%u.%u\n", + info->family_id, info->variant_id); +} + +static ssize_t mxt_show_instance(char *buf, int count, + struct mxt_object *object, int instance, + const u8 *val) +{ + int i; + + if (mxt_obj_instances(object) > 1) + count += scnprintf(buf + count, PAGE_SIZE - count, + "Instance %u\n", instance); + + for (i = 0; i < mxt_obj_size(object); i++) + count += scnprintf(buf + count, PAGE_SIZE - count, + "\t[%2u]: %02x (%d)\n", i, val[i], val[i]); + count += scnprintf(buf + count, PAGE_SIZE - count, "\n"); + + return count; +} + +static ssize_t mxt_object_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mxt_data *data = dev_get_drvdata(dev); + struct mxt_object *object; + int count = 0; + int i, j; + int error; + u8 *obuf; + + /* Pre-allocate buffer large enough to hold max sized object. */ + obuf = kmalloc(256, GFP_KERNEL); + if (!obuf) + return -ENOMEM; + + error = 0; + for (i = 0; i < data->info->object_num; i++) { + object = data->object_table + i; + + if (!mxt_object_readable(object->type)) + continue; + + count += scnprintf(buf + count, PAGE_SIZE - count, + "T%u:\n", object->type); + + for (j = 0; j < mxt_obj_instances(object); j++) { + u16 size = mxt_obj_size(object); + u16 addr = object->start_address + j * size; + + error = __mxt_read_reg(data, addr, size, obuf); + if (error) + goto done; + + count = mxt_show_instance(buf, count, object, j, obuf); + } + } + +done: + kfree(obuf); + return error ?: count; +} + +static int mxt_check_firmware_format(struct device *dev, + const struct firmware *fw) +{ + unsigned int pos = 0; + char c; + + while (pos < fw->size) { + c = *(fw->data + pos); + + if (c < '0' || (c > '9' && c < 'A') || c > 'F') + return 0; + + pos++; + } + + /* + * To convert file try: + * xxd -r -p mXTXXX__APP_VX-X-XX.enc > maxtouch.fw + */ + dev_err(dev, "Aborting: firmware file must be in binary format\n"); + + return -EINVAL; +} + +static int mxt_load_fw(struct device *dev, const char *fn) +{ + struct mxt_data *data = dev_get_drvdata(dev); + const struct firmware *fw = NULL; + unsigned int frame_size; + unsigned int pos = 0; + unsigned int retry = 0; + unsigned int frame = 0; + int ret; + + ret = request_firmware(&fw, fn, dev); + if (ret) { + dev_err(dev, "Unable to open firmware %s\n", fn); + return ret; + } + + /* Check for incorrect enc file */ + ret = mxt_check_firmware_format(dev, fw); + if (ret) + goto release_firmware; + + if (!data->in_bootloader) { + /* Change to the bootloader mode */ + data->in_bootloader = true; + + ret = mxt_t6_command(data, MXT_COMMAND_RESET, + MXT_BOOT_VALUE, false); + if (ret) + goto release_firmware; + + msleep(MXT_RESET_TIME); + + /* Do not need to scan since we know family ID */ + ret = mxt_lookup_bootloader_address(data, 0); + if (ret) + goto release_firmware; + + mxt_free_input_device(data); + mxt_free_object_table(data); + } else { + enable_irq(data->irq); + } + + reinit_completion(&data->bl_completion); + + ret = mxt_check_bootloader(data, MXT_WAITING_BOOTLOAD_CMD, false); + if (ret) { + /* Bootloader may still be unlocked from previous attempt */ + ret = mxt_check_bootloader(data, MXT_WAITING_FRAME_DATA, false); + if (ret) + goto disable_irq; + } else { + dev_info(dev, "Unlocking bootloader\n"); + + /* Unlock bootloader */ + ret = mxt_send_bootloader_cmd(data, true); + if (ret) + goto disable_irq; + } + + while (pos < fw->size) { + ret = mxt_check_bootloader(data, MXT_WAITING_FRAME_DATA, true); + if (ret) + goto disable_irq; + + frame_size = ((*(fw->data + pos) << 8) | *(fw->data + pos + 1)); + + /* Take account of CRC bytes */ + frame_size += 2; + + /* Write one frame to device */ + ret = mxt_bootloader_write(data, fw->data + pos, frame_size); + if (ret) + goto disable_irq; + + ret = mxt_check_bootloader(data, MXT_FRAME_CRC_PASS, true); + if (ret) { + retry++; + + /* Back off by 20ms per retry */ + msleep(retry * 20); + + if (retry > 20) { + dev_err(dev, "Retry count exceeded\n"); + goto disable_irq; + } + } else { + retry = 0; + pos += frame_size; + frame++; + } + + if (frame % 50 == 0) + dev_dbg(dev, "Sent %d frames, %d/%zd bytes\n", + frame, pos, fw->size); + } + + /* Wait for flash. */ + ret = mxt_wait_for_completion(data, &data->bl_completion, + MXT_FW_RESET_TIME); + if (ret) + goto disable_irq; + + dev_dbg(dev, "Sent %d frames, %d bytes\n", frame, pos); + + /* + * Wait for device to reset. Some bootloader versions do not assert + * the CHG line after bootloading has finished, so ignore potential + * errors. + */ + mxt_wait_for_completion(data, &data->bl_completion, MXT_FW_RESET_TIME); + + data->in_bootloader = false; + +disable_irq: + disable_irq(data->irq); +release_firmware: + release_firmware(fw); + return ret; +} + +static ssize_t mxt_update_fw_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mxt_data *data = dev_get_drvdata(dev); + int error; + + error = mxt_load_fw(dev, MXT_FW_NAME); + if (error) { + dev_err(dev, "The firmware update failed(%d)\n", error); + count = error; + } else { + dev_info(dev, "The firmware update succeeded\n"); + + error = mxt_initialize(data); + if (error) + return error; + } + + return count; +} + +static DEVICE_ATTR(fw_version, S_IRUGO, mxt_fw_version_show, NULL); +static DEVICE_ATTR(hw_version, S_IRUGO, mxt_hw_version_show, NULL); +static DEVICE_ATTR(object, S_IRUGO, mxt_object_show, NULL); +static DEVICE_ATTR(update_fw, S_IWUSR, NULL, mxt_update_fw_store); + +static struct attribute *mxt_attrs[] = { + &dev_attr_fw_version.attr, + &dev_attr_hw_version.attr, + &dev_attr_object.attr, + &dev_attr_update_fw.attr, + NULL +}; + +static const struct attribute_group mxt_attr_group = { + .attrs = mxt_attrs, +}; + +static void mxt_start(struct mxt_data *data) +{ + if (!data->suspended || data->in_bootloader) + return; + + /* enable gpios */ + mxt_gpio_enable(data, true); + + /* enable regulators */ + mxt_regulator_enable(data); + + switch (data->suspend_mode) { + case MXT_SUSPEND_T9_CTRL: + mxt_soft_reset(data); + + /* Touch enable */ + /* 0x83 = SCANEN | RPTEN | ENABLE */ + mxt_write_object(data, + MXT_TOUCH_MULTI_T9, MXT_T9_CTRL, 0x83); + break; + + case MXT_SUSPEND_DEEP_SLEEP: + default: + mxt_set_t7_power_cfg(data, MXT_POWER_CFG_RUN); + + /* Recalibrate since chip has been in deep sleep */ + mxt_t6_command(data, MXT_COMMAND_CALIBRATE, 1, false); + break; + } + + mxt_acquire_irq(data); + data->suspended = false; +} + +static void mxt_stop(struct mxt_data *data) +{ + if (data->suspended || data->in_bootloader) + return; + + disable_irq(data->irq); + + switch (data->suspend_mode) { + case MXT_SUSPEND_T9_CTRL: + /* Touch disable */ + mxt_write_object(data, + MXT_TOUCH_MULTI_T9, MXT_T9_CTRL, 0); + break; + + case MXT_SUSPEND_DEEP_SLEEP: + default: + mxt_set_t7_power_cfg(data, MXT_POWER_CFG_DEEPSLEEP); + break; + } + + /* disable regulators */ + mxt_regulator_disable(data); + /* disable gpios */ + mxt_gpio_enable(data, false); + + data->suspended = true; +} + +static int mxt_input_open(struct input_dev *dev) +{ +#if !defined(CONFIG_DRM) + struct mxt_data *data = input_get_drvdata(dev); + + mxt_start(data); +#endif + return 0; +} + +static void mxt_input_close(struct input_dev *dev) +{ +#if !defined(CONFIG_DRM) + struct mxt_data *data = input_get_drvdata(dev); + + mxt_stop(data); +#endif +} + +static int mxt_parse_device_properties(struct mxt_data *data) +{ + static const char keymap_property[] = "linux,gpio-keymap"; + struct device *dev = &data->client->dev; + u32 *keymap; + int n_keys; + int error; + + if (device_property_present(dev, keymap_property)) { + n_keys = device_property_read_u32_array(dev, keymap_property, + NULL, 0); + if (n_keys <= 0) { + error = n_keys < 0 ? n_keys : -EINVAL; + dev_err(dev, "invalid/malformed '%s' property: %d\n", + keymap_property, error); + return error; + } + + keymap = devm_kmalloc_array(dev, n_keys, sizeof(*keymap), + GFP_KERNEL); + if (!keymap) + return -ENOMEM; + + error = device_property_read_u32_array(dev, keymap_property, + keymap, n_keys); + if (error) { + dev_err(dev, "failed to parse '%s' property: %d\n", + keymap_property, error); + return error; + } + + data->t19_keymap = keymap; + data->t19_num_keys = n_keys; + } + + return 0; +} + +#ifdef CONFIG_OF + +static int mxt_check_dedicated_touch(struct device_node *dt, const char *prop, + const char *active_prop) +{ + const char *active_touch; + const char *compatible; + char *temp; + int ret = 0; + + ret = of_property_read_string(dt->parent, active_prop, &active_touch); + if (ret < 0) { + pr_info(" %s:not dedicated active touch\n", __func__); + return -ENODEV; + } + + ret = of_property_read_string(dt, prop, &compatible); + if (ret < 0) { + pr_err(" %s:fail to read %s %d\n", __func__, "compatible", ret); + return -ENODEV; + } + + temp = strnstr(active_touch, compatible, strlen(active_touch)); + if (!temp) { + pr_info(" %s:no match compatible, %s, %s\n", + __func__, compatible, active_touch); + return -ENODEV; + + } + + return ret; +} + +#ifdef CONFIG_DRM +static struct drm_panel *active_panel; +static int mxt_check_dt(struct device_node *np) +{ + int i; + int count; + struct device_node *node; + struct drm_panel *panel; + + count = of_count_phandle_with_args(np, "panel", NULL); + if (count <= 0) + return 0; + + for (i = 0; i < count; i++) { + node = of_parse_phandle(np, "panel", i); + panel = of_drm_find_panel(node); + of_node_put(node); + if (!IS_ERR(panel)) { + active_panel = panel; + return 0; + } + } + + return PTR_ERR(panel); +} +#endif + +static int mxt_get_dt_coords(struct device *dev, char *name, + struct mxt_data *data) +{ + u32 coords[MXT_COORDS_ARR_SIZE]; + struct property *prop; + struct device_node *np = dev->of_node; + size_t coords_size; + int error; + + prop = of_find_property(np, name, NULL); + if (!prop) + return -EINVAL; + if (!prop->value) + return -ENODATA; + + coords_size = prop->length / sizeof(u32); + if (coords_size != MXT_COORDS_ARR_SIZE) { + dev_err(dev, "invalid %s\n", name); + return -EINVAL; + } + + error = of_property_read_u32_array(np, name, coords, coords_size); + if (error && (error != -EINVAL)) { + dev_err(dev, "Unable to read %s\n", name); + return error; + } + + if (strcmp(name, "atmel,panel-coords") == 0) { + data->panel_minx = coords[0]; + data->panel_miny = coords[1]; + data->panel_maxx = coords[2]; + data->panel_maxy = coords[3]; + } else if (strcmp(name, "atmel,display-coords") == 0) { + data->disp_minx = coords[0]; + data->disp_miny = coords[1]; + data->disp_maxx = coords[2]; + data->disp_maxy = coords[3]; + } else { + dev_err(dev, "unsupported property %s\n", name); + return -EINVAL; + } + + return 0; +} + +#endif + +static const struct dmi_system_id chromebook_T9_suspend_dmi[] = { + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "GOOGLE"), + DMI_MATCH(DMI_PRODUCT_NAME, "Link"), + }, + }, + { + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "Peppy"), + }, + }, + { } +}; + +#ifdef CONFIG_DRM + +static void mxt_panel_notifier_callback(enum panel_event_notifier_tag tag, + struct panel_event_notification *notification, + void *client_data) +{ + struct mxt_data *mxt = client_data; + struct input_dev *input_dev; + + if (!notification) { + pr_err("Invalid notification\n"); + return; + } + + if (!mxt) { + pr_err("Invalid mxt data\n"); + return; + } + + pr_debug("Notification type:%d, early_trigger:%d\n", + notification->notif_type, + notification->notif_data.early_trigger); + + input_dev = mxt->input_dev; + + switch (notification->notif_type) { + case DRM_PANEL_EVENT_UNBLANK: + if (!notification->notif_data.early_trigger) { + if (input_dev) + mutex_lock(&input_dev->mutex); + + mxt_start(mxt); + + if (input_dev) + mutex_unlock(&input_dev->mutex); + } + break; + case DRM_PANEL_EVENT_BLANK: + if (input_dev) + mutex_lock(&input_dev->mutex); + + mxt_stop(mxt); + + if (input_dev) + mutex_unlock(&input_dev->mutex); + break; + default: + break; + } +} + +static void mxt_register_for_panel_events(struct device_node *dt, + struct mxt_data *data) +{ + void *cookie = NULL; + + cookie = panel_event_notifier_register( + PANEL_EVENT_NOTIFICATION_PRIMARY, + PANEL_EVENT_NOTIFIER_CLIENT_PRIMARY_TOUCH, + active_panel, &mxt_panel_notifier_callback, data); + if (!cookie) { + pr_err("Failed to register for panel events\n"); + return; + } + pr_debug("registered for panel notifications on panel: 0x%x\n", + active_panel); + + data->notifier_cookie = cookie; +} +#endif + +static int mxt_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct mxt_data *data; + int error = 0; +#ifdef CONFIG_OF + struct device_node *dt = client->dev.of_node; +#endif + /* + * Ignore devices that do not have device properties attached to + * them, as we need help determining whether we are dealing with + * touch screen or touchpad. + * + * So far on x86 the only users of Atmel touch controllers are + * Chromebooks, and chromeos_laptop driver will ensure that + * necessary properties are provided (if firmware does not do that). + */ + if (!device_property_present(&client->dev, "compatible")) + return -ENXIO; + + /* + * Ignore ACPI devices representing bootloader mode. + * + * This is a bit of a hack: Google Chromebook BIOS creates ACPI + * devices for both application and bootloader modes, but we are + * interested in application mode only (if device is in bootloader + * mode we'll end up switching into application anyway). So far + * application mode addresses were all above 0x40, so we'll use it + * as a threshold. + */ + if (ACPI_COMPANION(&client->dev) && client->addr < 0x40) + return -ENXIO; + +#ifdef CONFIG_OF +#ifdef CONFIG_DRM + error = mxt_check_dt(dt); + if (error == -EPROBE_DEFER) + return error; + + if (error) { + if (!(mxt_check_dedicated_touch(dt, "compatible", + "qcom,i2c-touch-active"))) + return -EPROBE_DEFER; + else + return -ENODEV; + } +#else + if (mxt_check_dedicated_touch(dt, "compatible", + "qcom,i2c-touch-active") < 0) + return -ENODEV; +#endif +#endif + data = devm_kzalloc(&client->dev, sizeof(struct mxt_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + snprintf(data->phys, sizeof(data->phys), "i2c-%u-%04x/input0", + client->adapter->nr, client->addr); + + data->client = client; + data->irq = client->irq; + i2c_set_clientdata(client, data); + +#ifdef CONFIG_OF + error = mxt_get_dt_coords(&client->dev, "atmel,display-coords", data); + if (error) + return error; + + data->xy_switch = of_property_read_bool(dt, "atmel,xy_switch"); + data->invertx = of_property_read_bool(dt, "atmel,invertx"); + data->inverty = of_property_read_bool(dt, "atmel,inverty"); +#endif + + data->cmd_buf = devm_kzalloc(&client->dev, 64, GFP_KERNEL); + if (!data->cmd_buf) + return -ENOMEM; + data->read_buf = data->cmd_buf + 32; + + init_completion(&data->bl_completion); + init_completion(&data->reset_completion); + init_completion(&data->crc_completion); + +#if defined(CONFIG_OF) && defined(CONFIG_DRM) + data->suspend_mode = MXT_SUSPEND_T9_CTRL; +#else + data->suspend_mode = dmi_check_system(chromebook_T9_suspend_dmi) ? + MXT_SUSPEND_T9_CTRL : MXT_SUSPEND_DEEP_SLEEP; +#endif + + error = mxt_parse_device_properties(data); + if (error) + return error; + + error = mxt_pinctrl_init(data); + if (error) + dev_info(&client->dev, "No pinctrl support\n"); + + data->reset_gpio = devm_gpiod_get_optional(&client->dev, + "reset", GPIOD_OUT_LOW); + data->irq_gpio = devm_gpiod_get_optional(&client->dev, + "irq", GPIOD_IN); + if (IS_ERR(data->reset_gpio)) { + error = PTR_ERR(data->reset_gpio); + dev_err(&client->dev, "Failed to get reset gpio: %d\n", error); + return error; + } + + gpiod_direction_input(data->irq_gpio); + gpiod_direction_output(data->reset_gpio, 0); + + error = mxt_regulator_parse(data); + if (error) { + dev_err(&client->dev, "Failed to parse regulators\n"); + return error; + } + + error = mxt_regulator_enable(data); + if (error) { + dev_err(&client->dev, "Error %d enabling regulators\n", error); + return error; + } + + error = devm_request_threaded_irq(&client->dev, client->irq, + NULL, mxt_interrupt, IRQF_ONESHOT, + client->name, data); + if (error) { + dev_err(&client->dev, "Failed to register interrupt\n"); + goto err_disable_regulator; + } + + disable_irq(client->irq); + + if (data->reset_gpio) { + gpiod_set_value(data->reset_gpio, 0); + msleep(MXT_RESET_GPIO_TIME); + gpiod_set_value(data->reset_gpio, 1); + msleep(MXT_RESET_INVALID_CHG); + } + + error = mxt_gpio_enable(data, true); + if (error) + dev_err(&client->dev, "Failed to configure gpios\n"); + + data->irq = data->client->irq = + gpiod_to_irq(data->irq_gpio); + + error = mxt_initialize(data); + if (error) + goto err_disable_regulator; + + error = sysfs_create_group(&client->dev.kobj, &mxt_attr_group); + if (error) { + dev_err(&client->dev, "Failure %d creating sysfs group\n", + error); + goto err_free_object; + } +#ifdef CONFIG_DRM + if (active_panel) + mxt_register_for_panel_events(dt, data); +#endif + return 0; + +err_free_object: + mxt_free_input_device(data); + mxt_free_object_table(data); + +err_disable_regulator: + mxt_regulator_disable(data); + return error; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0)) +static void mxt_remove(struct i2c_client *client) +{ + struct mxt_data *data = i2c_get_clientdata(client); + +#ifdef CONFIG_DRM + if (active_panel && data->notifier_cookie) + panel_event_notifier_unregister(data->notifier_cookie); +#endif + disable_irq(data->irq); + mxt_regulator_disable(data); + sysfs_remove_group(&client->dev.kobj, &mxt_attr_group); + mxt_free_input_device(data); + mxt_free_object_table(data); +} +#else +static int mxt_remove(struct i2c_client *client) +{ + struct mxt_data *data = i2c_get_clientdata(client); + +#ifdef CONFIG_DRM + if (active_panel && data->notifier_cookie) + panel_event_notifier_unregister(data->notifier_cookie); +#endif + disable_irq(data->irq); + mxt_regulator_disable(data); + sysfs_remove_group(&client->dev.kobj, &mxt_attr_group); + mxt_free_input_device(data); + mxt_free_object_table(data); + + return 0; +} +#endif + +static int __maybe_unused mxt_suspend(struct device *dev) +{ +#if !defined(CONFIG_DRM) + struct i2c_client *client = to_i2c_client(dev); + struct mxt_data *data = i2c_get_clientdata(client); + struct input_dev *input_dev = data->input_dev; + + if (!input_dev) + return 0; + + mutex_lock(&input_dev->mutex); + + if (input_dev->users) + mxt_stop(data); + + mutex_unlock(&input_dev->mutex); + + disable_irq(data->irq); +#endif + + return 0; +} + +static int __maybe_unused mxt_resume(struct device *dev) +{ +#if !defined(CONFIG_DRM) + struct i2c_client *client = to_i2c_client(dev); + struct mxt_data *data = i2c_get_clientdata(client); + struct input_dev *input_dev = data->input_dev; + + if (!input_dev) + return 0; + + enable_irq(data->irq); + + mutex_lock(&input_dev->mutex); + + if (input_dev->users) + mxt_start(data); + + mutex_unlock(&input_dev->mutex); +#endif + + return 0; +} + +static SIMPLE_DEV_PM_OPS(mxt_pm_ops, mxt_suspend, mxt_resume); + +static const struct of_device_id mxt_of_match[] = { + { .compatible = "atmel,maxtouch", }, + /* Compatibles listed below are deprecated */ + { .compatible = "atmel,qt602240_ts", }, + { .compatible = "atmel,atmel_mxt_ts", }, + { .compatible = "atmel,atmel_mxt_tp", }, + { .compatible = "atmel,mXT224", }, + {}, +}; +MODULE_DEVICE_TABLE(of, mxt_of_match); + +#ifdef CONFIG_ACPI +static const struct acpi_device_id mxt_acpi_id[] = { + { "ATML0000", 0 }, /* Touchpad */ + { "ATML0001", 0 }, /* Touchscreen */ + { } +}; +MODULE_DEVICE_TABLE(acpi, mxt_acpi_id); +#endif + +static const struct i2c_device_id mxt_id[] = { + { "qt602240_ts", 0 }, + { "atmel_mxt_ts", 0 }, + { "atmel_mxt_tp", 0 }, + { "maxtouch", 0 }, + { "mXT224", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mxt_id); + +static struct i2c_driver mxt_driver = { + .driver = { + .name = "atmel_mxt_ts", + .of_match_table = mxt_of_match, + .acpi_match_table = ACPI_PTR(mxt_acpi_id), + .pm = &mxt_pm_ops, + }, + .probe = mxt_probe, + .remove = mxt_remove, + .id_table = mxt_id, +}; + +module_i2c_driver(mxt_driver); + +/* Module information */ +MODULE_AUTHOR("Joonyoung Shim "); +MODULE_DESCRIPTION("Atmel maXTouch Touchscreen driver"); +MODULE_LICENSE("GPL"); diff --git a/qcom/opensource/touch-drivers/config/gki_bengaltouch.conf b/qcom/opensource/touch-drivers/config/gki_bengaltouch.conf new file mode 100644 index 0000000000..0bdf70a279 --- /dev/null +++ b/qcom/opensource/touch-drivers/config/gki_bengaltouch.conf @@ -0,0 +1,7 @@ +export CONFIG_MSM_TOUCH=m +export CONFIG_TOUCHSCREEN_SYNAPTICS_TCM=y +export CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_I2C=y +export CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_CORE=y +export CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_TOUCH=y +export CONFIG_TOUCH_FOCALTECH=y +export CONFIG_TOUCHSCREEN_FTS_DIRECTORY="focaltech_touch" diff --git a/qcom/opensource/touch-drivers/config/gki_bengaltouchconf.h b/qcom/opensource/touch-drivers/config/gki_bengaltouchconf.h new file mode 100644 index 0000000000..e0d703d824 --- /dev/null +++ b/qcom/opensource/touch-drivers/config/gki_bengaltouchconf.h @@ -0,0 +1,11 @@ +/* +* Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. +* SPDX-License-Identifier: GPL-2.0-only +*/ + +#define CONFIG_TOUCHSCREEN_SYNAPTICS_TCM 1 +#define CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_I2C 1 +#define CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_CORE 1 +#define CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_TOUCH 1 +#define CONFIG_TOUCH_FOCALTECH 1 +#define CONFIG_TOUCHSCREEN_FTS_DIRECTORY "focaltech_touch" diff --git a/qcom/opensource/touch-drivers/config/gki_blairtouch.conf b/qcom/opensource/touch-drivers/config/gki_blairtouch.conf new file mode 100644 index 0000000000..546b7d8fff --- /dev/null +++ b/qcom/opensource/touch-drivers/config/gki_blairtouch.conf @@ -0,0 +1,10 @@ +export CONFIG_MSM_TOUCH=m +export CONFIG_TOUCH_FOCALTECH=y +export CONFIG_TOUCHSCREEN_NT36XXX_I2C=y +export CONFIG_TOUCHSCREEN_SYNAPTICS_TCM=y +export CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_I2C=y +export CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_CORE=y +export CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_TOUCH=y +export CONFIG_TOUCHSCREEN_GOODIX_BRL=y +export CONFIG_TOUCHSCREEN_FTS_DIRECTORY="focaltech_touch" +export CONFIG_FTS_TRUSTED_TOUCH=n diff --git a/qcom/opensource/touch-drivers/config/gki_blairtouchconf.h b/qcom/opensource/touch-drivers/config/gki_blairtouchconf.h new file mode 100644 index 0000000000..27469e99cd --- /dev/null +++ b/qcom/opensource/touch-drivers/config/gki_blairtouchconf.h @@ -0,0 +1,13 @@ +/* +* Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. +* SPDX-License-Identifier: GPL-2.0-only +*/ + +#define CONFIG_TOUCH_FOCALTECH 1 +#define CONFIG_TOUCHSCREEN_NT36XXX_I2C 1 +#define CONFIG_TOUCHSCREEN_SYNAPTICS_TCM 1 +#define CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_I2C 1 +#define CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_CORE 1 +#define CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_TOUCH 1 +#define CONFIG_TOUCHSCREEN_GOODIX_BRL 1 +#define CONFIG_TOUCHSCREEN_FTS_DIRECTORY "focaltech_touch" diff --git a/qcom/opensource/touch-drivers/config/gki_crowtouch.conf b/qcom/opensource/touch-drivers/config/gki_crowtouch.conf new file mode 100644 index 0000000000..cfad2ad177 --- /dev/null +++ b/qcom/opensource/touch-drivers/config/gki_crowtouch.conf @@ -0,0 +1,2 @@ +export CONFIG_MSM_TOUCH=m +export CONFIG_TOUCHSCREEN_GOODIX_BRL=y diff --git a/qcom/opensource/touch-drivers/config/gki_crowtouchconf.h b/qcom/opensource/touch-drivers/config/gki_crowtouchconf.h new file mode 100644 index 0000000000..044dc92a10 --- /dev/null +++ b/qcom/opensource/touch-drivers/config/gki_crowtouchconf.h @@ -0,0 +1,6 @@ +/* +* Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. +* SPDX-License-Identifier: GPL-2.0-only +*/ + +#define CONFIG_TOUCHSCREEN_GOODIX_BRL 1 diff --git a/qcom/opensource/touch-drivers/config/gki_kalamatouch.conf b/qcom/opensource/touch-drivers/config/gki_kalamatouch.conf new file mode 100644 index 0000000000..0849bec783 --- /dev/null +++ b/qcom/opensource/touch-drivers/config/gki_kalamatouch.conf @@ -0,0 +1,5 @@ +export CONFIG_TOUCHSCREEN_NT36XXX_I2C=y +export CONFIG_TOUCHSCREEN_GOODIX_BRL=y +export CONFIG_TOUCHSCREEN_ATMEL_MXT=y +export CONFIG_TOUCHSCREEN_DUMMY=y +export CONFIG_MSM_TOUCH=m diff --git a/qcom/opensource/touch-drivers/config/gki_kalamatouchconf.h b/qcom/opensource/touch-drivers/config/gki_kalamatouchconf.h new file mode 100644 index 0000000000..7774e7359f --- /dev/null +++ b/qcom/opensource/touch-drivers/config/gki_kalamatouchconf.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2021, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#define CONFIG_TOUCHSCREEN_NT36XXX_I2C 1 +#define CONFIG_TOUCHSCREEN_GOODIX_BRL 1 +#define CONFIG_TOUCHSCREEN_ATMEL_MXT 1 + diff --git a/qcom/opensource/touch-drivers/config/gki_khajetouch.conf b/qcom/opensource/touch-drivers/config/gki_khajetouch.conf new file mode 100644 index 0000000000..29f3770654 --- /dev/null +++ b/qcom/opensource/touch-drivers/config/gki_khajetouch.conf @@ -0,0 +1,6 @@ +export CONFIG_TOUCHSCREEN_SYNAPTICS_TCM=y +export CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_I2C=y +export CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_CORE=y +export CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_TOUCH=y +export CONFIG_TOUCHSCREEN_NT36XXX_I2C=y +export CONFIG_MSM_TOUCH=m diff --git a/qcom/opensource/touch-drivers/config/gki_khajetouchconf.h b/qcom/opensource/touch-drivers/config/gki_khajetouchconf.h new file mode 100644 index 0000000000..f0a37b4c25 --- /dev/null +++ b/qcom/opensource/touch-drivers/config/gki_khajetouchconf.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#define CONFIG_TOUCHSCREEN_SYNAPTICS_TCM 1 +#define CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_I2C 1 +#define CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_CORE 1 +#define CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_TOUCH 1 +#define CONFIG_TOUCHSCREEN_NT36XXX_I2C 1 + diff --git a/qcom/opensource/touch-drivers/config/gki_konatouch.conf b/qcom/opensource/touch-drivers/config/gki_konatouch.conf new file mode 100644 index 0000000000..1921d5823e --- /dev/null +++ b/qcom/opensource/touch-drivers/config/gki_konatouch.conf @@ -0,0 +1,4 @@ +export CONFIG_MSM_TOUCH=m +export CONFIG_TOUCH_FOCALTECH=y +export CONFIG_TOUCHSCREEN_FTS_DIRECTORY="focaltech_touch" +export CONFIG_FTS_TRUSTED_TOUCH=n diff --git a/qcom/opensource/touch-drivers/config/gki_konatouchconf.h b/qcom/opensource/touch-drivers/config/gki_konatouchconf.h new file mode 100644 index 0000000000..954901a9fa --- /dev/null +++ b/qcom/opensource/touch-drivers/config/gki_konatouchconf.h @@ -0,0 +1,7 @@ +/* +* Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. +* SPDX-License-Identifier: GPL-2.0-only +*/ + +#define CONFIG_TOUCH_FOCALTECH 1 +#define CONFIG_TOUCHSCREEN_FTS_DIRECTORY "focaltech_touch" diff --git a/qcom/opensource/touch-drivers/config/gki_monacotouch.conf b/qcom/opensource/touch-drivers/config/gki_monacotouch.conf new file mode 100644 index 0000000000..cfa8246980 --- /dev/null +++ b/qcom/opensource/touch-drivers/config/gki_monacotouch.conf @@ -0,0 +1,7 @@ +export CONFIG_MSM_TOUCH=m +export CONFIG_TOUCHSCREEN_PARADE=y +export CONFIG_TOUCHSCREEN_PARADE_DEVICETREE_SUPPORT=y +export CONFIG_TOUCHSCREEN_PARADE_I2C=y +export CONFIG_TOUCHSCREEN_PARADE_DEVICE_ACCESS=y +export CONFIG_TOUCHSCREEN_RM_TS=y +export CONFIG_TOUCHSCREEN_MSM_GLINK=y diff --git a/qcom/opensource/touch-drivers/config/gki_monacotouchconf.h b/qcom/opensource/touch-drivers/config/gki_monacotouchconf.h new file mode 100644 index 0000000000..e940a0f454 --- /dev/null +++ b/qcom/opensource/touch-drivers/config/gki_monacotouchconf.h @@ -0,0 +1,8 @@ +#define CONFIG_TOUCHSCREEN_PARADE 1 +#define CONFIG_TOUCHSCREEN_PARADE_DEVICETREE_SUPPORT 1 +#define CONFIG_TOUCHSCREEN_PARADE_I2C 1 +#define CONFIG_TOUCHSCREEN_PARADE_DEVICE_ACCESS 1 +#define CONFIG_TOUCHSCREEN_PARADE_BUTTON 1 +#define CONFIG_TOUCHSCREEN_PARADE_PROXIMITY 1 +#define CONFIG_TOUCHSCREEN_RM_TS 1 +#define CONFIG_TOUCHSCREEN_MSM_GLINK 1 diff --git a/qcom/opensource/touch-drivers/config/gki_pineappletouch.conf b/qcom/opensource/touch-drivers/config/gki_pineappletouch.conf new file mode 100644 index 0000000000..0849bec783 --- /dev/null +++ b/qcom/opensource/touch-drivers/config/gki_pineappletouch.conf @@ -0,0 +1,5 @@ +export CONFIG_TOUCHSCREEN_NT36XXX_I2C=y +export CONFIG_TOUCHSCREEN_GOODIX_BRL=y +export CONFIG_TOUCHSCREEN_ATMEL_MXT=y +export CONFIG_TOUCHSCREEN_DUMMY=y +export CONFIG_MSM_TOUCH=m diff --git a/qcom/opensource/touch-drivers/config/gki_pineappletouchconf.h b/qcom/opensource/touch-drivers/config/gki_pineappletouchconf.h new file mode 100644 index 0000000000..934e7175b3 --- /dev/null +++ b/qcom/opensource/touch-drivers/config/gki_pineappletouchconf.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2022, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#define CONFIG_TOUCHSCREEN_NT36XXX_I2C 1 +#define CONFIG_TOUCHSCREEN_GOODIX_BRL 1 +#define CONFIG_TOUCHSCREEN_ATMEL_MXT 1 diff --git a/qcom/opensource/touch-drivers/config/gki_trinkettouch.conf b/qcom/opensource/touch-drivers/config/gki_trinkettouch.conf new file mode 100644 index 0000000000..9b5b5f85c6 --- /dev/null +++ b/qcom/opensource/touch-drivers/config/gki_trinkettouch.conf @@ -0,0 +1,5 @@ +export CONFIG_MSM_TOUCH=m +export CONFIG_TOUCHSCREEN_SYNAPTICS_TCM=y +export CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_I2C=y +export CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_CORE=y +export CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_TOUCH=y diff --git a/qcom/opensource/touch-drivers/config/gki_trinkettouchconf.h b/qcom/opensource/touch-drivers/config/gki_trinkettouchconf.h new file mode 100644 index 0000000000..981944037d --- /dev/null +++ b/qcom/opensource/touch-drivers/config/gki_trinkettouchconf.h @@ -0,0 +1,9 @@ +/* +* Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. +* SPDX-License-Identifier: GPL-2.0-only +*/ + +#define CONFIG_TOUCHSCREEN_SYNAPTICS_TCM 1 +#define CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_I2C 1 +#define CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_CORE 1 +#define CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_TOUCH 1 diff --git a/qcom/opensource/touch-drivers/config/gki_waipiotouch.conf b/qcom/opensource/touch-drivers/config/gki_waipiotouch.conf new file mode 100644 index 0000000000..8fa39f562b --- /dev/null +++ b/qcom/opensource/touch-drivers/config/gki_waipiotouch.conf @@ -0,0 +1,6 @@ +export CONFIG_TOUCH_FOCALTECH=y +export CONFIG_TOUCHSCREEN_FTS_DIRECTORY="focaltech_touch" +export CONFIG_FTS_TRUSTED_TOUCH=y +export CONFIG_TOUCHSCREEN_SYNAPTICS_DSX=y +export CONFIG_TOUCHSCREEN_NT36XXX_I2C=y +export CONFIG_TOUCH_BUILD=m diff --git a/qcom/opensource/touch-drivers/config/gki_waipiotouchconf.h b/qcom/opensource/touch-drivers/config/gki_waipiotouchconf.h new file mode 100644 index 0000000000..ef2175bb56 --- /dev/null +++ b/qcom/opensource/touch-drivers/config/gki_waipiotouchconf.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + */ + +#define CONFIG_TOUCH_FOCALTECH 1 +#define CONFIG_TOUCHSCREEN_FTS_DIRECTORY "focaltech_touch" +#define CONFIG_FTS_TRUSTED_TOUCH 1 +#define CONFIG_TOUCHSCREEN_SYNAPTICS_DSX 1 +#define CONFIG_TOUCHSCREEN_NT36XXX_I2C 1 diff --git a/qcom/opensource/touch-drivers/dummy_touch/dummy_touch.c b/qcom/opensource/touch-drivers/dummy_touch/dummy_touch.c new file mode 100644 index 0000000000..59558bbb6b --- /dev/null +++ b/qcom/opensource/touch-drivers/dummy_touch/dummy_touch.c @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include + +MODULE_DESCRIPTION("QTI dummy touchscreen driver"); +MODULE_LICENSE("GPL v2"); + +#define DEVICE_COMPATIBLE "qti,dummy_ts" +#define DRIVER_NAME "qti_dummy_ts" + +static int dummy_touch_probe(struct platform_device *device) +{ + struct input_dev *touch_dev = NULL; + int rc; + + touch_dev = input_allocate_device(); + if (!touch_dev) { + pr_err("%s: input device allocate failed\n", __func__); + return -ENOMEM; + } + + touch_dev->name = "qti_dummy_ts"; + touch_dev->id.bustype = BUS_VIRTUAL; + touch_dev->evbit[0] = BIT_MASK(EV_KEY); + touch_dev->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0); + touch_dev->dev.parent = NULL; + + rc = input_register_device(touch_dev); + if (rc) { + pr_err("%s: touch device register failed, rc = %d\n", __func__, rc); + input_free_device(touch_dev); + return rc; + } + + pr_info("qti_dummy_ts device registered\n"); + + platform_set_drvdata(device, (void *)touch_dev); + + return 0; +} + +static int dummy_touch_remove(struct platform_device *device) +{ + struct input_dev *touch_dev = NULL; + + touch_dev = (struct input_dev *)platform_get_drvdata(device); + if (touch_dev) + input_free_device(touch_dev); + + return 0; +} + +static const struct of_device_id dummy_touch_id[] = { + {.compatible = DEVICE_COMPATIBLE}, + {} +}; + +static struct platform_driver dummy_touch_driver = { + + .driver = { + .name = DRIVER_NAME, + .of_match_table = dummy_touch_id, + }, + .probe = dummy_touch_probe, + .remove = dummy_touch_remove, +}; + +static int __init dummy_touch_init(void) +{ + platform_driver_register(&dummy_touch_driver); + + return 0; +} + +static void __exit dummy_touch_exit(void) +{ + platform_driver_unregister(&dummy_touch_driver); +} + +late_initcall(dummy_touch_init); +module_exit(dummy_touch_exit); diff --git a/qcom/opensource/touch-drivers/focaltech_touch/focaltech_common.h b/qcom/opensource/touch-drivers/focaltech_touch/focaltech_common.h new file mode 100644 index 0000000000..c9e93e1875 --- /dev/null +++ b/qcom/opensource/touch-drivers/focaltech_touch/focaltech_common.h @@ -0,0 +1,167 @@ +/* + * + * FocalTech fts TouchScreen driver. + * + * Copyright (c) 2012-2019, Focaltech Ltd. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +/***************************************************************************** +* +* File Name: focaltech_common.h +* +* Author: Focaltech Driver Team +* +* Created: 2016-08-16 +* +* Abstract: +* +* Reference: +* +*****************************************************************************/ + +#ifndef __LINUX_FOCALTECH_COMMON_H__ +#define __LINUX_FOCALTECH_COMMON_H__ + +#include "focaltech_config.h" + +/***************************************************************************** +* Macro definitions using #define +*****************************************************************************/ +#define FTS_DRIVER_VERSION "Focaltech V3.2 20200422" + +#define BYTE_OFF_0(x) (u8)((x) & 0xFF) +#define BYTE_OFF_8(x) (u8)(((x) >> 8) & 0xFF) +#define BYTE_OFF_16(x) (u8)(((x) >> 16) & 0xFF) +#define BYTE_OFF_24(x) (u8)(((x) >> 24) & 0xFF) +#define FLAGBIT(x) (0x00000001 << (x)) +#define FLAGBITS(x, y) ((0xFFFFFFFF >> (32 - (y) - 1)) & (0xFFFFFFFF << (x))) + +#define FLAG_ICSERIALS_LEN 8 +#define FLAG_HID_BIT 10 +#define FLAG_IDC_BIT 11 + +#define IC_SERIALS(type) ((type) & FLAGBITS(0, FLAG_ICSERIALS_LEN-1)) +#define IC_TO_SERIALS(x) ((x) & FLAGBITS(0, FLAG_ICSERIALS_LEN-1)) +#define FTS_CHIP_IDC(type) (((type) & FLAGBIT(FLAG_IDC_BIT)) == FLAGBIT(FLAG_IDC_BIT)) +#define FTS_HID_SUPPORTTED(type) (((type) & FLAGBIT(FLAG_HID_BIT)) == FLAGBIT(FLAG_HID_BIT)) + +#define FILE_NAME_LENGTH 128 +#define ENABLE 1 +#define DISABLE 0 +#define VALID 1 +#define INVALID 0 +#define FTS_CMD_START1 0x55 +#define FTS_CMD_START2 0xAA +#define FTS_CMD_START_DELAY 12 +#define FTS_CMD_READ_ID 0x90 +#define FTS_CMD_READ_ID_LEN 4 +#define FTS_CMD_READ_ID_LEN_INCELL 1 +#define FTS_CMD_READ_FW_CONF 0xA8 +/*register address*/ +#define FTS_REG_INT_CNT 0x8F +#define FTS_REG_FLOW_WORK_CNT 0x91 +#define FTS_REG_WORKMODE 0x00 +#define FTS_REG_WORKMODE_FACTORY_VALUE 0x40 +#define FTS_REG_WORKMODE_WORK_VALUE 0x00 +#define FTS_REG_ESDCHECK_DISABLE 0x8D +#define FTS_REG_CHIP_ID 0xA3 +#define FTS_REG_CHIP_ID2 0x9F +#define FTS_REG_POWER_MODE 0xA5 +#define FTS_REG_POWER_MODE_SLEEP 0x03 +#define FTS_REG_FW_VER 0xA6 +#define FTS_REG_VENDOR_ID 0xA8 +#define FTS_REG_LCD_BUSY_NUM 0xAB +#define FTS_REG_FACE_DEC_MODE_EN 0xB0 +#define FTS_REG_FACTORY_MODE_DETACH_FLAG 0xB4 +#define FTS_REG_FACE_DEC_MODE_STATUS 0x01 +#define FTS_REG_IDE_PARA_VER_ID 0xB5 +#define FTS_REG_IDE_PARA_STATUS 0xB6 +#define FTS_REG_GLOVE_MODE_EN 0xC0 +#define FTS_REG_COVER_MODE_EN 0xC1 +#define FTS_REG_REPORT_RATE 0x88 +#define FTS_REG_CHARGER_MODE_EN 0x8B +#define FTS_REG_GESTURE_EN 0xD0 +#define FTS_REG_GESTURE_OUTPUT_ADDRESS 0xD3 +#define FTS_REG_MODULE_ID 0xE3 +#define FTS_REG_LIC_VER 0xE4 +#define FTS_REG_ESD_SATURATE 0xED + +#define FTS_SYSFS_ECHO_ON(buf) (buf[0] == '1') +#define FTS_SYSFS_ECHO_OFF(buf) (buf[0] == '0') + +#define kfree_safe(pbuf) do {\ + if (pbuf) {\ + kfree(pbuf);\ + pbuf = NULL;\ + }\ +} while(0) + +/***************************************************************************** +* Alternative mode (When something goes wrong, +* the modules may be able to solve the problem.) +*****************************************************************************/ +/* + * point report check + * default: disable + */ +#define FTS_POINT_REPORT_CHECK_EN 0 + +/***************************************************************************** +* Global variable or extern global variabls/functions +*****************************************************************************/ +struct ft_chip_t { + u64 type; + u8 chip_idh; + u8 chip_idl; + u8 rom_idh; + u8 rom_idl; + u8 pb_idh; + u8 pb_idl; + u8 bl_idh; + u8 bl_idl; +}; + +struct ts_ic_info { + bool is_incell; + bool hid_supported; + struct ft_chip_t ids; +}; + +/***************************************************************************** +* DEBUG function define here +*****************************************************************************/ +#if FTS_DEBUG_EN +#define FTS_DEBUG(fmt, args...) do { \ + printk("[FTS_TS]%s:"fmt"\n", __func__, ##args); \ +} while (0) + +#define FTS_FUNC_ENTER() do { \ + printk("[FTS_TS]%s: Enter\n", __func__); \ +} while (0) + +#define FTS_FUNC_EXIT() do { \ + printk("[FTS_TS]%s: Exit(%d)\n", __func__, __LINE__); \ +} while (0) +#else /* #if FTS_DEBUG_EN*/ +#define FTS_DEBUG(fmt, args...) +#define FTS_FUNC_ENTER() +#define FTS_FUNC_EXIT() +#endif + +#define FTS_INFO(fmt, args...) do { \ + printk(KERN_INFO "[FTS_TS/I]%s:"fmt"\n", __func__, ##args); \ +} while (0) + +#define FTS_ERROR(fmt, args...) do { \ + printk(KERN_ERR "[FTS_TS/E]%s:"fmt"\n", __func__, ##args); \ +} while (0) +#endif /* __LINUX_FOCALTECH_COMMON_H__ */ diff --git a/qcom/opensource/touch-drivers/focaltech_touch/focaltech_config.h b/qcom/opensource/touch-drivers/focaltech_touch/focaltech_config.h new file mode 100644 index 0000000000..a089e1607b --- /dev/null +++ b/qcom/opensource/touch-drivers/focaltech_touch/focaltech_config.h @@ -0,0 +1,278 @@ +/* + * + * FocalTech TouchScreen driver. + * + * Copyright (c) 2012-2019, FocalTech Systems, Ltd., all rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/************************************************************************ +* +* File Name: focaltech_config.h +* +* Author: Focaltech Driver Team +* +* Created: 2016-08-08 +* +* Abstract: global configurations +* +* Version: v1.0 +* +************************************************************************/ +#ifndef _LINUX_FOCLATECH_CONFIG_H_ +#define _LINUX_FOCLATECH_CONFIG_H_ + +/**************************************************/ +/****** G: A, I: B, S: C, U: D ******************/ +/****** chip type defines, do not modify *********/ +#define _FT8716 0x87160805 +#define _FT8736 0x87360806 +#define _FT8006M 0x80060807 +#define _FT8607 0x86070809 +#define _FT8006U 0x8006D80B +#define _FT8006S 0x8006A80B +#define _FT8613 0x8613080C +#define _FT8719 0x8719080D +#define _FT8739 0x8739080E +#define _FT8615 0x8615080F +#define _FT8201 0x82010810 +#define _FT8006P 0x86220811 +#define _FT7251 0x72510812 +#define _FT7252 0x72520813 +#define _FT8613S 0x8613C814 +#define _FT8756 0x87560815 +#define _FT8302 0x83020816 +#define _FT8009 0x80090817 +#define _FT8656 0x86560818 +#define _FT8006S_AA 0x86320819 +#define _FT7250 0x7250081A +#define _FT7120 0x7120081B +#define _FT8720 0x8720081C +#define _FT8726 0x8726081C +#define _FT8016 0x8016081D + + +#define _FT5416 0x54160402 +#define _FT5426 0x54260402 +#define _FT5435 0x54350402 +#define _FT5436 0x54360402 +#define _FT5526 0x55260402 +#define _FT5526I 0x5526B402 +#define _FT5446 0x54460402 +#define _FT5346 0x53460402 +#define _FT5446I 0x5446B402 +#define _FT5346I 0x5346B402 +#define _FT7661 0x76610402 +#define _FT7511 0x75110402 +#define _FT7421 0x74210402 +#define _FT7681 0x76810402 +#define _FT3C47U 0x3C47D402 +#define _FT3417 0x34170402 +#define _FT3517 0x35170402 +#define _FT3327 0x33270402 +#define _FT3427 0x34270402 +#define _FT7311 0x73110402 +#define _FT5526_V00 0x5526C402 + +#define _FT5626 0x56260401 +#define _FT5726 0x57260401 +#define _FT5826B 0x5826B401 +#define _FT5826S 0x5826C401 +#define _FT7811 0x78110401 +#define _FT3D47 0x3D470401 +#define _FT3617 0x36170401 +#define _FT3717 0x37170401 +#define _FT3817B 0x3817B401 +#define _FT3517U 0x3517D401 + +#define _FT6236U 0x6236D003 +#define _FT6336G 0x6336A003 +#define _FT6336U 0x6336D003 +#define _FT6436U 0x6436D003 +#define _FT6436T 0x6436E003 + +#define _FT3267 0x32670004 +#define _FT3367 0x33670004 + +#define _FT3327DQQ_XXX 0x3327D482 +#define _FT5446DQS_XXX 0x5446D482 + +#define _FT3427_003 0x3427D482 +#define _FT3427G_003 0x3427A482 +#define _FT5446_003 0x5446D482 +#define _FT5446_Q03 0x5446C482 +#define _FT5446_P03 0x5446A481 +#define _FT5426_003 0x5426D482 +#define _FT5526_003 0x5526D482 + +#define _FT3518 0x35180481 +#define _FT3518U 0x3518D481 +#define _FT3558 0x35580481 +#define _FT3528 0x35280481 +#define _FT5536 0x55360481 +#define _FT5536L 0x5536E481 +#define _FT3418 0x34180481 + +#define _FT5446U 0x5446D083 +#define _FT5456U 0x5456D083 +#define _FT3417U 0x3417D083 +#define _FT5426U 0x5426D083 +#define _FT3428 0x34280083 +#define _FT3437U 0x3437D083 + +#define _FT7302 0x73020084 +#define _FT7202 0x72020084 +#define _FT3308 0x33080084 +#define _FT6446 0x64460084 + +#define _FT6346U 0x6346D085 +#define _FT6346G 0x6346A085 +#define _FT3067 0x30670085 +#define _FT3068 0x30680085 +#define _FT3168 0x31680085 +#define _FT3268 0x32680085 +#define _FT6146 0x61460085 + +#define _FT5726_003 0x5726D486 +#define _FT5726_V03 0x5726C486 + +#define _FT3618 0x36180487 +#define _FT5646 0x56460487 +#define _FT3A58 0x3A580487 +#define _FT3B58 0x3B580487 +#define _FT3D58 0x3D580487 +#define _FT5936 0x59360487 +#define _FT5A36 0x5A360487 +#define _FT5B36 0x5B360487 +#define _FT5D36 0x5D360487 +#define _FT5946 0x59460487 +#define _FT5A46 0x5A460487 +#define _FT5B46 0x5B460487 +#define _FT5D46 0x5D460487 + +#define _FT3658U 0x3658D488 + +/******************* Enables *********************/ +/*********** 1 to enable, 0 to disable ***********/ + +/* + * show debug log info + * enable it for debug, disable it for release + */ +#define FTS_DEBUG_EN 0 + +/* + * Linux MultiTouch Protocol + * 1: Protocol B(default), 0: Protocol A + */ +#define FTS_MT_PROTOCOL_B_EN 1 + +/* + * Report Pressure in multitouch + * 1:enable(default),0:disable +*/ +#define FTS_REPORT_PRESSURE_EN 1 + +/* + * Gesture function enable + * default: disable + */ +#define FTS_GESTURE_EN 0 + +/* + * ESD check & protection + * default: disable + */ +#define FTS_ESDCHECK_EN 0 + +/* + * Production test enable + * 1: enable, 0:disable(default) + */ +#define FTS_TEST_EN 0 + +/* + * Pinctrl enable + * default: disable + */ +#define FTS_PINCTRL_EN 1 + +/* + * Customer power enable + * enable it when customer need control TP power + * default: disable + */ +#define FTS_POWER_SOURCE_CUST_EN 1 + +/****************************************************/ + +/********************** Upgrade ****************************/ +/* + * auto upgrade + */ +#define FTS_AUTO_UPGRADE_EN 1 + +/* + * auto upgrade for lcd cfg + */ +#define FTS_AUTO_LIC_UPGRADE_EN 0 + +/* + * Numbers of modules support + */ +#define FTS_GET_MODULE_NUM 2 + +/* + * module_id: mean vendor_id generally, also maybe gpio or lcm_id... + * If means vendor_id, the FTS_MODULE_ID = PANEL_ID << 8 + VENDOR_ID + * FTS_GET_MODULE_NUM == 0/1, no check module id, you may ignore them + * FTS_GET_MODULE_NUM >= 2, compatible with FTS_MODULE2_ID + * FTS_GET_MODULE_NUM >= 3, compatible with FTS_MODULE3_ID + */ +#define FTS_MODULE_ID 0x0000 +#define FTS_MODULE2_ID 0xd566 +#define FTS_MODULE3_ID 0x0000 + +/* + * Need set the following when get firmware via firmware_request() + * For example: if module'vendor is tianma, + * #define FTS_MODULE_NAME "tianma" + * then file_name will be "focaltech_ts_fw_tianma" + * You should rename fw to "focaltech_ts_fw_tianma", and push it into + * etc/firmware or by customers + */ +#define FTS_MODULE_NAME "gvo" +#define FTS_MODULE2_NAME "jdi" +#define FTS_MODULE3_NAME "" + +/* + * FW.i file for auto upgrade, you must replace it with your own + * define your own fw_file, the sample one to be replaced is invalid + * NOTE: if FTS_GET_MODULE_NUM > 1, it's the fw corresponding with FTS_VENDOR_ID + */ +#define FTS_UPGRADE_FW_FILE "include/firmware/fw_sample.i" + +/* + * if FTS_GET_MODULE_NUM >= 2, fw corrsponding with FTS_VENDOR_ID2 + * define your own fw_file, the sample one is invalid + */ +#define FTS_UPGRADE_FW2_FILE "include/firmware/fw_sample.i" + +/* + * if FTS_GET_MODULE_NUM >= 3, fw corrsponding with FTS_VENDOR_ID3 + * define your own fw_file, the sample one is invalid + */ +#define FTS_UPGRADE_FW3_FILE "include/firmware/fw_sample.i" + +/*********************************************************/ + +#endif /* _LINUX_FOCLATECH_CONFIG_H_ */ diff --git a/qcom/opensource/touch-drivers/focaltech_touch/focaltech_core.c b/qcom/opensource/touch-drivers/focaltech_touch/focaltech_core.c new file mode 100644 index 0000000000..8d836c5cea --- /dev/null +++ b/qcom/opensource/touch-drivers/focaltech_touch/focaltech_core.c @@ -0,0 +1,3423 @@ +/* + * + * FocalTech TouchScreen driver. + * + * Copyright (c) 2012-2019, FocalTech Systems, Ltd., all rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +/***************************************************************************** +* +* File Name: focaltech_core.c +* +* Author: Focaltech Driver Team +* +* Created: 2016-08-08 +* +* Abstract: entrance for focaltech ts driver +* +* Version: V1.0 +* +*****************************************************************************/ + +/***************************************************************************** +* Included header files +*****************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(CONFIG_DRM) +#include +#elif defined(CONFIG_FB) +#include +#include +#elif defined(CONFIG_HAS_EARLYSUSPEND) +#include +#define FTS_SUSPEND_LEVEL 1 /* Early-suspend level */ +#endif +#include "focaltech_core.h" + +#if defined(CONFIG_FTS_TRUSTED_TOUCH) +#include +#include +#include +#include +#include +#include +#include +#include +#include "linux/gunyah/gh_msgq.h" +#include "linux/gunyah/gh_rm_drv.h" +#include +#include +#endif + +/***************************************************************************** +* Private constant and macro definitions using #define +*****************************************************************************/ +#define FTS_DRIVER_NAME "fts_ts" +#define INTERVAL_READ_REG 200 /* unit:ms */ +#define TIMEOUT_READ_REG 1000 /* unit:ms */ +#if FTS_POWER_SOURCE_CUST_EN +#define FTS_VTG_MIN_UV 3000000 +#define FTS_VTG_MAX_UV 3300000 +#define FTS_LOAD_MAX_UA 30000 +#define FTS_LOAD_AVDD_UA 10000 +#define FTS_LOAD_DISABLE_UA 0 +#define FTS_I2C_VTG_MIN_UV 1800000 +#define FTS_I2C_VTG_MAX_UV 1800000 +#endif + +/***************************************************************************** +* Global variable or extern global variabls/functions +*****************************************************************************/ +struct fts_ts_data *fts_data; + +#if defined(CONFIG_DRM) +static struct drm_panel *active_panel; +static void fts_ts_panel_notifier_callback(enum panel_event_notifier_tag tag, + struct panel_event_notification *event, void *client_data); +#endif + +static struct ft_chip_t ctype[] = { + {0x88, 0x56, 0x52, 0x00, 0x00, 0x00, 0x00, 0x56, 0xB2}, + {0x81, 0x54, 0x52, 0x54, 0x52, 0x00, 0x00, 0x54, 0x5C}, + {0x1C, 0x87, 0x26, 0x87, 0x20, 0x87, 0xA0, 0x00, 0x00}, +}; + +/***************************************************************************** +* Static function prototypes +*****************************************************************************/ +static int fts_ts_suspend(struct device *dev); +static int fts_ts_resume(struct device *dev); +static irqreturn_t fts_irq_handler(int irq, void *data); +static int fts_ts_probe_delayed(struct fts_ts_data *fts_data); +static int fts_ts_enable_reg(struct fts_ts_data *ts_data, bool enable); + +static void fts_ts_register_for_panel_events(struct device_node *dp, + struct fts_ts_data *ts_data) +{ + const char *touch_type; + int rc = 0; + void *cookie = NULL; + + rc = of_property_read_string(dp, "focaltech,touch-type", + &touch_type); + if (rc) { + dev_warn(&fts_data->client->dev, + "%s: No touch type\n", __func__); + return; + } + if (strcmp(touch_type, "primary")) { + pr_err("Invalid touch type\n"); + return; + } + + cookie = panel_event_notifier_register(PANEL_EVENT_NOTIFICATION_PRIMARY, + PANEL_EVENT_NOTIFIER_CLIENT_PRIMARY_TOUCH, active_panel, + &fts_ts_panel_notifier_callback, ts_data); + if (!cookie) { + pr_err("Failed to register for panel events\n"); + return; + } + + FTS_DEBUG("registered for panel notifications panel: 0x%x\n", + active_panel); + + ts_data->notifier_cookie = cookie; +} + +#ifdef CONFIG_FTS_TRUSTED_TOUCH + +static void fts_ts_trusted_touch_abort_handler(struct fts_ts_data *fts_data, + int error); +static struct gh_acl_desc *fts_ts_vm_get_acl(enum gh_vm_names vm_name) +{ + struct gh_acl_desc *acl_desc; + gh_vmid_t vmid; + + gh_rm_get_vmid(vm_name, &vmid); + + acl_desc = kzalloc(offsetof(struct gh_acl_desc, acl_entries[1]), + GFP_KERNEL); + if (!acl_desc) + return ERR_PTR(ENOMEM); + + acl_desc->n_acl_entries = 1; + acl_desc->acl_entries[0].vmid = vmid; + acl_desc->acl_entries[0].perms = GH_RM_ACL_R | GH_RM_ACL_W; + + return acl_desc; +} + +static struct gh_sgl_desc *fts_ts_vm_get_sgl( + struct trusted_touch_vm_info *vm_info) +{ + struct gh_sgl_desc *sgl_desc; + int i; + + sgl_desc = kzalloc(offsetof(struct gh_sgl_desc, + sgl_entries[vm_info->iomem_list_size]), GFP_KERNEL); + if (!sgl_desc) + return ERR_PTR(ENOMEM); + + sgl_desc->n_sgl_entries = vm_info->iomem_list_size; + + for (i = 0; i < vm_info->iomem_list_size; i++) { + sgl_desc->sgl_entries[i].ipa_base = vm_info->iomem_bases[i]; + sgl_desc->sgl_entries[i].size = vm_info->iomem_sizes[i]; + } + + return sgl_desc; +} + +static int fts_ts_populate_vm_info_iomem(struct fts_ts_data *fts_data) +{ + int i, gpio, rc = 0; + int num_regs, num_sizes, num_gpios, list_size; + struct resource res; + struct device_node *np = fts_data->dev->of_node; + struct trusted_touch_vm_info *vm_info = fts_data->vm_info; + + num_regs = of_property_count_u32_elems(np, "focaltech,trusted-touch-io-bases"); + if (num_regs < 0) { + FTS_ERROR("Invalid number of IO regions specified\n"); + return -EINVAL; + } + + num_sizes = of_property_count_u32_elems(np, "focaltech,trusted-touch-io-sizes"); + if (num_sizes < 0) { + FTS_ERROR("Invalid number of IO regions specified\n"); + return -EINVAL; + } + + if (num_regs != num_sizes) { + FTS_ERROR("IO bases and sizes array lengths mismatch\n"); + return -EINVAL; + } + + num_gpios = of_gpio_named_count(np, "focaltech,trusted-touch-vm-gpio-list"); + if (num_gpios < 0) { + dev_warn(fts_data->dev, "Ignoring invalid trusted gpio list: %d\n", num_gpios); + num_gpios = 0; + } + + list_size = num_regs + num_gpios; + vm_info->iomem_list_size = list_size; + vm_info->iomem_bases = devm_kcalloc(fts_data->dev, list_size, sizeof(*vm_info->iomem_bases), + GFP_KERNEL); + if (!vm_info->iomem_bases) + return -ENOMEM; + + vm_info->iomem_sizes = devm_kcalloc(fts_data->dev, list_size, sizeof(*vm_info->iomem_sizes), + GFP_KERNEL); + if (!vm_info->iomem_sizes) + return -ENOMEM; + + for (i = 0; i < num_gpios; ++i) { + gpio = of_get_named_gpio(np, "focaltech,trusted-touch-vm-gpio-list", i); + if (gpio < 0 || !gpio_is_valid(gpio)) { + FTS_ERROR("Invalid gpio %d at position %d\n", gpio, i); + return gpio; + } + + if (!msm_gpio_get_pin_address(gpio, &res)) { + FTS_ERROR("Failed to retrieve gpio-%d resource\n", gpio); + return -ENODATA; + } + + vm_info->iomem_bases[i] = res.start; + vm_info->iomem_sizes[i] = resource_size(&res); + } + + rc = of_property_read_u32_array(np, "focaltech,trusted-touch-io-bases", + &vm_info->iomem_bases[i], list_size - i); + if (rc) { + FTS_ERROR("Failed to read trusted touch io bases:%d\n", rc); + return rc; + } + + rc = of_property_read_u32_array(np, "focaltech,trusted-touch-io-sizes", + &vm_info->iomem_sizes[i], list_size - i); + if (rc) { + FTS_ERROR("Failed to read trusted touch io sizes:%d\n", rc); + return rc; + } + + return 0; +} + +static int fts_ts_populate_vm_info(struct fts_ts_data *fts_data) +{ + int rc; + struct trusted_touch_vm_info *vm_info; + struct device_node *np = fts_data->dev->of_node; + + vm_info = devm_kzalloc(fts_data->dev, sizeof(struct trusted_touch_vm_info), GFP_KERNEL); + if (!vm_info) + return -ENOMEM; + + fts_data->vm_info = vm_info; + vm_info->vm_name = GH_TRUSTED_VM; + rc = of_property_read_u32(np, "focaltech,trusted-touch-spi-irq", &vm_info->hw_irq); + if (rc) { + pr_err("Failed to read trusted touch SPI irq:%d\n", rc); + return rc; + } + + rc = fts_ts_populate_vm_info_iomem(fts_data); + if (rc) { + pr_err("Failed to read trusted touch mmio ranges:%d\n", rc); + return rc; + } + + rc = of_property_read_string(np, "focaltech,trusted-touch-type", + &vm_info->trusted_touch_type); + if (rc) { + pr_warn("%s: No trusted touch type selection made\n", __func__); + vm_info->mem_tag = GH_MEM_NOTIFIER_TAG_TOUCH_PRIMARY; + vm_info->irq_label = GH_IRQ_LABEL_TRUSTED_TOUCH_PRIMARY; + rc = 0; + } else if (!strcmp(vm_info->trusted_touch_type, "primary")) { + vm_info->mem_tag = GH_MEM_NOTIFIER_TAG_TOUCH_PRIMARY; + vm_info->irq_label = GH_IRQ_LABEL_TRUSTED_TOUCH_PRIMARY; + } else if (!strcmp(vm_info->trusted_touch_type, "secondary")) { + vm_info->mem_tag = GH_MEM_NOTIFIER_TAG_TOUCH_SECONDARY; + vm_info->irq_label = GH_IRQ_LABEL_TRUSTED_TOUCH_SECONDARY; + } + + return 0; +} + +static void fts_ts_destroy_vm_info(struct fts_ts_data *fts_data) +{ + kfree(fts_data->vm_info->iomem_sizes); + kfree(fts_data->vm_info->iomem_bases); + kfree(fts_data->vm_info); +} + +static void fts_ts_vm_deinit(struct fts_ts_data *fts_data) +{ + if (fts_data->vm_info->mem_cookie) + gh_mem_notifier_unregister(fts_data->vm_info->mem_cookie); + fts_ts_destroy_vm_info(fts_data); +} + +static int fts_ts_trusted_touch_get_vm_state(struct fts_ts_data *fts_data) +{ + return atomic_read(&fts_data->vm_info->vm_state); +} + +static void fts_ts_trusted_touch_set_vm_state(struct fts_ts_data *fts_data, + int state) +{ + atomic_set(&fts_data->vm_info->vm_state, state); +} + +#ifdef CONFIG_ARCH_QTI_VM +static int fts_ts_vm_mem_release(struct fts_ts_data *fts_data); +static void fts_ts_trusted_touch_tvm_vm_mode_disable(struct fts_ts_data *fts_data); +static void fts_ts_trusted_touch_abort_tvm(struct fts_ts_data *fts_data); +static void fts_ts_trusted_touch_event_notify(struct fts_ts_data *fts_data, int event); + +void fts_ts_trusted_touch_tvm_i2c_failure_report(struct fts_ts_data *fts_data) +{ + pr_err("initiating trusted touch abort due to i2c failure\n"); + fts_ts_trusted_touch_abort_handler(fts_data, + TRUSTED_TOUCH_EVENT_I2C_FAILURE); +} + +static void fts_ts_trusted_touch_reset_gpio_toggle(struct fts_ts_data *fts_data) +{ + void __iomem *base; + + if (fts_data->bus_type != BUS_TYPE_I2C) + return; + + base = ioremap(TOUCH_RESET_GPIO_BASE, TOUCH_RESET_GPIO_SIZE); + writel_relaxed(0x1, base + TOUCH_RESET_GPIO_OFFSET); + /* wait until toggle to finish*/ + wmb(); + writel_relaxed(0x0, base + TOUCH_RESET_GPIO_OFFSET); + /* wait until toggle to finish*/ + wmb(); + iounmap(base); +} + +static void fts_trusted_touch_intr_gpio_toggle(struct fts_ts_data *fts_data, + bool enable) +{ + void __iomem *base; + u32 val; + + if (fts_data->bus_type != BUS_TYPE_I2C) + return; + + base = ioremap(TOUCH_INTR_GPIO_BASE, TOUCH_INTR_GPIO_SIZE); + val = readl_relaxed(base + TOUCH_RESET_GPIO_OFFSET); + if (enable) { + val |= BIT(0); + writel_relaxed(val, base + TOUCH_INTR_GPIO_OFFSET); + /* wait until toggle to finish*/ + wmb(); + } else { + val &= ~BIT(0); + writel_relaxed(val, base + TOUCH_INTR_GPIO_OFFSET); + /* wait until toggle to finish*/ + wmb(); + } + iounmap(base); +} + +static int fts_ts_sgl_cmp(const void *a, const void *b) +{ + struct gh_sgl_entry *left = (struct gh_sgl_entry *)a; + struct gh_sgl_entry *right = (struct gh_sgl_entry *)b; + + return (left->ipa_base - right->ipa_base); +} + +static int fts_ts_vm_compare_sgl_desc(struct gh_sgl_desc *expected, + struct gh_sgl_desc *received) +{ + int idx; + + if (expected->n_sgl_entries != received->n_sgl_entries) + return -E2BIG; + sort(received->sgl_entries, received->n_sgl_entries, + sizeof(received->sgl_entries[0]), fts_ts_sgl_cmp, NULL); + sort(expected->sgl_entries, expected->n_sgl_entries, + sizeof(expected->sgl_entries[0]), fts_ts_sgl_cmp, NULL); + + for (idx = 0; idx < expected->n_sgl_entries; idx++) { + struct gh_sgl_entry *left = &expected->sgl_entries[idx]; + struct gh_sgl_entry *right = &received->sgl_entries[idx]; + + if ((left->ipa_base != right->ipa_base) || + (left->size != right->size)) { + pr_err("sgl mismatch: left_base:%d right base:%d left size:%d right size:%d\n", + left->ipa_base, right->ipa_base, + left->size, right->size); + return -EINVAL; + } + } + return 0; +} + +static int fts_ts_vm_handle_vm_hardware(struct fts_ts_data *fts_data) +{ + int rc = 0; + + if (atomic_read(&fts_data->delayed_vm_probe_pending)) { + rc = fts_ts_probe_delayed(fts_data); + if (rc) { + pr_err(" Delayed probe failure on VM!\n"); + return rc; + } + atomic_set(&fts_data->delayed_vm_probe_pending, 0); + return rc; + } + + fts_irq_enable(); + fts_ts_trusted_touch_set_vm_state(fts_data, TVM_INTERRUPT_ENABLED); + return rc; +} + +static void fts_ts_trusted_touch_tvm_vm_mode_enable(struct fts_ts_data *fts_data) +{ + + struct gh_sgl_desc *sgl_desc, *expected_sgl_desc; + struct gh_acl_desc *acl_desc; + struct irq_data *irq_data; + int rc = 0; + int irq = 0; + + if (fts_ts_trusted_touch_get_vm_state(fts_data) != TVM_ALL_RESOURCES_LENT_NOTIFIED) { + pr_err("All lend notifications not received\n"); + fts_ts_trusted_touch_event_notify(fts_data, + TRUSTED_TOUCH_EVENT_NOTIFICATIONS_PENDING); + return; + } + + acl_desc = fts_ts_vm_get_acl(GH_TRUSTED_VM); + if (IS_ERR(acl_desc)) { + pr_err("failed to populated acl data:rc=%d\n", + PTR_ERR(acl_desc)); + goto accept_fail; + } + + sgl_desc = gh_rm_mem_accept(fts_data->vm_info->vm_mem_handle, + GH_RM_MEM_TYPE_IO, + GH_RM_TRANS_TYPE_LEND, + GH_RM_MEM_ACCEPT_VALIDATE_ACL_ATTRS | + GH_RM_MEM_ACCEPT_VALIDATE_LABEL | + GH_RM_MEM_ACCEPT_DONE, TRUSTED_TOUCH_MEM_LABEL, + acl_desc, NULL, NULL, 0); + if (IS_ERR_OR_NULL(sgl_desc)) { + pr_err("failed to do mem accept :rc=%d\n", + PTR_ERR(sgl_desc)); + goto acl_fail; + } + fts_ts_trusted_touch_set_vm_state(fts_data, TVM_IOMEM_ACCEPTED); + + /* Initiate session on tvm */ + if (fts_data->bus_type == BUS_TYPE_I2C) + rc = pm_runtime_get_sync(fts_data->client->adapter->dev.parent); + else + rc = pm_runtime_get_sync(fts_data->spi->master->dev.parent); + + if (rc < 0) { + pr_err("failed to get sync rc:%d\n", rc); + goto acl_fail; + } + fts_ts_trusted_touch_set_vm_state(fts_data, TVM_I2C_SESSION_ACQUIRED); + + expected_sgl_desc = fts_ts_vm_get_sgl(fts_data->vm_info); + if (fts_ts_vm_compare_sgl_desc(expected_sgl_desc, sgl_desc)) { + pr_err("IO sg list does not match\n"); + goto sgl_cmp_fail; + } + + kfree(expected_sgl_desc); + kfree(acl_desc); + + irq = gh_irq_accept(fts_data->vm_info->irq_label, -1, IRQ_TYPE_EDGE_RISING); + fts_trusted_touch_intr_gpio_toggle(fts_data, false); + if (irq < 0) { + pr_err("failed to accept irq\n"); + goto accept_fail; + } + fts_ts_trusted_touch_set_vm_state(fts_data, TVM_IRQ_ACCEPTED); + + + irq_data = irq_get_irq_data(irq); + if (!irq_data) { + pr_err("Invalid irq data for trusted touch\n"); + goto accept_fail; + } + if (!irq_data->hwirq) { + pr_err("Invalid irq in irq data\n"); + goto accept_fail; + } + if (irq_data->hwirq != fts_data->vm_info->hw_irq) { + pr_err("Invalid irq lent\n"); + goto accept_fail; + } + + pr_debug("irq:returned from accept:%d\n", irq); + fts_data->irq = irq; + + rc = fts_ts_vm_handle_vm_hardware(fts_data); + if (rc) { + pr_err(" Delayed probe failure on VM!\n"); + goto accept_fail; + } + atomic_set(&fts_data->trusted_touch_enabled, 1); + pr_info("trusted touch enabled\n"); + + return; +sgl_cmp_fail: + kfree(expected_sgl_desc); +acl_fail: + kfree(acl_desc); +accept_fail: + fts_ts_trusted_touch_abort_handler(fts_data, + TRUSTED_TOUCH_EVENT_ACCEPT_FAILURE); +} +static void fts_ts_vm_irq_on_lend_callback(void *data, + unsigned long notif_type, + enum gh_irq_label label) +{ + struct fts_ts_data *fts_data = data; + + pr_debug("received irq lend request for label:%d\n", label); + if (fts_ts_trusted_touch_get_vm_state(fts_data) == TVM_IOMEM_LENT_NOTIFIED) + fts_ts_trusted_touch_set_vm_state(fts_data, TVM_ALL_RESOURCES_LENT_NOTIFIED); + else + fts_ts_trusted_touch_set_vm_state(fts_data, TVM_IRQ_LENT_NOTIFIED); +} + +static void fts_ts_vm_mem_on_lend_handler(enum gh_mem_notifier_tag tag, + unsigned long notif_type, void *entry_data, void *notif_msg) +{ + struct gh_rm_notif_mem_shared_payload *payload; + struct trusted_touch_vm_info *vm_info; + struct fts_ts_data *fts_data; + + fts_data = (struct fts_ts_data *)entry_data; + vm_info = fts_data->vm_info; + if (!vm_info) { + pr_err("Invalid vm_info\n"); + return; + } + + if (notif_type != GH_RM_NOTIF_MEM_SHARED || + tag != vm_info->mem_tag) { + pr_err("Invalid command passed from rm\n"); + return; + } + + if (!entry_data || !notif_msg) { + pr_err("Invalid entry data passed from rm\n"); + return; + } + + + payload = (struct gh_rm_notif_mem_shared_payload *)notif_msg; + if (payload->trans_type != GH_RM_TRANS_TYPE_LEND || + payload->label != TRUSTED_TOUCH_MEM_LABEL) { + pr_err("Invalid label or transaction type\n"); + return; + } + + vm_info->vm_mem_handle = payload->mem_handle; + pr_debug("received mem lend request with handle:%d\n", + vm_info->vm_mem_handle); + if (fts_ts_trusted_touch_get_vm_state(fts_data) == TVM_IRQ_LENT_NOTIFIED) + fts_ts_trusted_touch_set_vm_state(fts_data, TVM_ALL_RESOURCES_LENT_NOTIFIED); + else + fts_ts_trusted_touch_set_vm_state(fts_data, TVM_IOMEM_LENT_NOTIFIED); +} + +static int fts_ts_vm_mem_release(struct fts_ts_data *fts_data) +{ + int rc = 0; + + if (!fts_data->vm_info->vm_mem_handle) { + pr_err("Invalid memory handle\n"); + return -EINVAL; + } + + rc = gh_rm_mem_release(fts_data->vm_info->vm_mem_handle, 0); + if (rc) + pr_err("VM mem release failed: rc=%d\n", rc); + + rc = gh_rm_mem_notify(fts_data->vm_info->vm_mem_handle, + GH_RM_MEM_NOTIFY_OWNER_RELEASED, + fts_data->vm_info->mem_tag, 0); + if (rc) + pr_err("Failed to notify mem release to PVM: rc=%d\n"); + pr_debug("vm mem release succeded\n"); + + fts_data->vm_info->vm_mem_handle = 0; + return rc; +} + +static void fts_ts_trusted_touch_tvm_vm_mode_disable(struct fts_ts_data *fts_data) +{ + int rc = 0; + + if (atomic_read(&fts_data->trusted_touch_abort_status)) { + fts_ts_trusted_touch_abort_tvm(fts_data); + return; + } + + fts_irq_disable(); + fts_ts_trusted_touch_set_vm_state(fts_data, TVM_INTERRUPT_DISABLED); + + rc = gh_irq_release(fts_data->vm_info->irq_label); + if (rc) { + pr_err("Failed to release irq rc:%d\n", rc); + goto error; + } else { + fts_ts_trusted_touch_set_vm_state(fts_data, TVM_IRQ_RELEASED); + } + rc = gh_irq_release_notify(fts_data->vm_info->irq_label); + if (rc) + pr_err("Failed to notify release irq rc:%d\n", rc); + + pr_debug("vm irq release succeded\n"); + + fts_release_all_finger(); + + if (fts_data->bus_type == BUS_TYPE_I2C) + pm_runtime_put_sync(fts_data->client->adapter->dev.parent); + else + pm_runtime_put_sync(fts_data->spi->master->dev.parent); + + fts_ts_trusted_touch_set_vm_state(fts_data, TVM_I2C_SESSION_RELEASED); + rc = fts_ts_vm_mem_release(fts_data); + if (rc) { + pr_err("Failed to release mem rc:%d\n", rc); + goto error; + } else { + fts_ts_trusted_touch_set_vm_state(fts_data, TVM_IOMEM_RELEASED); + } + fts_ts_trusted_touch_set_vm_state(fts_data, TRUSTED_TOUCH_TVM_INIT); + atomic_set(&fts_data->trusted_touch_enabled, 0); + pr_info("trusted touch disabled\n"); + return; +error: + fts_ts_trusted_touch_abort_handler(fts_data, + TRUSTED_TOUCH_EVENT_RELEASE_FAILURE); +} + +int fts_ts_handle_trusted_touch_tvm(struct fts_ts_data *fts_data, int value) +{ + int err = 0; + + switch (value) { + case 0: + if ((atomic_read(&fts_data->trusted_touch_enabled) == 0) && + (atomic_read(&fts_data->trusted_touch_abort_status) == 0)) { + pr_err("Trusted touch is already disabled\n"); + break; + } + if (atomic_read(&fts_data->trusted_touch_mode) == + TRUSTED_TOUCH_VM_MODE) { + fts_ts_trusted_touch_tvm_vm_mode_disable(fts_data); + } else { + pr_err("Unsupported trusted touch mode\n"); + } + break; + + case 1: + if (atomic_read(&fts_data->trusted_touch_enabled)) { + pr_err("Trusted touch usecase underway\n"); + err = -EBUSY; + break; + } + if (atomic_read(&fts_data->trusted_touch_mode) == + TRUSTED_TOUCH_VM_MODE) { + fts_ts_trusted_touch_tvm_vm_mode_enable(fts_data); + } else { + pr_err("Unsupported trusted touch mode\n"); + } + break; + + default: + FTS_ERROR("unsupported value: %lu\n", value); + err = -EINVAL; + break; + } + + return err; +} + +static void fts_ts_trusted_touch_abort_tvm(struct fts_ts_data *fts_data) +{ + int rc = 0; + int vm_state = fts_ts_trusted_touch_get_vm_state(fts_data); + + if (vm_state >= TRUSTED_TOUCH_TVM_STATE_MAX) { + pr_err("invalid tvm driver state: %d\n", vm_state); + return; + } + + switch (vm_state) { + case TVM_INTERRUPT_ENABLED: + fts_irq_disable(); + case TVM_IRQ_ACCEPTED: + case TVM_INTERRUPT_DISABLED: + rc = gh_irq_release(fts_data->vm_info->irq_label); + if (rc) + pr_err("Failed to release irq rc:%d\n", rc); + rc = gh_irq_release_notify(fts_data->vm_info->irq_label); + if (rc) + pr_err("Failed to notify irq release rc:%d\n", rc); + case TVM_I2C_SESSION_ACQUIRED: + case TVM_IOMEM_ACCEPTED: + case TVM_IRQ_RELEASED: + fts_release_all_finger(); + if (fts_data->bus_type == BUS_TYPE_I2C) + pm_runtime_put_sync(fts_data->client->adapter->dev.parent); + else + pm_runtime_put_sync(fts_data->spi->master->dev.parent); + case TVM_I2C_SESSION_RELEASED: + rc = fts_ts_vm_mem_release(fts_data); + if (rc) + pr_err("Failed to release mem rc:%d\n", rc); + case TVM_IOMEM_RELEASED: + case TVM_ALL_RESOURCES_LENT_NOTIFIED: + case TRUSTED_TOUCH_TVM_INIT: + case TVM_IRQ_LENT_NOTIFIED: + case TVM_IOMEM_LENT_NOTIFIED: + atomic_set(&fts_data->trusted_touch_enabled, 0); + } + + atomic_set(&fts_data->trusted_touch_abort_status, 0); + fts_ts_trusted_touch_set_vm_state(fts_data, TRUSTED_TOUCH_TVM_INIT); +} + +#else + +static void fts_ts_bus_put(struct fts_ts_data *fts_data); + +static void fts_ts_trusted_touch_abort_pvm(struct fts_ts_data *fts_data) +{ + int rc = 0; + int vm_state = fts_ts_trusted_touch_get_vm_state(fts_data); + + if (vm_state >= TRUSTED_TOUCH_PVM_STATE_MAX) { + pr_err("Invalid driver state: %d\n", vm_state); + return; + } + + switch (vm_state) { + case PVM_IRQ_RELEASE_NOTIFIED: + case PVM_ALL_RESOURCES_RELEASE_NOTIFIED: + case PVM_IRQ_LENT: + case PVM_IRQ_LENT_NOTIFIED: + rc = gh_irq_reclaim(fts_data->vm_info->irq_label); + if (rc) + pr_err("failed to reclaim irq on pvm rc:%d\n", rc); + case PVM_IRQ_RECLAIMED: + case PVM_IOMEM_LENT: + case PVM_IOMEM_LENT_NOTIFIED: + case PVM_IOMEM_RELEASE_NOTIFIED: + rc = gh_rm_mem_reclaim(fts_data->vm_info->vm_mem_handle, 0); + if (rc) + pr_err("failed to reclaim iomem on pvm rc:%d\n", rc); + fts_data->vm_info->vm_mem_handle = 0; + case PVM_IOMEM_RECLAIMED: + case PVM_INTERRUPT_DISABLED: + fts_irq_enable(); + case PVM_I2C_RESOURCE_ACQUIRED: + case PVM_INTERRUPT_ENABLED: + fts_ts_bus_put(fts_data); + case TRUSTED_TOUCH_PVM_INIT: + case PVM_I2C_RESOURCE_RELEASED: + atomic_set(&fts_data->trusted_touch_enabled, 0); + atomic_set(&fts_data->trusted_touch_transition, 0); + } + + atomic_set(&fts_data->trusted_touch_abort_status, 0); + + fts_ts_trusted_touch_set_vm_state(fts_data, TRUSTED_TOUCH_PVM_INIT); +} + +static int fts_ts_clk_prepare_enable(struct fts_ts_data *fts_data) +{ + int ret; + + ret = clk_prepare_enable(fts_data->iface_clk); + if (ret) { + FTS_ERROR("error on clk_prepare_enable(iface_clk):%d\n", ret); + return ret; + } + + ret = clk_prepare_enable(fts_data->core_clk); + if (ret) { + clk_disable_unprepare(fts_data->iface_clk); + FTS_ERROR("error clk_prepare_enable(core_clk):%d\n", ret); + } + return ret; +} + +static void fts_ts_clk_disable_unprepare(struct fts_ts_data *fts_data) +{ + clk_disable_unprepare(fts_data->core_clk); + clk_disable_unprepare(fts_data->iface_clk); +} + +static int fts_ts_bus_get(struct fts_ts_data *fts_data) +{ + int rc = 0; + struct device *dev = NULL; + + cancel_work_sync(&fts_data->resume_work); + reinit_completion(&fts_data->trusted_touch_powerdown); + fts_ts_enable_reg(fts_data, true); + + if (fts_data->bus_type == BUS_TYPE_I2C) + dev = fts_data->client->adapter->dev.parent; + else + dev = fts_data->spi->master->dev.parent; + + mutex_lock(&fts_data->fts_clk_io_ctrl_mutex); + rc = pm_runtime_get_sync(dev); + if (rc >= 0 && fts_data->core_clk != NULL && + fts_data->iface_clk != NULL) { + rc = fts_ts_clk_prepare_enable(fts_data); + if (rc) + pm_runtime_put_sync(dev); + } + + mutex_unlock(&fts_data->fts_clk_io_ctrl_mutex); + return rc; +} + +static void fts_ts_bus_put(struct fts_ts_data *fts_data) +{ + struct device *dev = NULL; + + if (fts_data->bus_type == BUS_TYPE_I2C) + dev = fts_data->client->adapter->dev.parent; + else + dev = fts_data->spi->master->dev.parent; + + mutex_lock(&fts_data->fts_clk_io_ctrl_mutex); + if (fts_data->core_clk != NULL && fts_data->iface_clk != NULL) + fts_ts_clk_disable_unprepare(fts_data); + pm_runtime_put_sync(dev); + mutex_unlock(&fts_data->fts_clk_io_ctrl_mutex); + complete(&fts_data->trusted_touch_powerdown); + fts_ts_enable_reg(fts_data, false); +} + +static struct gh_notify_vmid_desc *fts_ts_vm_get_vmid(gh_vmid_t vmid) +{ + struct gh_notify_vmid_desc *vmid_desc; + + vmid_desc = kzalloc(offsetof(struct gh_notify_vmid_desc, + vmid_entries[1]), GFP_KERNEL); + if (!vmid_desc) + return ERR_PTR(ENOMEM); + + vmid_desc->n_vmid_entries = 1; + vmid_desc->vmid_entries[0].vmid = vmid; + return vmid_desc; +} + +static void fts_trusted_touch_pvm_vm_mode_disable(struct fts_ts_data *fts_data) +{ + int rc = 0; + + atomic_set(&fts_data->trusted_touch_transition, 1); + + if (atomic_read(&fts_data->trusted_touch_abort_status)) { + fts_ts_trusted_touch_abort_pvm(fts_data); + return; + } + + if (fts_ts_trusted_touch_get_vm_state(fts_data) != PVM_ALL_RESOURCES_RELEASE_NOTIFIED) + pr_err("all release notifications are not received yet\n"); + + rc = gh_rm_mem_reclaim(fts_data->vm_info->vm_mem_handle, 0); + if (rc) { + pr_err("Trusted touch VM mem reclaim failed rc:%d\n", rc); + goto error; + } + fts_ts_trusted_touch_set_vm_state(fts_data, PVM_IOMEM_RECLAIMED); + fts_data->vm_info->vm_mem_handle = 0; + pr_debug("vm mem reclaim succeded!\n"); + + rc = gh_irq_reclaim(fts_data->vm_info->irq_label); + if (rc) { + pr_err("failed to reclaim irq on pvm rc:%d\n", rc); + goto error; + } + fts_ts_trusted_touch_set_vm_state(fts_data, PVM_IRQ_RECLAIMED); + pr_debug("vm irq reclaim succeded!\n"); + + fts_irq_enable(); + fts_ts_trusted_touch_set_vm_state(fts_data, PVM_INTERRUPT_ENABLED); + fts_ts_bus_put(fts_data); + atomic_set(&fts_data->trusted_touch_transition, 0); + fts_ts_trusted_touch_set_vm_state(fts_data, PVM_I2C_RESOURCE_RELEASED); + fts_ts_trusted_touch_set_vm_state(fts_data, TRUSTED_TOUCH_PVM_INIT); + atomic_set(&fts_data->trusted_touch_enabled, 0); + pr_info("trusted touch disabled\n"); + return; +error: + fts_ts_trusted_touch_abort_handler(fts_data, + TRUSTED_TOUCH_EVENT_RECLAIM_FAILURE); +} + +static void fts_ts_vm_irq_on_release_callback(void *data, + unsigned long notif_type, + enum gh_irq_label label) +{ + struct fts_ts_data *fts_data = data; + + if (notif_type != GH_RM_NOTIF_VM_IRQ_RELEASED) { + pr_err("invalid notification type\n"); + return; + } + + if (fts_ts_trusted_touch_get_vm_state(fts_data) == PVM_IOMEM_RELEASE_NOTIFIED) + fts_ts_trusted_touch_set_vm_state(fts_data, PVM_ALL_RESOURCES_RELEASE_NOTIFIED); + else + fts_ts_trusted_touch_set_vm_state(fts_data, PVM_IRQ_RELEASE_NOTIFIED); +} + +static void fts_ts_vm_mem_on_release_handler(enum gh_mem_notifier_tag tag, + unsigned long notif_type, void *entry_data, void *notif_msg) +{ + struct gh_rm_notif_mem_released_payload *release_payload; + struct trusted_touch_vm_info *vm_info; + struct fts_ts_data *fts_data; + + fts_data = (struct fts_ts_data *)entry_data; + vm_info = fts_data->vm_info; + if (!vm_info) { + pr_err(" Invalid vm_info\n"); + return; + } + + if (notif_type != GH_RM_NOTIF_MEM_RELEASED) { + pr_err(" Invalid notification type\n"); + return; + } + + if (tag != vm_info->mem_tag) { + pr_err(" Invalid tag\n"); + return; + } + + if (!entry_data || !notif_msg) { + pr_err(" Invalid data or notification message\n"); + return; + } + + release_payload = (struct gh_rm_notif_mem_released_payload *)notif_msg; + if (release_payload->mem_handle != vm_info->vm_mem_handle) { + pr_err("Invalid mem handle detected\n"); + return; + } + + if (fts_ts_trusted_touch_get_vm_state(fts_data) == PVM_IRQ_RELEASE_NOTIFIED) + fts_ts_trusted_touch_set_vm_state(fts_data, PVM_ALL_RESOURCES_RELEASE_NOTIFIED); + else + fts_ts_trusted_touch_set_vm_state(fts_data, PVM_IOMEM_RELEASE_NOTIFIED); +} + +static int fts_ts_vm_mem_lend(struct fts_ts_data *fts_data) +{ + struct gh_acl_desc *acl_desc; + struct gh_sgl_desc *sgl_desc; + struct gh_notify_vmid_desc *vmid_desc; + gh_memparcel_handle_t mem_handle; + gh_vmid_t trusted_vmid; + int rc = 0; + + acl_desc = fts_ts_vm_get_acl(GH_TRUSTED_VM); + if (IS_ERR(acl_desc)) { + pr_err("Failed to get acl of IO memories for Trusted touch\n"); + rc = PTR_ERR(acl_desc); + return rc; + } + + sgl_desc = fts_ts_vm_get_sgl(fts_data->vm_info); + if (IS_ERR(sgl_desc)) { + pr_err("Failed to get sgl of IO memories for Trusted touch\n"); + rc = PTR_ERR(sgl_desc); + goto sgl_error; + } + + rc = gh_rm_mem_lend(GH_RM_MEM_TYPE_IO, 0, TRUSTED_TOUCH_MEM_LABEL, + acl_desc, sgl_desc, NULL, &mem_handle); + if (rc) { + pr_err("Failed to lend IO memories for Trusted touch rc:%d\n", + rc); + goto error; + } + + pr_info("vm mem lend succeded\n"); + + fts_ts_trusted_touch_set_vm_state(fts_data, PVM_IOMEM_LENT); + + gh_rm_get_vmid(GH_TRUSTED_VM, &trusted_vmid); + + vmid_desc = fts_ts_vm_get_vmid(trusted_vmid); + + rc = gh_rm_mem_notify(mem_handle, GH_RM_MEM_NOTIFY_RECIPIENT_SHARED, + fts_data->vm_info->mem_tag, vmid_desc); + if (rc) { + pr_err("Failed to notify mem lend to hypervisor rc:%d\n", rc); + goto vmid_error; + } + + fts_ts_trusted_touch_set_vm_state(fts_data, PVM_IOMEM_LENT_NOTIFIED); + + fts_data->vm_info->vm_mem_handle = mem_handle; +vmid_error: + kfree(vmid_desc); +error: + kfree(sgl_desc); +sgl_error: + kfree(acl_desc); + + return rc; +} + +static int fts_ts_trusted_touch_pvm_vm_mode_enable(struct fts_ts_data *fts_data) +{ + int rc = 0; + struct trusted_touch_vm_info *vm_info = fts_data->vm_info; + + atomic_set(&fts_data->trusted_touch_transition, 1); + mutex_lock(&fts_data->transition_lock); + + if (fts_data->suspended) { + FTS_ERROR("Invalid power state for operation\n"); + atomic_set(&fts_data->trusted_touch_transition, 0); + rc = -EPERM; + goto error; + } + + /* i2c session start and resource acquire */ + if (fts_ts_bus_get(fts_data) < 0) { + FTS_ERROR("fts_ts_bus_get failed\n"); + rc = -EIO; + goto error; + } + + fts_ts_trusted_touch_set_vm_state(fts_data, PVM_I2C_RESOURCE_ACQUIRED); + /* flush pending interurpts from FIFO */ + fts_irq_disable(); + fts_ts_trusted_touch_set_vm_state(fts_data, PVM_INTERRUPT_DISABLED); + fts_release_all_finger(); + + rc = fts_ts_vm_mem_lend(fts_data); + if (rc) { + pr_err("Failed to lend memory\n"); + goto abort_handler; + } + pr_debug("vm mem lend succeded\n"); + rc = gh_irq_lend_v2(vm_info->irq_label, vm_info->vm_name, + fts_data->irq, &fts_ts_vm_irq_on_release_callback, fts_data); + if (rc) { + pr_err("Failed to lend irq\n"); + goto abort_handler; + } + + pr_debug("vm irq lend succeded for irq:%d\n", fts_data->irq); + fts_ts_trusted_touch_set_vm_state(fts_data, PVM_IRQ_LENT); + + rc = gh_irq_lend_notify(vm_info->irq_label); + if (rc) { + pr_err("Failed to notify irq\n"); + goto abort_handler; + } + fts_ts_trusted_touch_set_vm_state(fts_data, PVM_IRQ_LENT_NOTIFIED); + + mutex_unlock(&fts_data->transition_lock); + atomic_set(&fts_data->trusted_touch_transition, 0); + atomic_set(&fts_data->trusted_touch_enabled, 1); + pr_info("trusted touch enabled\n"); + return rc; + +abort_handler: + fts_ts_trusted_touch_abort_handler(fts_data, TRUSTED_TOUCH_EVENT_LEND_FAILURE); + +error: + mutex_unlock(&fts_data->transition_lock); + return rc; +} + +int fts_ts_handle_trusted_touch_pvm(struct fts_ts_data *fts_data, int value) +{ + int err = 0; + + switch (value) { + case 0: + if (atomic_read(&fts_data->trusted_touch_enabled) == 0 && + (atomic_read(&fts_data->trusted_touch_abort_status) == 0)) { + pr_err("Trusted touch is already disabled\n"); + break; + } + if (atomic_read(&fts_data->trusted_touch_mode) == + TRUSTED_TOUCH_VM_MODE) { + fts_trusted_touch_pvm_vm_mode_disable(fts_data); + } else { + pr_err("Unsupported trusted touch mode\n"); + } + break; + + case 1: + if (atomic_read(&fts_data->trusted_touch_enabled)) { + pr_err("Trusted touch usecase underway\n"); + err = -EBUSY; + break; + } + if (atomic_read(&fts_data->trusted_touch_mode) == + TRUSTED_TOUCH_VM_MODE) { + err = fts_ts_trusted_touch_pvm_vm_mode_enable(fts_data); + } else { + pr_err("Unsupported trusted touch mode\n"); + } + break; + + default: + FTS_ERROR("unsupported value: %lu\n", value); + err = -EINVAL; + break; + } + return err; +} + +#endif + +static void fts_ts_trusted_touch_event_notify(struct fts_ts_data *fts_data, int event) +{ + atomic_set(&fts_data->trusted_touch_event, event); + sysfs_notify(&fts_data->dev->kobj, NULL, "trusted_touch_event"); +} + +static void fts_ts_trusted_touch_abort_handler(struct fts_ts_data *fts_data, int error) +{ + atomic_set(&fts_data->trusted_touch_abort_status, error); + pr_err("TUI session aborted with failure:%d\n", error); + fts_ts_trusted_touch_event_notify(fts_data, error); +#ifdef CONFIG_ARCH_QTI_VM + pr_err("Resetting touch controller\n"); + if (fts_ts_trusted_touch_get_vm_state(fts_data) >= TVM_IOMEM_ACCEPTED && + error == TRUSTED_TOUCH_EVENT_I2C_FAILURE) { + pr_err("Resetting touch controller\n"); + fts_ts_trusted_touch_reset_gpio_toggle(fts_data); + } +#endif +} + +static int fts_ts_vm_init(struct fts_ts_data *fts_data) +{ + int rc = 0; + struct trusted_touch_vm_info *vm_info; + void *mem_cookie; + + rc = fts_ts_populate_vm_info(fts_data); + if (rc) { + pr_err("Cannot setup vm pipeline\n"); + rc = -EINVAL; + goto fail; + } + + vm_info = fts_data->vm_info; +#ifdef CONFIG_ARCH_QTI_VM + mem_cookie = gh_mem_notifier_register(vm_info->mem_tag, + fts_ts_vm_mem_on_lend_handler, fts_data); + if (!mem_cookie) { + pr_err("Failed to register on lend mem notifier\n"); + rc = -EINVAL; + goto init_fail; + } + vm_info->mem_cookie = mem_cookie; + rc = gh_irq_wait_for_lend_v2(vm_info->irq_label, GH_PRIMARY_VM, + &fts_ts_vm_irq_on_lend_callback, fts_data); + fts_ts_trusted_touch_set_vm_state(fts_data, TRUSTED_TOUCH_TVM_INIT); +#else + mem_cookie = gh_mem_notifier_register(vm_info->mem_tag, + fts_ts_vm_mem_on_release_handler, fts_data); + if (!mem_cookie) { + pr_err("Failed to register on release mem notifier\n"); + rc = -EINVAL; + goto init_fail; + } + vm_info->mem_cookie = mem_cookie; + fts_ts_trusted_touch_set_vm_state(fts_data, TRUSTED_TOUCH_PVM_INIT); +#endif + return rc; +init_fail: + fts_ts_vm_deinit(fts_data); +fail: + return rc; +} + +static void fts_ts_dt_parse_trusted_touch_info(struct fts_ts_data *fts_data) +{ + struct device_node *np = fts_data->dev->of_node; + int rc = 0; + const char *selection; + const char *environment; + + rc = of_property_read_string(np, "focaltech,trusted-touch-mode", + &selection); + if (rc) { + dev_warn(fts_data->dev, + "%s: No trusted touch mode selection made\n", __func__); + atomic_set(&fts_data->trusted_touch_mode, + TRUSTED_TOUCH_MODE_NONE); + return; + } + + if (!strcmp(selection, "vm_mode")) { + atomic_set(&fts_data->trusted_touch_mode, + TRUSTED_TOUCH_VM_MODE); + pr_err("Selected trusted touch mode to VM mode\n"); + } else { + atomic_set(&fts_data->trusted_touch_mode, + TRUSTED_TOUCH_MODE_NONE); + pr_err("Invalid trusted_touch mode\n"); + } + + rc = of_property_read_string(np, "focaltech,touch-environment", + &environment); + if (rc) { + dev_warn(fts_data->dev, + "%s: No trusted touch mode environment\n", __func__); + } + fts_data->touch_environment = environment; + pr_err("Trusted touch environment:%s\n", + fts_data->touch_environment); +} + +static void fts_ts_trusted_touch_init(struct fts_ts_data *fts_data) +{ + int rc = 0; + + atomic_set(&fts_data->trusted_touch_initialized, 0); + fts_ts_dt_parse_trusted_touch_info(fts_data); + + if (atomic_read(&fts_data->trusted_touch_mode) == + TRUSTED_TOUCH_MODE_NONE) + return; + + init_completion(&fts_data->trusted_touch_powerdown); + + /* Get clocks */ + fts_data->core_clk = devm_clk_get(fts_data->dev->parent, + "m-ahb"); + if (IS_ERR(fts_data->core_clk)) { + fts_data->core_clk = NULL; + dev_warn(fts_data->dev, + "%s: core_clk is not defined\n", __func__); + } + + fts_data->iface_clk = devm_clk_get(fts_data->dev->parent, + "se-clk"); + if (IS_ERR(fts_data->iface_clk)) { + fts_data->iface_clk = NULL; + dev_warn(fts_data->dev, + "%s: iface_clk is not defined\n", __func__); + } + + if (atomic_read(&fts_data->trusted_touch_mode) == + TRUSTED_TOUCH_VM_MODE) { + rc = fts_ts_vm_init(fts_data); + if (rc) + pr_err("Failed to init VM\n"); + } + atomic_set(&fts_data->trusted_touch_initialized, 1); +} + +#endif + +/***************************************************************************** +* Name: fts_wait_tp_to_valid +* Brief: Read chip id until TP FW become valid(Timeout: TIMEOUT_READ_REG), +* need call when reset/power on/resume... +* Input: +* Output: +* Return: return 0 if tp valid, otherwise return error code +*****************************************************************************/ +int fts_wait_tp_to_valid(void) +{ + int ret = 0; + int cnt = 0; + u8 idh = 0; + u8 idl = 0; + u8 chip_idh = fts_data->ic_info.ids.chip_idh; + u8 chip_idl = fts_data->ic_info.ids.chip_idl; + + do { + ret = fts_read_reg(FTS_REG_CHIP_ID, &idh); + ret = fts_read_reg(FTS_REG_CHIP_ID2, &idl); + if ((ret < 0) || (idh != chip_idh) || (idl != chip_idl)) { + FTS_DEBUG("TP Not Ready,ReadData:0x%02x%02x", idh, idl); + } else if ((idh == chip_idh) && (idl == chip_idl)) { + FTS_INFO("TP Ready,Device ID:0x%02x%02x", idh, idl); + return 0; + } + cnt++; + msleep(INTERVAL_READ_REG); + } while ((cnt * INTERVAL_READ_REG) < TIMEOUT_READ_REG); + + return -EIO; +} + +/***************************************************************************** +* Name: fts_tp_state_recovery +* Brief: Need execute this function when reset +* Input: +* Output: +* Return: +*****************************************************************************/ +void fts_tp_state_recovery(struct fts_ts_data *ts_data) +{ + FTS_FUNC_ENTER(); + /* wait tp stable */ + fts_wait_tp_to_valid(); + /* recover TP charger state 0x8B */ + /* recover TP glove state 0xC0 */ + /* recover TP cover state 0xC1 */ + fts_ex_mode_recovery(ts_data); + /* recover TP gesture state 0xD0 */ + fts_gesture_recovery(ts_data); + FTS_FUNC_EXIT(); +} + +int fts_reset_proc(int hdelayms) +{ + FTS_DEBUG("tp reset"); + gpio_direction_output(fts_data->pdata->reset_gpio, 0); + msleep(1); + gpio_direction_output(fts_data->pdata->reset_gpio, 1); + if (hdelayms) { + msleep(hdelayms); + } + + return 0; +} + +void fts_irq_disable(void) +{ + unsigned long irqflags; + + FTS_FUNC_ENTER(); + spin_lock_irqsave(&fts_data->irq_lock, irqflags); + + if (!fts_data->irq_disabled) { +#ifdef CONFIG_FTS_TRUSTED_TOUCH + if (atomic_read(&fts_data->trusted_touch_transition)) + disable_irq_wake(fts_data->irq); + else + disable_irq_nosync(fts_data->irq); +#else + disable_irq_nosync(fts_data->irq); +#endif + fts_data->irq_disabled = true; + } + + spin_unlock_irqrestore(&fts_data->irq_lock, irqflags); + FTS_FUNC_EXIT(); +} + +void fts_irq_enable(void) +{ + unsigned long irqflags = 0; + + FTS_FUNC_ENTER(); + spin_lock_irqsave(&fts_data->irq_lock, irqflags); + + if (fts_data->irq_disabled) { +#ifdef CONFIG_FTS_TRUSTED_TOUCH + if (atomic_read(&fts_data->trusted_touch_transition)) + enable_irq_wake(fts_data->irq); + else + enable_irq(fts_data->irq); +#else + enable_irq(fts_data->irq); +#endif + fts_data->irq_disabled = false; + } + + spin_unlock_irqrestore(&fts_data->irq_lock, irqflags); + FTS_FUNC_EXIT(); +} + +void fts_hid2std(void) +{ + int ret = 0; + u8 buf[3] = {0xEB, 0xAA, 0x09}; + + if (fts_data->bus_type != BUS_TYPE_I2C) + return; + + ret = fts_write(buf, 3); + if (ret < 0) { + FTS_ERROR("hid2std cmd write fail"); + return; + } + + msleep(20); + buf[0] = buf[1] = buf[2] = 0; + ret = fts_read(NULL, 0, buf, 3); + if (ret < 0) + FTS_ERROR("hid2std cmd read fail"); + else if ((buf[0] == 0xEB) && (buf[1] == 0xAA) && (buf[2] == 0x08)) + FTS_DEBUG("hidi2c change to stdi2c successful"); + else + FTS_DEBUG("hidi2c change to stdi2c not support or fail"); + +} + +static int fts_get_chip_types( + struct fts_ts_data *ts_data, + u8 id_h, u8 id_l, bool fw_valid) +{ + int i = 0; + u32 ctype_entries = sizeof(ctype) / sizeof(struct ft_chip_t); + + if ((0x0 == id_h) || (0x0 == id_l)) { + FTS_ERROR("id_h/id_l is 0"); + return -EINVAL; + } + + FTS_DEBUG("verify id:0x%02x%02x", id_h, id_l); + for (i = 0; i < ctype_entries; i++) { + if (VALID == fw_valid) { + if ((id_h == ctype[i].chip_idh) && (id_l == ctype[i].chip_idl)) + break; + } else { + if (((id_h == ctype[i].rom_idh) && (id_l == ctype[i].rom_idl)) + || ((id_h == ctype[i].pb_idh) && (id_l == ctype[i].pb_idl)) + || ((id_h == ctype[i].bl_idh) && (id_l == ctype[i].bl_idl))) + break; + } + } + + if (i >= ctype_entries) + return -ENODATA; + + ts_data->ic_info.ids = ctype[i]; + return 0; +} + +static int fts_read_bootid(struct fts_ts_data *ts_data, u8 *id) +{ + int ret = 0; + u8 chip_id[2] = { 0 }; + u8 id_cmd[4] = { 0 }; + u32 id_cmd_len = 0; + + id_cmd[0] = FTS_CMD_START1; + id_cmd[1] = FTS_CMD_START2; + ret = fts_write(id_cmd, 2); + if (ret < 0) { + FTS_ERROR("start cmd write fail"); + return ret; + } + + msleep(FTS_CMD_START_DELAY); + id_cmd[0] = FTS_CMD_READ_ID; + id_cmd[1] = id_cmd[2] = id_cmd[3] = 0x00; + if (ts_data->ic_info.is_incell) + id_cmd_len = FTS_CMD_READ_ID_LEN_INCELL; + else + id_cmd_len = FTS_CMD_READ_ID_LEN; + ret = fts_read(id_cmd, id_cmd_len, chip_id, 2); + if ((ret < 0) || (0x0 == chip_id[0]) || (0x0 == chip_id[1])) { + FTS_ERROR("read boot id fail,read:0x%02x%02x", chip_id[0], chip_id[1]); + return -EIO; + } + + id[0] = chip_id[0]; + id[1] = chip_id[1]; + return 0; +} + +/***************************************************************************** +* Name: fts_get_ic_information +* Brief: read chip id to get ic information, after run the function, driver w- +* ill know which IC is it. +* If cant get the ic information, maybe not focaltech's touch IC, need +* unregister the driver +* Input: +* Output: +* Return: return 0 if get correct ic information, otherwise return error code +*****************************************************************************/ +static int fts_get_ic_information(struct fts_ts_data *ts_data) +{ + int ret = 0; + int cnt = 0; + u8 chip_id[2] = { 0 }; + u32 type = ts_data->pdata->type; + + ts_data->ic_info.is_incell = FTS_CHIP_IDC(type); + ts_data->ic_info.hid_supported = FTS_HID_SUPPORTTED(type); + + do { + ret = fts_read_reg(FTS_REG_CHIP_ID, &chip_id[0]); + ret = fts_read_reg(FTS_REG_CHIP_ID2, &chip_id[1]); + if ((ret < 0) || (0x0 == chip_id[0]) || (0x0 == chip_id[1])) { + FTS_DEBUG("i2c read invalid, read:0x%02x%02x", + chip_id[0], chip_id[1]); + } else { + ret = fts_get_chip_types(ts_data, chip_id[0], chip_id[1], VALID); + if (!ret) + break; + else + FTS_DEBUG("TP not ready, read:0x%02x%02x", + chip_id[0], chip_id[1]); + } + + cnt++; + msleep(INTERVAL_READ_REG); + } while ((cnt * INTERVAL_READ_REG) < TIMEOUT_READ_REG); + + if ((cnt * INTERVAL_READ_REG) >= TIMEOUT_READ_REG) { + FTS_INFO("fw is invalid, need read boot id"); + if (ts_data->ic_info.hid_supported) { + fts_hid2std(); + } + + ret = fts_read_bootid(ts_data, &chip_id[0]); + if (ret < 0) { + FTS_ERROR("read boot id fail"); + return ret; + } + + ret = fts_get_chip_types(ts_data, chip_id[0], chip_id[1], INVALID); + if (ret < 0) { + FTS_ERROR("can't get ic informaton"); + return ret; + } + } + + FTS_INFO("get ic information, chip id = 0x%02x%02x", + ts_data->ic_info.ids.chip_idh, ts_data->ic_info.ids.chip_idl); + + return 0; +} + +/***************************************************************************** +* Reprot related +*****************************************************************************/ +static void fts_show_touch_buffer(u8 *data, int datalen) +{ + int i = 0; + int count = 0; + char *tmpbuf = NULL; + + tmpbuf = kzalloc(1024, GFP_KERNEL); + if (!tmpbuf) { + FTS_ERROR("tmpbuf zalloc fail"); + return; + } + + for (i = 0; i < datalen; i++) { + count += snprintf(tmpbuf + count, 1024 - count, "%02X,", data[i]); + if (count >= 1024) + break; + } + FTS_DEBUG("point buffer:%s", tmpbuf); + + if (tmpbuf) { + kfree(tmpbuf); + tmpbuf = NULL; + } +} + +void fts_release_all_finger(void) +{ + struct input_dev *input_dev = fts_data->input_dev; +#if FTS_MT_PROTOCOL_B_EN + u32 finger_count = 0; + u32 max_touches = fts_data->pdata->max_touch_number; +#endif + + FTS_FUNC_ENTER(); + mutex_lock(&fts_data->report_mutex); +#if FTS_MT_PROTOCOL_B_EN + for (finger_count = 0; finger_count < max_touches; finger_count++) { + input_mt_slot(input_dev, finger_count); + input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, false); + } +#else + input_mt_sync(input_dev); +#endif + input_report_key(input_dev, BTN_TOUCH, 0); + input_sync(input_dev); + + fts_data->touchs = 0; + fts_data->key_state = 0; + mutex_unlock(&fts_data->report_mutex); + FTS_FUNC_EXIT(); +} + +/***************************************************************************** +* Name: fts_input_report_key +* Brief: process key events,need report key-event if key enable. +* if point's coordinate is in (x_dim-50,y_dim-50) ~ (x_dim+50,y_dim+50), +* need report it to key event. +* x_dim: parse from dts, means key x_coordinate, dimension:+-50 +* y_dim: parse from dts, means key y_coordinate, dimension:+-50 +* Input: +* Output: +* Return: return 0 if it's key event, otherwise return error code +*****************************************************************************/ +static int fts_input_report_key(struct fts_ts_data *data, int index) +{ + int i = 0; + int x = data->events[index].x; + int y = data->events[index].y; + int *x_dim = &data->pdata->key_x_coords[0]; + int *y_dim = &data->pdata->key_y_coords[0]; + + if (!data->pdata->have_key) { + return -EINVAL; + } + + for (i = 0; i < data->pdata->key_number; i++) { + if ((x >= x_dim[i] - FTS_KEY_DIM) && (x <= x_dim[i] + FTS_KEY_DIM) && + (y >= y_dim[i] - FTS_KEY_DIM) && (y <= y_dim[i] + FTS_KEY_DIM)) { + if (EVENT_DOWN(data->events[index].flag) + && !(data->key_state & (1 << i))) { + input_report_key(data->input_dev, data->pdata->keys[i], 1); + data->key_state |= (1 << i); + FTS_DEBUG("Key%d(%d,%d) DOWN!", i, x, y); + } else if (EVENT_UP(data->events[index].flag) + && (data->key_state & (1 << i))) { + input_report_key(data->input_dev, data->pdata->keys[i], 0); + data->key_state &= ~(1 << i); + FTS_DEBUG("Key%d(%d,%d) Up!", i, x, y); + } + return 0; + } + } + return -EINVAL; +} + +#if FTS_MT_PROTOCOL_B_EN +static int fts_input_report_b(struct fts_ts_data *data) +{ + int i = 0; + int uppoint = 0; + int touchs = 0; + bool va_reported = false; + u32 max_touch_num = data->pdata->max_touch_number; + struct ts_event *events = data->events; + + for (i = 0; i < data->touch_point; i++) { + if (fts_input_report_key(data, i) == 0) + continue; + + va_reported = true; + input_mt_slot(data->input_dev, events[i].id); + + if (EVENT_DOWN(events[i].flag)) { + input_mt_report_slot_state(data->input_dev, MT_TOOL_FINGER, true); + +#if FTS_REPORT_PRESSURE_EN + if (events[i].p <= 0) { + events[i].p = 0x3f; + } + input_report_abs(data->input_dev, ABS_MT_PRESSURE, events[i].p); +#endif + if (events[i].area <= 0) { + events[i].area = 0x09; + } + input_report_abs(data->input_dev, ABS_MT_TOUCH_MAJOR, events[i].area); + input_report_abs(data->input_dev, ABS_MT_POSITION_X, events[i].x); + input_report_abs(data->input_dev, ABS_MT_POSITION_Y, events[i].y); + + touchs |= BIT(events[i].id); + data->touchs |= BIT(events[i].id); + + if ((data->log_level >= 2) || + ((1 == data->log_level) && (FTS_TOUCH_DOWN == events[i].flag))) { + FTS_DEBUG("[B]P%d(%d, %d)[p:%d,tm:%d] DOWN!", + events[i].id, + events[i].x, events[i].y, + events[i].p, events[i].area); + } + } else { + uppoint++; + input_mt_report_slot_state(data->input_dev, MT_TOOL_FINGER, false); + data->touchs &= ~BIT(events[i].id); + if (data->log_level >= 1) { + FTS_DEBUG("[B]P%d UP!", events[i].id); + } + } + } + + if (unlikely(data->touchs ^ touchs)) { + for (i = 0; i < max_touch_num; i++) { + if (BIT(i) & (data->touchs ^ touchs)) { + if (data->log_level >= 1) { + FTS_DEBUG("[B]P%d UP!", i); + } + va_reported = true; + input_mt_slot(data->input_dev, i); + input_mt_report_slot_state(data->input_dev, MT_TOOL_FINGER, false); + } + } + } + data->touchs = touchs; + + if (va_reported) { + /* touchs==0, there's no point but key */ + if (EVENT_NO_DOWN(data) || (!touchs)) { + if (data->log_level >= 1) { + FTS_DEBUG("[B]Points All Up!"); + } + input_report_key(data->input_dev, BTN_TOUCH, 0); + } else { + input_report_key(data->input_dev, BTN_TOUCH, 1); + } + } + + input_sync(data->input_dev); + return 0; +} + +#else +static int fts_input_report_a(struct fts_ts_data *data) +{ + int i = 0; + int touchs = 0; + bool va_reported = false; + struct ts_event *events = data->events; + + for (i = 0; i < data->touch_point; i++) { + if (fts_input_report_key(data, i) == 0) { + continue; + } + + va_reported = true; + if (EVENT_DOWN(events[i].flag)) { + input_report_abs(data->input_dev, ABS_MT_TRACKING_ID, events[i].id); +#if FTS_REPORT_PRESSURE_EN + if (events[i].p <= 0) { + events[i].p = 0x3f; + } + input_report_abs(data->input_dev, ABS_MT_PRESSURE, events[i].p); +#endif + if (events[i].area <= 0) { + events[i].area = 0x09; + } + input_report_abs(data->input_dev, ABS_MT_TOUCH_MAJOR, events[i].area); + + input_report_abs(data->input_dev, ABS_MT_POSITION_X, events[i].x); + input_report_abs(data->input_dev, ABS_MT_POSITION_Y, events[i].y); + + input_mt_sync(data->input_dev); + + if ((data->log_level >= 2) || + ((1 == data->log_level) && (FTS_TOUCH_DOWN == events[i].flag))) { + FTS_DEBUG("[A]P%d(%d, %d)[p:%d,tm:%d] DOWN!", + events[i].id, + events[i].x, events[i].y, + events[i].p, events[i].area); + } + touchs++; + } + } + + /* last point down, current no point but key */ + if (data->touchs && !touchs) { + va_reported = true; + } + data->touchs = touchs; + + if (va_reported) { + if (EVENT_NO_DOWN(data)) { + if (data->log_level >= 1) { + FTS_DEBUG("[A]Points All Up!"); + } + input_report_key(data->input_dev, BTN_TOUCH, 0); + input_mt_sync(data->input_dev); + } else { + input_report_key(data->input_dev, BTN_TOUCH, 1); + } + } + + input_sync(data->input_dev); + return 0; +} +#endif + +static int fts_read_touchdata(struct fts_ts_data *data) +{ + int ret = 0; + u8 *buf = data->point_buf; + + memset(buf, 0xFF, data->pnt_buf_size); + buf[0] = 0x01; + + if (data->gesture_mode) { + if (0 == fts_gesture_readdata(data, NULL)) { + FTS_INFO("succuss to get gesture data in irq handler"); + return 1; + } + } + + ret = fts_read(buf, 1, buf + 1, data->pnt_buf_size - 1); + if (ret < 0) { + FTS_ERROR("read touchdata failed, ret:%d", ret); + return ret; + } + + if (data->log_level >= 3) { + fts_show_touch_buffer(buf, data->pnt_buf_size); + } + + return 0; +} + +static int fts_read_parse_touchdata(struct fts_ts_data *data) +{ + int ret = 0; + int i = 0; + u8 pointid = 0; + int base = 0; + struct ts_event *events = data->events; + int max_touch_num = data->pdata->max_touch_number; + u8 *buf = data->point_buf; + + ret = fts_read_touchdata(data); + if (ret) { + return ret; + } + + data->point_num = buf[FTS_TOUCH_POINT_NUM] & 0x0F; + data->touch_point = 0; + + if (data->ic_info.is_incell) { + if ((data->point_num == 0x0F) && (buf[2] == 0xFF) && (buf[3] == 0xFF) + && (buf[4] == 0xFF) && (buf[5] == 0xFF) && (buf[6] == 0xFF)) { + FTS_DEBUG("touch buff is 0xff, need recovery state"); + fts_release_all_finger(); + fts_tp_state_recovery(data); + return -EIO; + } + } + + if (data->point_num > max_touch_num) { + FTS_INFO("invalid point_num(%d)", data->point_num); + return -EIO; + } + + for (i = 0; i < max_touch_num; i++) { + base = FTS_ONE_TCH_LEN * i; + pointid = (buf[FTS_TOUCH_ID_POS + base]) >> 4; + if (pointid >= FTS_MAX_ID) + break; + else if (pointid >= max_touch_num) { + FTS_ERROR("ID(%d) beyond max_touch_number", pointid); + return -EINVAL; + } + + data->touch_point++; + events[i].x = ((buf[FTS_TOUCH_X_H_POS + base] & 0x0F) << 8) + + (buf[FTS_TOUCH_X_L_POS + base] & 0xFF); + events[i].y = ((buf[FTS_TOUCH_Y_H_POS + base] & 0x0F) << 8) + + (buf[FTS_TOUCH_Y_L_POS + base] & 0xFF); + events[i].flag = buf[FTS_TOUCH_EVENT_POS + base] >> 6; + events[i].id = buf[FTS_TOUCH_ID_POS + base] >> 4; + events[i].area = buf[FTS_TOUCH_AREA_POS + base] >> 4; + events[i].p = buf[FTS_TOUCH_PRE_POS + base]; + + if (EVENT_DOWN(events[i].flag) && (data->point_num == 0)) { + FTS_INFO("abnormal touch data from fw"); + return -EIO; + } + } + + if (data->touch_point == 0) { + FTS_INFO("no touch point information"); + return -EIO; + } + + return 0; +} + +static void fts_irq_read_report(void) +{ + int ret = 0; + struct fts_ts_data *ts_data = fts_data; + +#if FTS_ESDCHECK_EN + fts_esdcheck_set_intr(1); +#endif + +#if FTS_POINT_REPORT_CHECK_EN + fts_prc_queue_work(ts_data); +#endif + + ret = fts_read_parse_touchdata(ts_data); + if (ret == 0) { + mutex_lock(&ts_data->report_mutex); +#if FTS_MT_PROTOCOL_B_EN + fts_input_report_b(ts_data); +#else + fts_input_report_a(ts_data); +#endif + mutex_unlock(&ts_data->report_mutex); + } + +#if FTS_ESDCHECK_EN + fts_esdcheck_set_intr(0); +#endif +} + +static irqreturn_t fts_irq_handler(int irq, void *data) +{ + struct fts_ts_data *fts_data = data; + + if (!fts_data) { + pr_err("%s: Invalid fts_data\n", __func__); + return IRQ_HANDLED; + } + + if (!mutex_trylock(&fts_data->transition_lock)) + return IRQ_HANDLED; + + fts_irq_read_report(); + mutex_unlock(&fts_data->transition_lock); + + return IRQ_HANDLED; +} + +static int fts_irq_registration(struct fts_ts_data *ts_data) +{ + int ret = 0; + struct fts_ts_platform_data *pdata = ts_data->pdata; + +#ifdef CONFIG_ARCH_QTI_VM + pdata->irq_gpio_flags = IRQF_TRIGGER_RISING | IRQF_ONESHOT; + FTS_INFO("irq:%d, flag:%x", ts_data->irq, pdata->irq_gpio_flags); + ret = request_threaded_irq(ts_data->irq, NULL, fts_irq_handler, + pdata->irq_gpio_flags, + FTS_DRIVER_NAME, ts_data); +#else + ts_data->irq = gpio_to_irq(pdata->irq_gpio); + pdata->irq_gpio_flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT; + FTS_INFO("irq:%d, flag:%x", ts_data->irq, pdata->irq_gpio_flags); + ret = request_threaded_irq(ts_data->irq, NULL, fts_irq_handler, + pdata->irq_gpio_flags, + FTS_DRIVER_NAME, ts_data); +#endif + return ret; +} + +static int fts_input_init(struct fts_ts_data *ts_data) +{ + int ret = 0; + int key_num = 0; + struct fts_ts_platform_data *pdata = ts_data->pdata; + struct input_dev *input_dev; + + FTS_FUNC_ENTER(); + input_dev = input_allocate_device(); + if (!input_dev) { + FTS_ERROR("Failed to allocate memory for input device"); + return -ENOMEM; + } + + /* Init and register Input device */ + input_dev->name = FTS_DRIVER_NAME; + if (ts_data->bus_type == BUS_TYPE_I2C) + input_dev->id.bustype = BUS_I2C; + else + input_dev->id.bustype = BUS_SPI; + input_dev->dev.parent = ts_data->dev; + + input_set_drvdata(input_dev, ts_data); + + __set_bit(EV_SYN, input_dev->evbit); + __set_bit(EV_ABS, input_dev->evbit); + __set_bit(EV_KEY, input_dev->evbit); + __set_bit(BTN_TOUCH, input_dev->keybit); + __set_bit(INPUT_PROP_DIRECT, input_dev->propbit); + + if (pdata->have_key) { + FTS_INFO("set key capabilities"); + for (key_num = 0; key_num < pdata->key_number; key_num++) + input_set_capability(input_dev, EV_KEY, pdata->keys[key_num]); + } + +#if FTS_MT_PROTOCOL_B_EN + input_mt_init_slots(input_dev, pdata->max_touch_number, INPUT_MT_DIRECT); +#else + input_set_abs_params(input_dev, ABS_MT_TRACKING_ID, 0, 0x0F, 0, 0); +#endif + input_set_abs_params(input_dev, ABS_MT_POSITION_X, pdata->x_min, pdata->x_max, 0, 0); + input_set_abs_params(input_dev, ABS_MT_POSITION_Y, pdata->y_min, pdata->y_max, 0, 0); + input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, 0xFF, 0, 0); +#if FTS_REPORT_PRESSURE_EN + input_set_abs_params(input_dev, ABS_MT_PRESSURE, 0, 0xFF, 0, 0); +#endif + + ret = input_register_device(input_dev); + if (ret) { + FTS_ERROR("Input device registration failed"); + input_set_drvdata(input_dev, NULL); + input_free_device(input_dev); + input_dev = NULL; + return ret; + } + + ts_data->input_dev = input_dev; + + FTS_FUNC_EXIT(); + return 0; +} + +static int fts_report_buffer_init(struct fts_ts_data *ts_data) +{ + int point_num = 0; + int events_num = 0; + + point_num = FTS_MAX_POINTS_SUPPORT; + ts_data->pnt_buf_size = FTS_TOUCH_DATA_LEN + FTS_GESTURE_DATA_LEN; + ts_data->point_buf = (u8 *)kzalloc(ts_data->pnt_buf_size + 1, GFP_KERNEL); + if (!ts_data->point_buf) { + FTS_ERROR("failed to alloc memory for point buf"); + return -ENOMEM; + } + + events_num = point_num * sizeof(struct ts_event); + ts_data->events = (struct ts_event *)kzalloc(events_num, GFP_KERNEL); + if (!ts_data->events) { + FTS_ERROR("failed to alloc memory for point events"); + kfree_safe(ts_data->point_buf); + return -ENOMEM; + } + + return 0; +} + +#if FTS_POWER_SOURCE_CUST_EN +/***************************************************************************** +* Power Control +*****************************************************************************/ +#if FTS_PINCTRL_EN +static int fts_pinctrl_init(struct fts_ts_data *ts) +{ + int ret = 0; + + ts->pinctrl = devm_pinctrl_get(ts->dev); + if (IS_ERR_OR_NULL(ts->pinctrl)) { + FTS_ERROR("Failed to get pinctrl, please check dts"); + ret = PTR_ERR(ts->pinctrl); + goto err_pinctrl_get; + } + + ts->pins_active = pinctrl_lookup_state(ts->pinctrl, "pmx_ts_active"); + if (IS_ERR_OR_NULL(ts->pins_active)) { + FTS_ERROR("Pin state[active] not found"); + ret = PTR_ERR(ts->pins_active); + goto err_pinctrl_lookup; + } + + ts->pins_suspend = pinctrl_lookup_state(ts->pinctrl, "pmx_ts_suspend"); + if (IS_ERR_OR_NULL(ts->pins_suspend)) { + FTS_ERROR("Pin state[suspend] not found"); + ret = PTR_ERR(ts->pins_suspend); + goto err_pinctrl_lookup; + } + + ts->pins_release = pinctrl_lookup_state(ts->pinctrl, "pmx_ts_release"); + if (IS_ERR_OR_NULL(ts->pins_release)) { + FTS_ERROR("Pin state[release] not found"); + ret = PTR_ERR(ts->pins_release); + } + + return 0; +err_pinctrl_lookup: + if (ts->pinctrl) { + devm_pinctrl_put(ts->pinctrl); + } +err_pinctrl_get: + ts->pinctrl = NULL; + ts->pins_release = NULL; + ts->pins_suspend = NULL; + ts->pins_active = NULL; + return ret; +} + +static int fts_pinctrl_select_normal(struct fts_ts_data *ts) +{ + int ret = 0; + + if (ts->pinctrl && ts->pins_active) { + ret = pinctrl_select_state(ts->pinctrl, ts->pins_active); + if (ret < 0) { + FTS_ERROR("Set normal pin state error:%d", ret); + } + } + + return ret; +} + +static int fts_pinctrl_select_suspend(struct fts_ts_data *ts) +{ + int ret = 0; + + if (ts->pinctrl && ts->pins_suspend) { + ret = pinctrl_select_state(ts->pinctrl, ts->pins_suspend); + if (ret < 0) { + FTS_ERROR("Set suspend pin state error:%d", ret); + } + } + + return ret; +} + +static int fts_pinctrl_select_release(struct fts_ts_data *ts) +{ + int ret = 0; + + if (ts->pinctrl) { + if (IS_ERR_OR_NULL(ts->pins_release)) { + devm_pinctrl_put(ts->pinctrl); + ts->pinctrl = NULL; + } else { + ret = pinctrl_select_state(ts->pinctrl, ts->pins_release); + if (ret < 0) + FTS_ERROR("Set gesture pin state error:%d", ret); + } + } + + return ret; +} +#endif /* FTS_PINCTRL_EN */ + +static int fts_power_configure(struct fts_ts_data *ts_data, bool enable) +{ + int ret = 0; + + FTS_FUNC_ENTER(); + + if (enable) { + if (regulator_count_voltages(ts_data->vdd) > 0) { + ret = regulator_set_load(ts_data->vdd, FTS_LOAD_MAX_UA); + if (ret) { + FTS_ERROR("vdd regulator set_load failed ret=%d", ret); + return ret; + } + + ret = regulator_set_voltage(ts_data->vdd, FTS_VTG_MIN_UV, + FTS_VTG_MAX_UV); + if (ret) { + FTS_ERROR("vdd regulator set_vtg failed ret=%d", ret); + goto err_vdd_load; + } + } + + if (!IS_ERR_OR_NULL(ts_data->vcc_i2c)) { + if (regulator_count_voltages(ts_data->vcc_i2c) > 0) { + ret = regulator_set_load(ts_data->vcc_i2c, FTS_LOAD_AVDD_UA); + if (ret) { + FTS_ERROR("vcc_i2c regulator set_load failed ret=%d", ret); + goto err_vdd_load; + } + + ret = regulator_set_voltage(ts_data->vcc_i2c, + FTS_I2C_VTG_MIN_UV, + FTS_I2C_VTG_MAX_UV); + if (ret) { + FTS_ERROR("vcc_i2c regulator set_vtg failed,ret=%d", ret); + goto err_vcc_load; + } + } + } + } else { + if (regulator_count_voltages(ts_data->vdd) > 0) { + ret = regulator_set_load(ts_data->vdd, FTS_LOAD_DISABLE_UA); + if (ret) { + FTS_ERROR("vdd regulator set_load failed ret=%d", ret); + return ret; + } + } + + if (!IS_ERR_OR_NULL(ts_data->vcc_i2c)) { + if (regulator_count_voltages(ts_data->vcc_i2c) > 0) { + ret = regulator_set_load(ts_data->vcc_i2c, FTS_LOAD_DISABLE_UA); + if (ret) { + FTS_ERROR("vcc_i2c regulator set_load failed ret=%d", ret); + return ret; + } + } + } + } + + FTS_FUNC_EXIT(); + return ret; + +err_vcc_load: + regulator_set_load(ts_data->vcc_i2c, FTS_LOAD_DISABLE_UA); +err_vdd_load: + regulator_set_load(ts_data->vdd, FTS_LOAD_DISABLE_UA); + return ret; +} + +static int fts_ts_enable_reg(struct fts_ts_data *ts_data, bool enable) +{ + int ret = 0; + + if (IS_ERR_OR_NULL(ts_data->vdd)) { + FTS_ERROR("vdd is invalid"); + return -EINVAL; + } + + if (enable) { + fts_power_configure(ts_data, true); + ret = regulator_enable(ts_data->vdd); + if (ret) + FTS_ERROR("enable vdd regulator failed,ret=%d", ret); + + if (!IS_ERR_OR_NULL(ts_data->vcc_i2c)) { + ret = regulator_enable(ts_data->vcc_i2c); + if (ret) + FTS_ERROR("enable vcc_i2c regulator failed,ret=%d", ret); + } + } else { + ret = regulator_disable(ts_data->vdd); + if (ret) + FTS_ERROR("disable vdd regulator failed,ret=%d", ret); + if (!IS_ERR_OR_NULL(ts_data->vcc_i2c)) { + ret = regulator_disable(ts_data->vcc_i2c); + if (ret) + FTS_ERROR("disable vcc_i2c regulator failed,ret=%d", ret); + } + fts_power_configure(ts_data, false); + } + + return ret; +} + +static int fts_power_source_ctrl(struct fts_ts_data *ts_data, int enable) +{ + int ret = 0; + + if (IS_ERR_OR_NULL(ts_data->vdd)) { + FTS_ERROR("vdd is invalid"); + return -EINVAL; + } + + FTS_FUNC_ENTER(); + if (enable) { + if (ts_data->power_disabled) { + FTS_DEBUG("regulator enable !"); + gpio_direction_output(ts_data->pdata->reset_gpio, 0); + msleep(1); + ret = fts_ts_enable_reg(ts_data, true); + if (ret) + FTS_ERROR("Touch reg enable failed\n"); + ts_data->power_disabled = false; + } + } else { + if (!ts_data->power_disabled) { + FTS_DEBUG("regulator disable !"); + gpio_direction_output(ts_data->pdata->reset_gpio, 0); + msleep(1); + ret = fts_ts_enable_reg(ts_data, false); + if (ret) + FTS_ERROR("Touch reg disable failed"); + ts_data->power_disabled = true; + } + } + + FTS_FUNC_EXIT(); + return ret; +} + +/***************************************************************************** +* Name: fts_power_source_init +* Brief: Init regulator power:vdd/vcc_io(if have), generally, no vcc_io +* vdd---->vdd-supply in dts, kernel will auto add "-supply" to parse +* Must be call after fts_gpio_configure() execute,because this function +* will operate reset-gpio which request gpio in fts_gpio_configure() +* Input: +* Output: +* Return: return 0 if init power successfully, otherwise return error code +*****************************************************************************/ +static int fts_power_source_init(struct fts_ts_data *ts_data) +{ + int ret = 0; + + FTS_FUNC_ENTER(); + ts_data->vdd = regulator_get(ts_data->dev, "vdd"); + if (IS_ERR_OR_NULL(ts_data->vdd)) { + ret = PTR_ERR(ts_data->vdd); + FTS_ERROR("get vdd regulator failed,ret=%d", ret); + return ret; + } + + ts_data->vcc_i2c = regulator_get(ts_data->dev, "vcc_i2c"); + if (IS_ERR_OR_NULL(ts_data->vcc_i2c)) + FTS_INFO("get vcc_i2c regulator failed"); + +#if FTS_PINCTRL_EN + fts_pinctrl_init(ts_data); + fts_pinctrl_select_normal(ts_data); +#endif + + ts_data->power_disabled = true; + ret = fts_power_source_ctrl(ts_data, ENABLE); + if (ret) { + FTS_ERROR("fail to enable power(regulator)"); + } + + FTS_FUNC_EXIT(); + return ret; +} + +static int fts_power_source_exit(struct fts_ts_data *ts_data) +{ +#if FTS_PINCTRL_EN + fts_pinctrl_select_release(ts_data); +#endif + + fts_power_source_ctrl(ts_data, DISABLE); + + if (!IS_ERR_OR_NULL(ts_data->vdd)) { + if (regulator_count_voltages(ts_data->vdd) > 0) + regulator_set_voltage(ts_data->vdd, 0, FTS_VTG_MAX_UV); + regulator_put(ts_data->vdd); + } + + if (!IS_ERR_OR_NULL(ts_data->vcc_i2c)) { + if (regulator_count_voltages(ts_data->vcc_i2c) > 0) + regulator_set_voltage(ts_data->vcc_i2c, 0, FTS_I2C_VTG_MAX_UV); + regulator_put(ts_data->vcc_i2c); + } + + return 0; +} + +static int fts_power_source_suspend(struct fts_ts_data *ts_data) +{ + int ret = 0; + +#if FTS_PINCTRL_EN + fts_pinctrl_select_suspend(ts_data); +#endif + + ret = fts_power_source_ctrl(ts_data, DISABLE); + if (ret < 0) { + FTS_ERROR("power off fail, ret=%d", ret); + } + + return ret; +} + +static int fts_power_source_resume(struct fts_ts_data *ts_data) +{ + int ret = 0; + +#if FTS_PINCTRL_EN + fts_pinctrl_select_normal(ts_data); +#endif + + ret = fts_power_source_ctrl(ts_data, ENABLE); + if (ret < 0) { + FTS_ERROR("power on fail, ret=%d", ret); + } + + return ret; +} +#endif /* FTS_POWER_SOURCE_CUST_EN */ + +static int fts_gpio_configure(struct fts_ts_data *data) +{ + int ret = 0; + + FTS_FUNC_ENTER(); + /* request irq gpio */ + if (gpio_is_valid(data->pdata->irq_gpio)) { + ret = gpio_request(data->pdata->irq_gpio, "fts_irq_gpio"); + if (ret) { + FTS_ERROR("[GPIO]irq gpio request failed"); + goto err_irq_gpio_req; + } + + ret = gpio_direction_input(data->pdata->irq_gpio); + if (ret) { + FTS_ERROR("[GPIO]set_direction for irq gpio failed"); + goto err_irq_gpio_dir; + } + } + + /* request reset gpio */ + if (gpio_is_valid(data->pdata->reset_gpio)) { + ret = gpio_request(data->pdata->reset_gpio, "fts_reset_gpio"); + if (ret) { + FTS_ERROR("[GPIO]reset gpio request failed"); + goto err_irq_gpio_dir; + } + + ret = gpio_direction_output(data->pdata->reset_gpio, 1); + if (ret) { + FTS_ERROR("[GPIO]set_direction for reset gpio failed"); + goto err_reset_gpio_dir; + } + } + + FTS_FUNC_EXIT(); + return 0; + +err_reset_gpio_dir: + if (gpio_is_valid(data->pdata->reset_gpio)) + gpio_free(data->pdata->reset_gpio); +err_irq_gpio_dir: + if (gpio_is_valid(data->pdata->irq_gpio)) + gpio_free(data->pdata->irq_gpio); +err_irq_gpio_req: + FTS_FUNC_EXIT(); + return ret; +} + +static int fts_get_dt_coords(struct device *dev, char *name, + struct fts_ts_platform_data *pdata) +{ + int ret = 0; + u32 coords[FTS_COORDS_ARR_SIZE] = { 0 }; + struct property *prop; + struct device_node *np = dev->of_node; + int coords_size; + + prop = of_find_property(np, name, NULL); + if (!prop) + return -EINVAL; + if (!prop->value) + return -ENODATA; + + coords_size = prop->length / sizeof(u32); + if (coords_size != FTS_COORDS_ARR_SIZE) { + FTS_ERROR("invalid:%s, size:%d", name, coords_size); + return -EINVAL; + } + + ret = of_property_read_u32_array(np, name, coords, coords_size); + if (ret < 0) { + FTS_ERROR("Unable to read %s, please check dts", name); + pdata->x_min = FTS_X_MIN_DISPLAY_DEFAULT; + pdata->y_min = FTS_Y_MIN_DISPLAY_DEFAULT; + pdata->x_max = FTS_X_MAX_DISPLAY_DEFAULT; + pdata->y_max = FTS_Y_MAX_DISPLAY_DEFAULT; + return -ENODATA; + } else { + pdata->x_min = coords[0]; + pdata->y_min = coords[1]; + pdata->x_max = coords[2]; + pdata->y_max = coords[3]; + } + + FTS_INFO("display x(%d %d) y(%d %d)", pdata->x_min, pdata->x_max, + pdata->y_min, pdata->y_max); + return 0; +} + +static int fts_parse_dt(struct device *dev, struct fts_ts_platform_data *pdata) +{ + int ret = 0; + struct device_node *np = dev->of_node; + u32 temp_val = 0; + + FTS_FUNC_ENTER(); + + ret = fts_get_dt_coords(dev, "focaltech,display-coords", pdata); + if (ret < 0) + FTS_ERROR("Unable to get display-coords"); + + /* key */ + pdata->have_key = of_property_read_bool(np, "focaltech,have-key"); + if (pdata->have_key) { + ret = of_property_read_u32(np, "focaltech,key-number", &pdata->key_number); + if (ret < 0) + FTS_ERROR("Key number undefined!"); + + ret = of_property_read_u32_array(np, "focaltech,keys", + pdata->keys, pdata->key_number); + if (ret < 0) + FTS_ERROR("Keys undefined!"); + else if (pdata->key_number > FTS_MAX_KEYS) + pdata->key_number = FTS_MAX_KEYS; + + ret = of_property_read_u32_array(np, "focaltech,key-x-coords", + pdata->key_x_coords, + pdata->key_number); + if (ret < 0) + FTS_ERROR("Key Y Coords undefined!"); + + ret = of_property_read_u32_array(np, "focaltech,key-y-coords", + pdata->key_y_coords, + pdata->key_number); + if (ret < 0) + FTS_ERROR("Key X Coords undefined!"); + + FTS_INFO("VK Number:%d, key:(%d,%d,%d), " + "coords:(%d,%d),(%d,%d),(%d,%d)", + pdata->key_number, + pdata->keys[0], pdata->keys[1], pdata->keys[2], + pdata->key_x_coords[0], pdata->key_y_coords[0], + pdata->key_x_coords[1], pdata->key_y_coords[1], + pdata->key_x_coords[2], pdata->key_y_coords[2]); + } + + /* reset, irq gpio info */ + pdata->reset_gpio = of_get_named_gpio_flags(np, "focaltech,reset-gpio", + 0, &pdata->reset_gpio_flags); + if (pdata->reset_gpio < 0) + FTS_ERROR("Unable to get reset_gpio"); + + pdata->irq_gpio = of_get_named_gpio_flags(np, "focaltech,irq-gpio", + 0, &pdata->irq_gpio_flags); + if (pdata->irq_gpio < 0) + FTS_ERROR("Unable to get irq_gpio"); + + ret = of_property_read_u32(np, "focaltech,max-touch-number", &temp_val); + if (ret < 0) { + FTS_ERROR("Unable to get max-touch-number, please check dts"); + pdata->max_touch_number = FTS_MAX_POINTS_SUPPORT; + } else { + if (temp_val < 2) + pdata->max_touch_number = 2; /* max_touch_number must >= 2 */ + else if (temp_val > FTS_MAX_POINTS_SUPPORT) + pdata->max_touch_number = FTS_MAX_POINTS_SUPPORT; + else + pdata->max_touch_number = temp_val; + } + + FTS_INFO("max touch number:%d, irq gpio:%d, reset gpio:%d", + pdata->max_touch_number, pdata->irq_gpio, pdata->reset_gpio); + + ret = of_property_read_u32(np, "focaltech,ic-type", &temp_val); + if (ret < 0) + pdata->type = _FT3518; + else + pdata->type = temp_val; + + FTS_FUNC_EXIT(); + return 0; +} + +#if defined(CONFIG_DRM) +static void fts_resume_work(struct work_struct *work) +{ + struct fts_ts_data *ts_data = container_of(work, struct fts_ts_data, + resume_work); + + fts_ts_resume(ts_data->dev); +} + +static void fts_ts_panel_notifier_callback(enum panel_event_notifier_tag tag, + struct panel_event_notification *notification, void *client_data) +{ + struct fts_ts_data *ts_data = client_data; + + if (!notification) { + pr_err("Invalid notification\n"); + return; + } + + FTS_DEBUG("Notification type:%d, early_trigger:%d", + notification->notif_type, + notification->notif_data.early_trigger); + switch (notification->notif_type) { + case DRM_PANEL_EVENT_UNBLANK: + if (notification->notif_data.early_trigger) + FTS_DEBUG("resume notification pre commit\n"); + else + queue_work(fts_data->ts_workqueue, &fts_data->resume_work); + break; + case DRM_PANEL_EVENT_BLANK: + if (notification->notif_data.early_trigger) { + cancel_work_sync(&fts_data->resume_work); + fts_ts_suspend(ts_data->dev); + } else { + FTS_DEBUG("suspend notification post commit\n"); + } + break; + case DRM_PANEL_EVENT_BLANK_LP: + FTS_DEBUG("received lp event\n"); + break; + case DRM_PANEL_EVENT_FPS_CHANGE: + FTS_DEBUG("shashank:Received fps change old fps:%d new fps:%d\n", + notification->notif_data.old_fps, + notification->notif_data.new_fps); + break; + default: + FTS_DEBUG("notification serviced :%d\n", + notification->notif_type); + break; + } +} + +#elif defined(CONFIG_FB) +static void fts_resume_work(struct work_struct *work) +{ + struct fts_ts_data *ts_data = container_of(work, struct fts_ts_data, + resume_work); + + fts_ts_resume(ts_data->dev); +} + +static int fb_notifier_callback(struct notifier_block *self, + unsigned long event, void *data) +{ + struct fb_event *evdata = data; + int *blank = NULL; + struct fts_ts_data *ts_data = container_of(self, struct fts_ts_data, + fb_notif); + + if (!(event == FB_EARLY_EVENT_BLANK || event == FB_EVENT_BLANK)) { + FTS_INFO("event(%lu) do not need process\n", event); + return 0; + } + + blank = evdata->data; + FTS_INFO("FB event:%lu,blank:%d", event, *blank); + switch (*blank) { + case FB_BLANK_UNBLANK: + if (FB_EARLY_EVENT_BLANK == event) { + FTS_INFO("resume: event = %lu, not care\n", event); + } else if (FB_EVENT_BLANK == event) { + queue_work(fts_data->ts_workqueue, &fts_data->resume_work); + } + break; + + case FB_BLANK_POWERDOWN: + if (FB_EARLY_EVENT_BLANK == event) { + cancel_work_sync(&fts_data->resume_work); + fts_ts_suspend(ts_data->dev); + } else if (FB_EVENT_BLANK == event) { + FTS_INFO("suspend: event = %lu, not care\n", event); + } + break; + + default: + FTS_INFO("FB BLANK(%d) do not need process\n", *blank); + break; + } + + return 0; +} +#elif defined(CONFIG_HAS_EARLYSUSPEND) +static void fts_ts_early_suspend(struct early_suspend *handler) +{ + struct fts_ts_data *ts_data = container_of(handler, struct fts_ts_data, + early_suspend); + + fts_ts_suspend(ts_data->dev); +} + +static void fts_ts_late_resume(struct early_suspend *handler) +{ + struct fts_ts_data *ts_data = container_of(handler, struct fts_ts_data, + early_suspend); + + fts_ts_resume(ts_data->dev); +} +#endif + +static int fts_ts_probe_delayed(struct fts_ts_data *fts_data) +{ + int ret = 0; + +/* Avoid setting up hardware for TVM during probe */ +#ifdef CONFIG_FTS_TRUSTED_TOUCH +#ifdef CONFIG_ARCH_QTI_VM + if (!atomic_read(&fts_data->delayed_vm_probe_pending)) { + atomic_set(&fts_data->delayed_vm_probe_pending, 1); + return 0; + } + goto tvm_setup; +#endif +#endif + ret = fts_gpio_configure(fts_data); + if (ret) { + FTS_ERROR("configure the gpios fail"); + goto err_gpio_config; + } + +#if FTS_POWER_SOURCE_CUST_EN + ret = fts_power_source_init(fts_data); + if (ret) { + FTS_ERROR("fail to get power(regulator)"); + goto err_power_init; + } +#endif + fts_reset_proc(200); + + ret = fts_get_ic_information(fts_data); + if (ret) { + FTS_ERROR("not focal IC, unregister driver"); + goto err_irq_req; + } + +#ifdef CONFIG_ARCH_QTI_VM +tvm_setup: +#endif + ret = fts_irq_registration(fts_data); + if (ret) { + FTS_ERROR("request irq failed"); +#ifdef CONFIG_ARCH_QTI_VM + return ret; +#endif + goto err_irq_req; + } + +#ifdef CONFIG_ARCH_QTI_VM + return ret; +#endif + + ret = fts_fwupg_init(fts_data); + if (ret) + FTS_ERROR("init fw upgrade fail"); + + return 0; + +err_irq_req: + if (gpio_is_valid(fts_data->pdata->reset_gpio)) + gpio_free(fts_data->pdata->reset_gpio); + if (gpio_is_valid(fts_data->pdata->irq_gpio)) + gpio_free(fts_data->pdata->irq_gpio); +#if FTS_POWER_SOURCE_CUST_EN +err_power_init: + fts_power_source_exit(fts_data); +#endif +err_gpio_config: + return ret; +} + +static int fts_ts_probe_entry(struct fts_ts_data *ts_data) +{ + int ret = 0; + int pdata_size = sizeof(struct fts_ts_platform_data); + + FTS_FUNC_ENTER(); + FTS_INFO("%s", FTS_DRIVER_VERSION); + ts_data->pdata = kzalloc(pdata_size, GFP_KERNEL); + if (!ts_data->pdata) { + FTS_ERROR("allocate memory for platform_data fail"); + return -ENOMEM; + } + + if (ts_data->dev->of_node) { + ret = fts_parse_dt(ts_data->dev, ts_data->pdata); + if (ret) + FTS_ERROR("device-tree parse fail"); + } else { + if (ts_data->dev->platform_data) { + memcpy(ts_data->pdata, ts_data->dev->platform_data, pdata_size); + } else { + FTS_ERROR("platform_data is null"); + return -ENODEV; + } + } + + ts_data->ts_workqueue = create_singlethread_workqueue("fts_wq"); + if (!ts_data->ts_workqueue) { + FTS_ERROR("create fts workqueue fail"); + } + + spin_lock_init(&ts_data->irq_lock); + mutex_init(&ts_data->report_mutex); + mutex_init(&ts_data->bus_lock); + mutex_init(&ts_data->transition_lock); + + /* Init communication interface */ + ret = fts_bus_init(ts_data); + if (ret) { + FTS_ERROR("bus initialize fail"); + goto err_bus_init; + } + + ret = fts_input_init(ts_data); + if (ret) { + FTS_ERROR("input initialize fail"); + goto err_input_init; + } + + ret = fts_report_buffer_init(ts_data); + if (ret) { + FTS_ERROR("report buffer init fail"); + goto err_report_buffer; + } + + ret = fts_create_apk_debug_channel(ts_data); + if (ret) { + FTS_ERROR("create apk debug node fail"); + } + + ret = fts_create_sysfs(ts_data); + if (ret) { + FTS_ERROR("create sysfs node fail"); + } + +#if FTS_POINT_REPORT_CHECK_EN + ret = fts_point_report_check_init(ts_data); + if (ret) { + FTS_ERROR("init point report check fail"); + } +#endif + + ret = fts_ex_mode_init(ts_data); + if (ret) { + FTS_ERROR("init glove/cover/charger fail"); + } + + ret = fts_gesture_init(ts_data); + if (ret) { + FTS_ERROR("init gesture fail"); + } + + +#if FTS_ESDCHECK_EN + ret = fts_esdcheck_init(ts_data); + if (ret) { + FTS_ERROR("init esd check fail"); + } +#endif + +#ifdef CONFIG_FTS_TRUSTED_TOUCH + fts_ts_trusted_touch_init(ts_data); + mutex_init(&(ts_data->fts_clk_io_ctrl_mutex)); +#endif + +#ifndef CONFIG_ARCH_QTI_VM + if (ts_data->pdata->type == _FT8726) { + atomic_set(&ts_data->delayed_vm_probe_pending, 1); + ts_data->suspended = true; + } else { + ret = fts_ts_probe_delayed(ts_data); + if (ret) { + FTS_ERROR("Failed to enable resources\n"); + goto err_probe_delayed; + } + } +#else + ret = fts_ts_probe_delayed(ts_data); + if (ret) { + FTS_ERROR("Failed to enable resources\n"); + goto err_probe_delayed; + } +#endif + +#if defined(CONFIG_DRM) + if (ts_data->ts_workqueue) + INIT_WORK(&ts_data->resume_work, fts_resume_work); + +#ifdef CONFIG_FTS_TRUSTED_TOUCH + if (!strcmp(fts_data->touch_environment, "pvm")) +#else + if (active_panel) +#endif + fts_ts_register_for_panel_events(ts_data->dev->of_node, ts_data); +#elif defined(CONFIG_FB) + if (ts_data->ts_workqueue) { + INIT_WORK(&ts_data->resume_work, fts_resume_work); + } + ts_data->fb_notif.notifier_call = fb_notifier_callback; + ret = fb_register_client(&ts_data->fb_notif); + if (ret) { + FTS_ERROR("[FB]Unable to register fb_notifier: %d", ret); + } +#elif defined(CONFIG_HAS_EARLYSUSPEND) + ts_data->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + FTS_SUSPEND_LEVEL; + ts_data->early_suspend.suspend = fts_ts_early_suspend; + ts_data->early_suspend.resume = fts_ts_late_resume; + register_early_suspend(&ts_data->early_suspend); +#endif + + FTS_FUNC_EXIT(); + return 0; + +err_probe_delayed: + kfree_safe(ts_data->point_buf); + kfree_safe(ts_data->events); +err_report_buffer: + input_unregister_device(ts_data->input_dev); +err_input_init: + if (ts_data->ts_workqueue) + destroy_workqueue(ts_data->ts_workqueue); +err_bus_init: + kfree_safe(ts_data->bus_tx_buf); + kfree_safe(ts_data->bus_rx_buf); + kfree_safe(ts_data->pdata); + + FTS_FUNC_EXIT(); + return ret; +} + +static int fts_ts_remove_entry(struct fts_ts_data *ts_data) +{ + FTS_FUNC_ENTER(); + +#if FTS_POINT_REPORT_CHECK_EN + fts_point_report_check_exit(ts_data); +#endif + + fts_release_apk_debug_channel(ts_data); + fts_remove_sysfs(ts_data); + fts_ex_mode_exit(ts_data); + + fts_fwupg_exit(ts_data); + + +#if FTS_ESDCHECK_EN + fts_esdcheck_exit(ts_data); +#endif + + fts_gesture_exit(ts_data); + fts_bus_exit(ts_data); + + free_irq(ts_data->irq, ts_data); + input_unregister_device(ts_data->input_dev); + + if (ts_data->ts_workqueue) + destroy_workqueue(ts_data->ts_workqueue); + +#if defined(CONFIG_DRM) + if (active_panel && ts_data->notifier_cookie) + panel_event_notifier_unregister(ts_data->notifier_cookie); + +#elif defined(CONFIG_FB) + if (fb_unregister_client(&ts_data->fb_notif)) + FTS_ERROR("Error occurred while unregistering fb_notifier."); +#elif defined(CONFIG_HAS_EARLYSUSPEND) + unregister_early_suspend(&ts_data->early_suspend); +#endif + + if (gpio_is_valid(ts_data->pdata->reset_gpio)) + gpio_free(ts_data->pdata->reset_gpio); + + if (gpio_is_valid(ts_data->pdata->irq_gpio)) + gpio_free(ts_data->pdata->irq_gpio); + +#if FTS_POWER_SOURCE_CUST_EN + fts_power_source_exit(ts_data); +#endif + + kfree_safe(ts_data->point_buf); + kfree_safe(ts_data->events); + + kfree_safe(ts_data->pdata); + kfree_safe(ts_data); + + FTS_FUNC_EXIT(); + + return 0; +} + +static int fts_ts_suspend(struct device *dev) +{ + int ret = 0; + struct fts_ts_data *ts_data = fts_data; + + FTS_FUNC_ENTER(); + if (ts_data->suspended) { + FTS_INFO("Already in suspend state"); + return 0; + } + + if (ts_data->fw_loading) { + FTS_INFO("fw upgrade in process, can't suspend"); + return 0; + } + +#ifdef CONFIG_FTS_TRUSTED_TOUCH + if (atomic_read(&fts_data->trusted_touch_transition) + || atomic_read(&fts_data->trusted_touch_enabled)) + wait_for_completion_interruptible( + &fts_data->trusted_touch_powerdown); +#endif + + mutex_lock(&ts_data->transition_lock); + +#if FTS_ESDCHECK_EN + fts_esdcheck_suspend(); +#endif + + if (ts_data->gesture_mode) { + fts_gesture_suspend(ts_data); + } else { + fts_irq_disable(); + + FTS_INFO("make TP enter into sleep mode"); + ret = fts_write_reg(FTS_REG_POWER_MODE, FTS_REG_POWER_MODE_SLEEP); + if (ret < 0) + FTS_ERROR("set TP to sleep mode fail, ret=%d", ret); + + if (!ts_data->ic_info.is_incell) { +#if FTS_POWER_SOURCE_CUST_EN + ret = fts_power_source_suspend(ts_data); + if (ret < 0) { + FTS_ERROR("power enter suspend fail"); + } +#endif + } else { +#if FTS_PINCTRL_EN + fts_pinctrl_select_suspend(ts_data); +#endif + gpio_direction_output(ts_data->pdata->reset_gpio, 0); + } + } + + fts_release_all_finger(); + ts_data->suspended = true; + mutex_unlock(&ts_data->transition_lock); + FTS_FUNC_EXIT(); + return 0; +} + +static int fts_ts_resume(struct device *dev) +{ + struct fts_ts_data *ts_data = fts_data; + int ret = 0; + + FTS_FUNC_ENTER(); + if (!ts_data->suspended) { + FTS_DEBUG("Already in awake state"); + return 0; + } + +#ifdef CONFIG_FTS_TRUSTED_TOUCH + + if (atomic_read(&ts_data->trusted_touch_transition)) + wait_for_completion_interruptible( + &ts_data->trusted_touch_powerdown); +#endif + if (ts_data->pdata->type == _FT8726 && + atomic_read(&ts_data->delayed_vm_probe_pending)) { + ret = fts_ts_probe_delayed(ts_data); + if (ret) { + FTS_ERROR("Failed to enable resources\n"); + return ret; + } + ts_data->suspended = false; + atomic_set(&ts_data->delayed_vm_probe_pending, 0); + return ret; + } + + mutex_lock(&ts_data->transition_lock); + + fts_release_all_finger(); + + if (!ts_data->ic_info.is_incell) { +#if FTS_POWER_SOURCE_CUST_EN + fts_power_source_resume(ts_data); +#endif + } else { +#if FTS_PINCTRL_EN + fts_pinctrl_select_normal(ts_data); +#endif + } + + fts_reset_proc(200); + + fts_wait_tp_to_valid(); + fts_ex_mode_recovery(ts_data); + +#if FTS_ESDCHECK_EN + fts_esdcheck_resume(); +#endif + + if (ts_data->gesture_mode) { + fts_gesture_resume(ts_data); + } else { + fts_irq_enable(); + } + + ts_data->suspended = false; + mutex_unlock(&ts_data->transition_lock); + FTS_FUNC_EXIT(); + return 0; +} + +/***************************************************************************** +* TP Driver +*****************************************************************************/ +static int fts_ts_check_dt(struct device_node *np) +{ + int i; + int count; + struct device_node *node; + struct drm_panel *panel; + + count = of_count_phandle_with_args(np, "panel", NULL); + if (count <= 0) + return 0; + + for (i = 0; i < count; i++) { + node = of_parse_phandle(np, "panel", i); + panel = of_drm_find_panel(node); + of_node_put(node); + if (!IS_ERR(panel)) { + active_panel = panel; + return 0; + } + } + + return PTR_ERR(panel); +} + +static int fts_ts_check_default_tp(struct device_node *dt, const char *prop) +{ + const char **active_tp = NULL; + int count, tmp, score = 0; + const char *active; + int ret, i; + + count = of_property_count_strings(dt->parent, prop); + if (count <= 0 || count > 3) + return -ENODEV; + + active_tp = kcalloc(count, sizeof(char *), GFP_KERNEL); + if (!active_tp) { + FTS_ERROR("FTS alloc failed\n"); + return -ENOMEM; + } + + ret = of_property_read_string_array(dt->parent, prop, + active_tp, count); + if (ret < 0) { + FTS_ERROR("fail to read %s %d\n", prop, ret); + ret = -ENODEV; + goto out; + } + + for (i = 0; i < count; i++) { + active = active_tp[i]; + if (active != NULL) { + tmp = of_device_is_compatible(dt, active); + if (tmp > 0) + score++; + } + } + + if (score <= 0) { + FTS_INFO("not match this driver\n"); + ret = -ENODEV; + goto out; + } + ret = 0; +out: + kfree(active_tp); + return ret; +} + +static int fts_ts_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + int ret = 0; + struct fts_ts_data *ts_data = NULL; + struct device_node *dp = client->dev.of_node; + + FTS_INFO("Touch Screen(I2C BUS) driver prboe..."); + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + FTS_ERROR("I2C not supported"); + return -ENODEV; + } + + ret = fts_ts_check_dt(dp); + if (ret == -EPROBE_DEFER) + return ret; + + if (ret) { + if (!fts_ts_check_default_tp(dp, "qcom,i2c-touch-active")) + ret = -EPROBE_DEFER; + else + ret = -ENODEV; + + return ret; + } + + /* malloc memory for global struct variable */ + ts_data = (struct fts_ts_data *)kzalloc(sizeof(*ts_data), GFP_KERNEL); + if (!ts_data) { + FTS_ERROR("allocate memory for fts_data fail"); + return -ENOMEM; + } + + fts_data = ts_data; + ts_data->client = client; + ts_data->dev = &client->dev; + ts_data->log_level = 1; + ts_data->fw_is_running = 0; + ts_data->bus_type = BUS_TYPE_I2C; + i2c_set_clientdata(client, ts_data); + + ret = fts_ts_probe_entry(ts_data); + if (ret) { + FTS_ERROR("Touch Screen(I2C BUS) driver probe fail"); + kfree_safe(ts_data); + return ret; + } + + FTS_INFO("Touch Screen(I2C BUS) driver prboe successfully"); + return 0; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0)) +static void fts_ts_i2c_remove(struct i2c_client *client) +#else +static int fts_ts_i2c_remove(struct i2c_client *client) +#endif +{ + int rc = 0; + + rc = fts_ts_remove_entry(i2c_get_clientdata(client)); + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0)) + return rc; +#endif +} + +static const struct i2c_device_id fts_ts_i2c_id[] = { + {FTS_DRIVER_NAME, 0}, + {}, +}; +static const struct of_device_id fts_dt_match[] = { + {.compatible = "focaltech,fts_ts", }, + {}, +}; +MODULE_DEVICE_TABLE(of, fts_dt_match); + +static struct i2c_driver fts_ts_i2c_driver = { + .probe = fts_ts_i2c_probe, + .remove = fts_ts_i2c_remove, + .driver = { + .name = FTS_DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(fts_dt_match), + }, + .id_table = fts_ts_i2c_id, +}; + +static int __init fts_ts_i2c_init(void) +{ + int ret = 0; + + FTS_FUNC_ENTER(); + ret = i2c_add_driver(&fts_ts_i2c_driver); + if (ret != 0) + FTS_ERROR("Focaltech touch screen driver init failed!"); + + FTS_FUNC_EXIT(); + return ret; +} + +static void __exit fts_ts_i2c_exit(void) +{ + i2c_del_driver(&fts_ts_i2c_driver); +} + +static int fts_ts_spi_probe(struct spi_device *spi) +{ + int ret = 0; + struct fts_ts_data *ts_data = NULL; + struct device_node *dp = spi->dev.of_node; + + FTS_INFO("Touch Screen(SPI BUS) driver prboe..."); + + ret = fts_ts_check_dt(dp); + if (ret == -EPROBE_DEFER) + return ret; + + if (ret) { + if (!fts_ts_check_default_tp(dp, "qcom,spi-touch-active")) + ret = -EPROBE_DEFER; + else + ret = -ENODEV; + + return ret; + } + + spi->mode = SPI_MODE_0; + spi->bits_per_word = 8; + ret = spi_setup(spi); + if (ret) { + FTS_ERROR("spi setup fail"); + return ret; + } + + /* malloc memory for global struct variable */ + ts_data = kzalloc(sizeof(*ts_data), GFP_KERNEL); + if (!ts_data) { + FTS_ERROR("allocate memory for fts_data fail"); + return -ENOMEM; + } + + fts_data = ts_data; + ts_data->spi = spi; + ts_data->dev = &spi->dev; + ts_data->log_level = 1; + + ts_data->bus_type = BUS_TYPE_SPI_V2; + spi_set_drvdata(spi, ts_data); + + ret = fts_ts_probe_entry(ts_data); + if (ret) { + FTS_ERROR("Touch Screen(SPI BUS) driver probe fail"); + kfree_safe(ts_data); + return ret; + } + + FTS_INFO("Touch Screen(SPI BUS) driver prboe successfully"); + return 0; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0)) +static void fts_ts_spi_remove(struct spi_device *spi) +#else +static int fts_ts_spi_remove(struct spi_device *spi) +#endif +{ + int rc = 0; + + rc = fts_ts_remove_entry(spi_get_drvdata(spi)); + + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0)) + return rc; +#endif +} + +static const struct spi_device_id fts_ts_spi_id[] = { + {FTS_DRIVER_NAME, 0}, + {}, +}; + +static struct spi_driver fts_ts_spi_driver = { + .probe = fts_ts_spi_probe, + .remove = fts_ts_spi_remove, + .driver = { + .name = FTS_DRIVER_NAME, + .owner = THIS_MODULE, +#if defined(CONFIG_PM) && FTS_PATCH_COMERR_PM + .pm = &fts_dev_pm_ops, +#endif + .of_match_table = of_match_ptr(fts_dt_match), + }, + .id_table = fts_ts_spi_id, +}; + +static int __init fts_ts_spi_init(void) +{ + int ret = 0; + + FTS_FUNC_ENTER(); + ret = spi_register_driver(&fts_ts_spi_driver); + if (ret != 0) + FTS_ERROR("Focaltech touch screen driver init failed!"); + + FTS_FUNC_EXIT(); + return ret; +} + +static void __exit fts_ts_spi_exit(void) +{ + spi_unregister_driver(&fts_ts_spi_driver); +} + +static int __init fts_ts_init(void) +{ + int ret = 0; + + ret = fts_ts_i2c_init(); + if (ret) + FTS_ERROR("Focaltech I2C driver init failed!"); + + ret = fts_ts_spi_init(); + if (ret) + FTS_ERROR("Focaltech SPI driver init failed!"); + + return ret; +} + +static void __exit fts_ts_exit(void) +{ + fts_ts_i2c_exit(); + fts_ts_spi_exit(); +} + +#ifdef CONFIG_ARCH_QTI_VM +module_init(fts_ts_init); +#else +late_initcall(fts_ts_init); +#endif +module_exit(fts_ts_exit); + +MODULE_AUTHOR("FocalTech Driver Team"); +MODULE_DESCRIPTION("FocalTech Touchscreen Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/qcom/opensource/touch-drivers/focaltech_touch/focaltech_core.h b/qcom/opensource/touch-drivers/focaltech_touch/focaltech_core.h new file mode 100644 index 0000000000..9cb797d581 --- /dev/null +++ b/qcom/opensource/touch-drivers/focaltech_touch/focaltech_core.h @@ -0,0 +1,391 @@ +/* + * + * FocalTech TouchScreen driver. + * + * Copyright (c) 2012-2019, Focaltech Ltd. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +/***************************************************************************** +* +* File Name: focaltech_core.h + +* Author: Focaltech Driver Team +* +* Created: 2016-08-08 +* +* Abstract: +* +* Reference: +* +*****************************************************************************/ + +#ifndef __LINUX_FOCALTECH_CORE_H__ +#define __LINUX_FOCALTECH_CORE_H__ +/***************************************************************************** +* Included header files +*****************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(CONFIG_FTS_TRUSTED_TOUCH) +#include +#include +#endif +#include "focaltech_common.h" + +/***************************************************************************** +* Private constant and macro definitions using #define +*****************************************************************************/ +#define FTS_MAX_POINTS_SUPPORT 10 /* constant value, can't be changed */ +#define FTS_MAX_KEYS 4 +#define FTS_KEY_DIM 10 +#define FTS_ONE_TCH_LEN 6 +#define FTS_TOUCH_DATA_LEN (FTS_MAX_POINTS_SUPPORT * FTS_ONE_TCH_LEN + 3) + +#define FTS_GESTURE_POINTS_MAX 6 +#define FTS_GESTURE_DATA_LEN (FTS_GESTURE_POINTS_MAX * 4 + 4) + +#define FTS_MAX_ID 0x0A +#define FTS_TOUCH_X_H_POS 3 +#define FTS_TOUCH_X_L_POS 4 +#define FTS_TOUCH_Y_H_POS 5 +#define FTS_TOUCH_Y_L_POS 6 +#define FTS_TOUCH_PRE_POS 7 +#define FTS_TOUCH_AREA_POS 8 +#define FTS_TOUCH_POINT_NUM 2 +#define FTS_TOUCH_EVENT_POS 3 +#define FTS_TOUCH_ID_POS 5 +#define FTS_COORDS_ARR_SIZE 4 +#define FTS_X_MIN_DISPLAY_DEFAULT 0 +#define FTS_Y_MIN_DISPLAY_DEFAULT 0 +#define FTS_X_MAX_DISPLAY_DEFAULT 720 +#define FTS_Y_MAX_DISPLAY_DEFAULT 1280 + +#define FTS_TOUCH_DOWN 0 +#define FTS_TOUCH_UP 1 +#define FTS_TOUCH_CONTACT 2 +#define EVENT_DOWN(flag) ((FTS_TOUCH_DOWN == flag) || (FTS_TOUCH_CONTACT == flag)) +#define EVENT_UP(flag) (FTS_TOUCH_UP == flag) +#define EVENT_NO_DOWN(data) (!data->point_num) + +#define FTX_MAX_COMPATIBLE_TYPE 4 +#define FTX_MAX_COMMMAND_LENGTH 16 + + +/***************************************************************************** +* Alternative mode (When something goes wrong, the modules may be able to solve the problem.) +*****************************************************************************/ +/* + * For commnication error in PM(deep sleep) state + */ +#define FTS_PATCH_COMERR_PM 0 +#define FTS_TIMEOUT_COMERR_PM 700 + + +/***************************************************************************** +* Private enumerations, structures and unions using typedef +*****************************************************************************/ +struct ftxxxx_proc { + struct proc_dir_entry *proc_entry; + u8 opmode; + u8 cmd_len; + u8 cmd[FTX_MAX_COMMMAND_LENGTH]; +}; + +struct fts_ts_platform_data { + u32 type; + u32 irq_gpio; + u32 irq_gpio_flags; + u32 reset_gpio; + u32 reset_gpio_flags; + bool have_key; + u32 key_number; + u32 keys[FTS_MAX_KEYS]; + u32 key_y_coords[FTS_MAX_KEYS]; + u32 key_x_coords[FTS_MAX_KEYS]; + u32 x_max; + u32 y_max; + u32 x_min; + u32 y_min; + u32 max_touch_number; +}; + +struct ts_event { + int x; /*x coordinate */ + int y; /*y coordinate */ + int p; /* pressure */ + int flag; /* touch event flag: 0 -- down; 1-- up; 2 -- contact */ + int id; /*touch ID */ + int area; +}; + +enum trusted_touch_mode_config { + TRUSTED_TOUCH_VM_MODE, + TRUSTED_TOUCH_MODE_NONE +}; + +enum trusted_touch_pvm_states { + TRUSTED_TOUCH_PVM_INIT, + PVM_I2C_RESOURCE_ACQUIRED, + PVM_INTERRUPT_DISABLED, + PVM_IOMEM_LENT, + PVM_IOMEM_LENT_NOTIFIED, + PVM_IRQ_LENT, + PVM_IRQ_LENT_NOTIFIED, + PVM_IOMEM_RELEASE_NOTIFIED, + PVM_IRQ_RELEASE_NOTIFIED, + PVM_ALL_RESOURCES_RELEASE_NOTIFIED, + PVM_IRQ_RECLAIMED, + PVM_IOMEM_RECLAIMED, + PVM_INTERRUPT_ENABLED, + PVM_I2C_RESOURCE_RELEASED, + TRUSTED_TOUCH_PVM_STATE_MAX +}; + +enum trusted_touch_tvm_states { + TRUSTED_TOUCH_TVM_INIT, + TVM_IOMEM_LENT_NOTIFIED, + TVM_IRQ_LENT_NOTIFIED, + TVM_ALL_RESOURCES_LENT_NOTIFIED, + TVM_IOMEM_ACCEPTED, + TVM_I2C_SESSION_ACQUIRED, + TVM_IRQ_ACCEPTED, + TVM_INTERRUPT_ENABLED, + TVM_INTERRUPT_DISABLED, + TVM_IRQ_RELEASED, + TVM_I2C_SESSION_RELEASED, + TVM_IOMEM_RELEASED, + TRUSTED_TOUCH_TVM_STATE_MAX +}; + +#ifdef CONFIG_FTS_TRUSTED_TOUCH +#define TRUSTED_TOUCH_MEM_LABEL 0x7 + +#define TOUCH_RESET_GPIO_BASE 0xF114000 +#define TOUCH_RESET_GPIO_SIZE 0x1000 +#define TOUCH_RESET_GPIO_OFFSET 0x4 +#define TOUCH_INTR_GPIO_BASE 0xF115000 +#define TOUCH_INTR_GPIO_SIZE 0x1000 +#define TOUCH_INTR_GPIO_OFFSET 0x8 + +#define TRUSTED_TOUCH_EVENT_LEND_FAILURE -1 +#define TRUSTED_TOUCH_EVENT_LEND_NOTIFICATION_FAILURE -2 +#define TRUSTED_TOUCH_EVENT_ACCEPT_FAILURE -3 +#define TRUSTED_TOUCH_EVENT_FUNCTIONAL_FAILURE -4 +#define TRUSTED_TOUCH_EVENT_RELEASE_FAILURE -5 +#define TRUSTED_TOUCH_EVENT_RECLAIM_FAILURE -6 +#define TRUSTED_TOUCH_EVENT_I2C_FAILURE -7 +#define TRUSTED_TOUCH_EVENT_NOTIFICATIONS_PENDING 5 + +struct trusted_touch_vm_info { + enum gh_irq_label irq_label; + enum gh_mem_notifier_tag mem_tag; + enum gh_vm_names vm_name; + const char *trusted_touch_type; + u32 hw_irq; + gh_memparcel_handle_t vm_mem_handle; + u32 *iomem_bases; + u32 *iomem_sizes; + u32 iomem_list_size; + void *mem_cookie; + atomic_t vm_state; +}; +#endif + +struct fts_ts_data { + struct i2c_client *client; + struct spi_device *spi; + struct device *dev; + struct input_dev *input_dev; + struct fts_ts_platform_data *pdata; + struct ts_ic_info ic_info; + struct workqueue_struct *ts_workqueue; + struct work_struct fwupg_work; + struct delayed_work esdcheck_work; + struct delayed_work prc_work; + struct work_struct resume_work; + struct work_struct suspend_work; + struct ftxxxx_proc proc; + spinlock_t irq_lock; + struct mutex report_mutex; + struct mutex bus_lock; + struct mutex transition_lock; + int irq; + int log_level; + int fw_is_running; /* confirm fw is running when using spi:default 0 */ + int dummy_byte; +#if defined(CONFIG_PM) && FTS_PATCH_COMERR_PM + struct completion pm_completion; + bool pm_suspend; +#endif + bool suspended; + bool fw_loading; + bool irq_disabled; + bool power_disabled; + bool glove_mode; + bool cover_mode; + bool charger_mode; + bool gesture_mode; /* gesture enable or disable, default: disable */ + int report_rate; + /* multi-touch */ + struct ts_event *events; + u8 *bus_tx_buf; + u8 *bus_rx_buf; + int bus_type; + u8 *point_buf; + void *notifier_cookie; + int pnt_buf_size; + int touchs; + int key_state; + int touch_point; + int point_num; + struct regulator *vdd; + struct regulator *vcc_i2c; +#if FTS_PINCTRL_EN + struct pinctrl *pinctrl; + struct pinctrl_state *pins_active; + struct pinctrl_state *pins_suspend; + struct pinctrl_state *pins_release; +#endif +#if defined(CONFIG_FB) || defined(CONFIG_DRM) + struct notifier_block fb_notif; +#elif defined(CONFIG_HAS_EARLYSUSPEND) + struct early_suspend early_suspend; +#endif + +#ifdef CONFIG_FTS_TRUSTED_TOUCH + struct trusted_touch_vm_info *vm_info; + struct mutex fts_clk_io_ctrl_mutex; + const char *touch_environment; + struct completion trusted_touch_powerdown; + struct clk *core_clk; + struct clk *iface_clk; + atomic_t trusted_touch_initialized; + atomic_t trusted_touch_enabled; + atomic_t trusted_touch_transition; + atomic_t trusted_touch_event; + atomic_t trusted_touch_abort_status; + atomic_t trusted_touch_mode; +#endif + atomic_t delayed_vm_probe_pending; +}; + +enum _FTS_BUS_TYPE { + BUS_TYPE_NONE, + BUS_TYPE_I2C, + BUS_TYPE_SPI, + BUS_TYPE_SPI_V2, +}; + +/***************************************************************************** +* Global variable or extern global variabls/functions +*****************************************************************************/ +extern struct fts_ts_data *fts_data; + +/* communication interface */ +int fts_read(u8 *cmd, u32 cmdlen, u8 *data, u32 datalen); +int fts_read_reg(u8 addr, u8 *value); +int fts_write(u8 *writebuf, u32 writelen); +int fts_write_reg(u8 addr, u8 value); +void fts_hid2std(void); +int fts_bus_init(struct fts_ts_data *ts_data); +int fts_bus_exit(struct fts_ts_data *ts_data); + +/* Gesture functions */ +int fts_gesture_init(struct fts_ts_data *ts_data); +int fts_gesture_exit(struct fts_ts_data *ts_data); +void fts_gesture_recovery(struct fts_ts_data *ts_data); +int fts_gesture_readdata(struct fts_ts_data *ts_data, u8 *data); +int fts_gesture_suspend(struct fts_ts_data *ts_data); +int fts_gesture_resume(struct fts_ts_data *ts_data); + +/* Apk and functions */ +int fts_create_apk_debug_channel(struct fts_ts_data *); +void fts_release_apk_debug_channel(struct fts_ts_data *); + +/* ADB functions */ +int fts_create_sysfs(struct fts_ts_data *ts_data); +int fts_remove_sysfs(struct fts_ts_data *ts_data); + +/* ESD */ +#if FTS_ESDCHECK_EN +int fts_esdcheck_init(struct fts_ts_data *ts_data); +int fts_esdcheck_exit(struct fts_ts_data *ts_data); +int fts_esdcheck_switch(bool enable); +int fts_esdcheck_proc_busy(bool proc_debug); +int fts_esdcheck_set_intr(bool intr); +int fts_esdcheck_suspend(void); +int fts_esdcheck_resume(void); +#endif + +/* Production test */ +#if FTS_TEST_EN +int fts_test_init(struct fts_ts_data *ts_data); +int fts_test_exit(struct fts_ts_data *ts_data); +#endif + +/* Point Report Check*/ +#if FTS_POINT_REPORT_CHECK_EN +int fts_point_report_check_init(struct fts_ts_data *ts_data); +int fts_point_report_check_exit(struct fts_ts_data *ts_data); +void fts_prc_queue_work(struct fts_ts_data *ts_data); +#endif + +/* FW upgrade */ +int fts_fwupg_init(struct fts_ts_data *ts_data); +int fts_fwupg_exit(struct fts_ts_data *ts_data); +int fts_enter_test_environment(bool test_state); + +/* Other */ +int fts_reset_proc(int hdelayms); +int fts_wait_tp_to_valid(void); +void fts_release_all_finger(void); +void fts_tp_state_recovery(struct fts_ts_data *ts_data); +int fts_ex_mode_init(struct fts_ts_data *ts_data); +int fts_ex_mode_exit(struct fts_ts_data *ts_data); +int fts_ex_mode_recovery(struct fts_ts_data *ts_data); + +void fts_irq_disable(void); +void fts_irq_enable(void); +int fts_ts_handle_trusted_touch_pvm(struct fts_ts_data *ts_data, int value); +int fts_ts_handle_trusted_touch_tvm(struct fts_ts_data *ts_data, int value); +#ifdef CONFIG_FTS_TRUSTED_TOUCH +#ifdef CONFIG_ARCH_QTI_VM +void fts_ts_trusted_touch_tvm_i2c_failure_report(struct fts_ts_data *fts_data); +#endif +#endif +#endif /* __LINUX_FOCALTECH_CORE_H__ */ diff --git a/qcom/opensource/touch-drivers/focaltech_touch/focaltech_esdcheck.c b/qcom/opensource/touch-drivers/focaltech_touch/focaltech_esdcheck.c new file mode 100644 index 0000000000..295f91d2f8 --- /dev/null +++ b/qcom/opensource/touch-drivers/focaltech_touch/focaltech_esdcheck.c @@ -0,0 +1,465 @@ +/* + * + * FocalTech TouchScreen driver. + * + * Copyright (c) 2012-2019, FocalTech Systems, Ltd., all rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/***************************************************************************** +* +* File Name: focaltech_esdcheck.c +* +* Author: Focaltech Driver Team +* +* Created: 2016-08-03 +* +* Abstract: ESD check function +* +* Version: v1.0 +* +* Revision History: +* v1.0: +* First release. By luougojin 2016-08-03 +* v1.1: By luougojin 2017-02-15 +* 1. Add LCD_ESD_PATCH to control idc_esdcheck_lcderror +*****************************************************************************/ + +/***************************************************************************** +* Included header files +*****************************************************************************/ +#include "focaltech_core.h" + +#if FTS_ESDCHECK_EN +/***************************************************************************** +* Private constant and macro definitions using #define +*****************************************************************************/ +#define ESDCHECK_WAIT_TIME 1000 /* ms */ +#define LCD_ESD_PATCH 0 +#define ESDCHECK_INTRCNT_MAX 2 + +/***************************************************************************** +* Private enumerations, structures and unions using typedef +*****************************************************************************/ +struct fts_esdcheck_st { + u8 mode : 1; /* 1- need check esd 0- no esd check */ + u8 suspend : 1; + u8 proc_debug : 1; /* apk or adb use */ + u8 intr : 1; /* 1- Interrupt trigger */ + u8 unused : 4; + u8 intr_cnt; + u8 flow_work_hold_cnt; /* Flow Work Cnt(reg0x91) keep a same value for x times. >=5 times is ESD, need reset */ + u8 flow_work_cnt_last; /* Save Flow Work Cnt(reg0x91) value */ + u32 hardware_reset_cnt; + u32 nack_cnt; + u32 dataerror_cnt; +}; + +/***************************************************************************** +* Static variables +*****************************************************************************/ +static struct fts_esdcheck_st fts_esdcheck_data; + +/***************************************************************************** +* Global variable or extern global variabls/functions +*****************************************************************************/ + +/***************************************************************************** +* Static function prototypes +*****************************************************************************/ + +/***************************************************************************** +* functions body +*****************************************************************************/ +#if LCD_ESD_PATCH +int lcd_need_reset; +static int tp_need_recovery; /* LCD reset cause Tp reset */ +int idc_esdcheck_lcderror(struct fts_ts_data *ts_data) +{ + int ret = 0; + u8 val = 0; + + FTS_DEBUG("check LCD ESD"); + if ( (tp_need_recovery == 1) && (lcd_need_reset == 0) ) { + tp_need_recovery = 0; + /* LCD reset, need recover TP state */ + fts_release_all_finger(); + fts_tp_state_recovery(ts_data); + } + + ret = fts_read_reg(FTS_REG_ESD_SATURATE, &val); + if ( ret < 0) { + FTS_ERROR("read reg0xED fail,ret:%d", ret); + return -EIO; + } + + if (val == 0xAA) { + /* + * 1. Set flag lcd_need_reset = 1; + * 2. LCD driver need reset(recovery) LCD and set lcd_need_reset to 0 + * 3. recover TP state + */ + FTS_INFO("LCD ESD, need execute LCD reset"); + lcd_need_reset = 1; + tp_need_recovery = 1; + } + + return 0; +} +#endif + +static int fts_esdcheck_tp_reset(struct fts_ts_data *ts_data) +{ + FTS_FUNC_ENTER(); + + fts_esdcheck_data.flow_work_hold_cnt = 0; + fts_esdcheck_data.hardware_reset_cnt++; + + fts_reset_proc(200); + fts_release_all_finger(); + fts_tp_state_recovery(ts_data); + + FTS_FUNC_EXIT(); + return 0; +} + +static bool get_chip_id(struct fts_ts_data *ts_data) +{ + int ret = 0; + int i = 0; + u8 reg_value = 0; + u8 reg_addr = 0; + u8 chip_id = ts_data->ic_info.ids.chip_idh; + + for (i = 0; i < 3; i++) { + reg_addr = FTS_REG_CHIP_ID; + ret = fts_read(®_addr, 1, ®_value, 1); + if (ret < 0) { + FTS_ERROR("read chip id fail,ret:%d", ret); + fts_esdcheck_data.nack_cnt++; + } else { + if (reg_value == chip_id) { + break; + } else { + FTS_DEBUG("read chip_id:%x,retry:%d", reg_value, i); + fts_esdcheck_data.dataerror_cnt++; + } + } + msleep(10); + } + + /* if can't get correct data in 3 times, then need hardware reset */ + if (i >= 3) { + FTS_ERROR("read chip id 3 times fail, need execute TP reset"); + return true; + } + + return false; +} + +/***************************************************************************** +* Name: get_flow_cnt +* Brief: Read flow cnt(0x91) +* Input: +* Output: +* Return: 1(true) - Reg 0x91(flow cnt) abnormal: hold a value for 5 times +* 0(false) - Reg 0x91(flow cnt) normal +*****************************************************************************/ +static bool get_flow_cnt(struct fts_ts_data *ts_data) +{ + int ret = 0; + u8 reg_value = 0; + u8 reg_addr = 0; + + reg_addr = FTS_REG_FLOW_WORK_CNT; + ret = fts_read(®_addr, 1, ®_value, 1); + if (ret < 0) { + FTS_ERROR("read reg0x91 fail,ret:%d", ret); + fts_esdcheck_data.nack_cnt++; + } else { + if ( reg_value == fts_esdcheck_data.flow_work_cnt_last ) { + FTS_DEBUG("reg0x91,val:%x,last:%x", reg_value, + fts_esdcheck_data.flow_work_cnt_last); + fts_esdcheck_data.flow_work_hold_cnt++; + } else { + fts_esdcheck_data.flow_work_hold_cnt = 0; + } + + fts_esdcheck_data.flow_work_cnt_last = reg_value; + } + + /* Flow Work Cnt keep a value for 5 times, need execute TP reset */ + if (fts_esdcheck_data.flow_work_hold_cnt >= 5) { + FTS_DEBUG("reg0x91 keep a value for 5 times, need execute TP reset"); + return true; + } + + return false; +} + +static int esdcheck_algorithm(struct fts_ts_data *ts_data) +{ + int ret = 0; + u8 reg_value = 0; + u8 reg_addr = 0; + bool hardware_reset = 0; + + /* 1. esdcheck is interrupt, then return */ + if (fts_esdcheck_data.intr == 1) { + fts_esdcheck_data.intr_cnt++; + if (fts_esdcheck_data.intr_cnt > ESDCHECK_INTRCNT_MAX) + fts_esdcheck_data.intr = 0; + else + return 0; + } + + /* 2. check power state, if suspend, no need check esd */ + if (fts_esdcheck_data.suspend == 1) { + FTS_DEBUG("In suspend, not check esd"); + /* because in suspend state, adb can be used, when upgrade FW, will + * active ESD check(active = 1); But in suspend, then will don't + * queue_delayed_work, when resume, don't check ESD again + */ + return 0; + } + + /* 3. check fts_esdcheck_data.proc_debug state, if 1-proc busy, no need check esd*/ + if (fts_esdcheck_data.proc_debug == 1) { + FTS_INFO("In apk/adb command mode, not check esd"); + return 0; + } + + /* 4. In factory mode, can't check esd */ + reg_addr = FTS_REG_WORKMODE; + ret = fts_read_reg(reg_addr, ®_value); + if ( ret < 0 ) { + fts_esdcheck_data.nack_cnt++; + } else if ( (reg_value & 0x70) != FTS_REG_WORKMODE_WORK_VALUE) { + FTS_DEBUG("not in work mode(%x), no check esd", reg_value); + return 0; + } + + /* 5. IDC esd check lcd default:close */ +#if LCD_ESD_PATCH + idc_esdcheck_lcderror(ts_data); +#endif + + /* 6. Get Chip ID */ + hardware_reset = get_chip_id(ts_data); + + /* 7. get Flow work cnt: 0x91 If no change for 5 times, then ESD and reset */ + if (!hardware_reset) { + hardware_reset = get_flow_cnt(ts_data); + } + + /* 8. If need hardware reset, then handle it here */ + if (hardware_reset == 1) { + FTS_DEBUG("NoACK=%d, Error Data=%d, Hardware Reset=%d", + fts_esdcheck_data.nack_cnt, + fts_esdcheck_data.dataerror_cnt, + fts_esdcheck_data.hardware_reset_cnt); + fts_esdcheck_tp_reset(ts_data); + } + + return 0; +} + +static void esdcheck_func(struct work_struct *work) +{ + struct fts_ts_data *ts_data = container_of(work, + struct fts_ts_data, esdcheck_work.work); + + if (ENABLE == fts_esdcheck_data.mode) { + esdcheck_algorithm(ts_data); + queue_delayed_work(ts_data->ts_workqueue, + &ts_data->esdcheck_work, + msecs_to_jiffies(ESDCHECK_WAIT_TIME)); + } +} + +int fts_esdcheck_set_intr(bool intr) +{ + /* interrupt don't add debug message */ + fts_esdcheck_data.intr = intr; + fts_esdcheck_data.intr_cnt = (u8)intr; + return 0; +} + +static int fts_esdcheck_get_status(void) +{ + /* interrupt don't add debug message */ + return fts_esdcheck_data.mode; +} + +/***************************************************************************** +* Name: fts_esdcheck_proc_busy +* Brief: When APK or ADB command access TP via driver, then need set proc_debug, +* then will not check ESD. +* Input: +* Output: +* Return: +*****************************************************************************/ +int fts_esdcheck_proc_busy(bool proc_debug) +{ + fts_esdcheck_data.proc_debug = proc_debug; + return 0; +} + +/***************************************************************************** +* Name: fts_esdcheck_switch +* Brief: FTS esd check function switch. +* Input: enable: 1 - Enable esd check +* 0 - Disable esd check +* Output: +* Return: +*****************************************************************************/ +int fts_esdcheck_switch(bool enable) +{ + struct fts_ts_data *ts_data = fts_data; + FTS_FUNC_ENTER(); + if (fts_esdcheck_data.mode == ENABLE) { + if (enable) { + FTS_DEBUG("ESD check start"); + fts_esdcheck_data.flow_work_hold_cnt = 0; + fts_esdcheck_data.flow_work_cnt_last = 0; + fts_esdcheck_data.intr = 0; + fts_esdcheck_data.intr_cnt = 0; + queue_delayed_work(ts_data->ts_workqueue, + &ts_data->esdcheck_work, + msecs_to_jiffies(ESDCHECK_WAIT_TIME)); + } else { + FTS_DEBUG("ESD check stop"); + cancel_delayed_work_sync(&ts_data->esdcheck_work); + } + } + + FTS_FUNC_EXIT(); + return 0; +} + +int fts_esdcheck_suspend(void) +{ + FTS_FUNC_ENTER(); + fts_esdcheck_switch(DISABLE); + fts_esdcheck_data.suspend = 1; + fts_esdcheck_data.intr = 0; + fts_esdcheck_data.intr_cnt = 0; + FTS_FUNC_EXIT(); + return 0; +} + +int fts_esdcheck_resume( void ) +{ + FTS_FUNC_ENTER(); + fts_esdcheck_switch(ENABLE); + fts_esdcheck_data.suspend = 0; + fts_esdcheck_data.intr = 0; + fts_esdcheck_data.intr_cnt = 0; + FTS_FUNC_EXIT(); + return 0; +} + +static ssize_t fts_esdcheck_store( + struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct input_dev *input_dev = fts_data->input_dev; + + mutex_lock(&input_dev->mutex); + if (FTS_SYSFS_ECHO_ON(buf)) { + FTS_DEBUG("enable esdcheck"); + fts_esdcheck_data.mode = ENABLE; + fts_esdcheck_switch(ENABLE); + } else if (FTS_SYSFS_ECHO_OFF(buf)) { + FTS_DEBUG("disable esdcheck"); + fts_esdcheck_switch(DISABLE); + fts_esdcheck_data.mode = DISABLE; + } + mutex_unlock(&input_dev->mutex); + + return count; +} + +static ssize_t fts_esdcheck_show( + struct device *dev, struct device_attribute *attr, char *buf) +{ + int count; + struct input_dev *input_dev = fts_data->input_dev; + + mutex_lock(&input_dev->mutex); + count = snprintf(buf, PAGE_SIZE, "Esd check: %s\n", \ + fts_esdcheck_get_status() ? "On" : "Off"); + mutex_unlock(&input_dev->mutex); + + return count; +} + +/* sysfs esd node + * read example: cat fts_esd_mode ---read esd mode + * write example:echo 01 > fts_esd_mode ---make esdcheck enable + * + */ +static DEVICE_ATTR (fts_esd_mode, S_IRUGO | S_IWUSR, fts_esdcheck_show, fts_esdcheck_store); + +static struct attribute *fts_esd_mode_attrs[] = { + + &dev_attr_fts_esd_mode.attr, + NULL, +}; + +static struct attribute_group fts_esd_group = { + .attrs = fts_esd_mode_attrs, +}; + +int fts_create_esd_sysfs(struct device *dev) +{ + int ret = 0; + + ret = sysfs_create_group(&dev->kobj, &fts_esd_group); + if ( ret != 0) { + FTS_ERROR("fts_create_esd_sysfs(sysfs) create fail"); + sysfs_remove_group(&dev->kobj, &fts_esd_group); + return ret; + } + return 0; +} + +int fts_esdcheck_init(struct fts_ts_data *ts_data) +{ + FTS_FUNC_ENTER(); + + if (ts_data->ts_workqueue) { + INIT_DELAYED_WORK(&ts_data->esdcheck_work, esdcheck_func); + } else { + FTS_ERROR("fts workqueue is NULL, can't run esd check func!"); + return -EINVAL; + } + + memset((u8 *)&fts_esdcheck_data, 0, sizeof(struct fts_esdcheck_st)); + + fts_esdcheck_data.mode = ENABLE; + fts_esdcheck_data.intr = 0; + fts_esdcheck_data.intr_cnt = 0; + fts_esdcheck_switch(ENABLE); + fts_create_esd_sysfs(ts_data->dev); + FTS_FUNC_EXIT(); + return 0; +} + +int fts_esdcheck_exit(struct fts_ts_data *ts_data) +{ + sysfs_remove_group(&ts_data->dev->kobj, &fts_esd_group); + return 0; +} + +#endif /* FTS_ESDCHECK_EN */ + diff --git a/qcom/opensource/touch-drivers/focaltech_touch/focaltech_ex_fun.c b/qcom/opensource/touch-drivers/focaltech_touch/focaltech_ex_fun.c new file mode 100644 index 0000000000..518a7682cd --- /dev/null +++ b/qcom/opensource/touch-drivers/focaltech_touch/focaltech_ex_fun.c @@ -0,0 +1,1229 @@ +/* + * + * FocalTech TouchScreen driver. + * + * Copyright (c) 2012-2019, Focaltech Ltd. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/***************************************************************************** +* +* File Name: Focaltech_ex_fun.c +* +* Author: Focaltech Driver Team +* +* Created: 2016-08-08 +* +* Abstract: +* +* Reference: +* +*****************************************************************************/ + +/***************************************************************************** +* 1.Included header files +*****************************************************************************/ +#include +#include "focaltech_core.h" + +/***************************************************************************** +* Private constant and macro definitions using #define +*****************************************************************************/ +#define PROC_UPGRADE 0 +#define PROC_READ_REGISTER 1 +#define PROC_WRITE_REGISTER 2 +#define PROC_AUTOCLB 4 +#define PROC_UPGRADE_INFO 5 +#define PROC_WRITE_DATA 6 +#define PROC_READ_DATA 7 +#define PROC_SET_TEST_FLAG 8 +#define PROC_SET_SLAVE_ADDR 10 +#define PROC_HW_RESET 11 +#define PROC_READ_STATUS 12 +#define PROC_SET_BOOT_MODE 13 +#define PROC_ENTER_TEST_ENVIRONMENT 14 +#define PROC_NAME "ftxxxx-debug" +#define PROC_BUF_SIZE 256 + +/***************************************************************************** +* Private enumerations, structures and unions using typedef +*****************************************************************************/ +enum { + RWREG_OP_READ = 0, + RWREG_OP_WRITE = 1, +}; + +/***************************************************************************** +* Static variables +*****************************************************************************/ +static struct rwreg_operation_t { + int type; /* 0: read, 1: write */ + int reg; /* register */ + int len; /* read/write length */ + int val; /* length = 1; read: return value, write: op return */ + int res; /* 0: success, otherwise: fail */ + char *opbuf; /* length >= 1, read return value, write: op return */ +} rw_op; + +/***************************************************************************** +* Global variable or extern global variabls/functions +*****************************************************************************/ + +/***************************************************************************** +* Static function prototypes +*****************************************************************************/ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0)) +static ssize_t fts_debug_write( + struct file *filp, const char __user *buff, size_t count, loff_t *ppos) +{ + u8 *writebuf = NULL; + u8 tmpbuf[PROC_BUF_SIZE] = { 0 }; + int buflen = count; + int writelen = 0; + int ret = 0; + char tmp[PROC_BUF_SIZE]; + struct fts_ts_data *ts_data = fts_data; + struct ftxxxx_proc *proc = &ts_data->proc; + + if ((buflen <= 1) || (buflen > PAGE_SIZE)) { + FTS_ERROR("apk proc wirte count(%d>%d) fail", buflen, (int)PAGE_SIZE); + return -EINVAL; + } + + if (buflen > PROC_BUF_SIZE) { + writebuf = (u8 *)kzalloc(buflen * sizeof(u8), GFP_KERNEL); + if (NULL == writebuf) { + FTS_ERROR("apk proc wirte buf zalloc fail"); + return -ENOMEM; + } + } else { + writebuf = tmpbuf; + } + + if (copy_from_user(writebuf, buff, buflen)) { + FTS_ERROR("[APK]: copy from user error!!"); + ret = -EFAULT; + goto proc_write_err; + } + + proc->opmode = writebuf[0]; + switch (proc->opmode) { + case PROC_SET_TEST_FLAG: + FTS_DEBUG("[APK]: PROC_SET_TEST_FLAG = %x", writebuf[1]); + if (writebuf[1] == 0) { +#if FTS_ESDCHECK_EN + fts_esdcheck_switch(ENABLE); +#endif + } else { +#if FTS_ESDCHECK_EN + fts_esdcheck_switch(DISABLE); +#endif + } + break; + + case PROC_READ_REGISTER: + proc->cmd[0] = writebuf[1]; + break; + + case PROC_WRITE_REGISTER: + ret = fts_write_reg(writebuf[1], writebuf[2]); + if (ret < 0) { + FTS_ERROR("PROC_WRITE_REGISTER write error"); + goto proc_write_err; + } + break; + + case PROC_READ_DATA: + writelen = buflen - 1; + if (writelen >= FTX_MAX_COMMMAND_LENGTH) { + FTS_ERROR("cmd(PROC_READ_DATA) len(%d) fail", writelen); + goto proc_write_err; + } + memcpy(proc->cmd, writebuf + 1, writelen); + proc->cmd_len = writelen; + ret = fts_write(writebuf + 1, writelen); + if (ret < 0) { + FTS_ERROR("PROC_READ_DATA write error"); + goto proc_write_err; + } + break; + + case PROC_WRITE_DATA: + writelen = buflen - 1; + ret = fts_write(writebuf + 1, writelen); + if (ret < 0) { + FTS_ERROR("PROC_WRITE_DATA write error"); + goto proc_write_err; + } + break; + + case PROC_SET_SLAVE_ADDR: + break; + + case PROC_HW_RESET: + snprintf(tmp, PROC_BUF_SIZE, "%s", writebuf + 1); + tmp[buflen - 1] = '\0'; + if (strncmp(tmp, "focal_driver", 12) == 0) { + FTS_INFO("APK execute HW Reset"); + fts_reset_proc(0); + } + break; + + case PROC_SET_BOOT_MODE: + FTS_DEBUG("[APK]: PROC_SET_BOOT_MODE = %x", writebuf[1]); + if (0 == writebuf[1]) { + ts_data->fw_is_running = true; + } else { + ts_data->fw_is_running = false; + } + break; + + case PROC_ENTER_TEST_ENVIRONMENT: + FTS_DEBUG("[APK]: PROC_ENTER_TEST_ENVIRONMENT = %x", writebuf[1]); + if (0 == writebuf[1]) { + fts_enter_test_environment(0); + } else { + fts_enter_test_environment(1); + } + break; + + default: + break; + } + + ret = buflen; +proc_write_err: + if ((buflen > PROC_BUF_SIZE) && writebuf) { + kfree(writebuf); + writebuf = NULL; + } + return ret; +} + +static ssize_t fts_debug_read( + struct file *filp, char __user *buff, size_t count, loff_t *ppos) +{ + int ret = 0; + int num_read_chars = 0; + int buflen = count; + u8 *readbuf = NULL; + u8 tmpbuf[PROC_BUF_SIZE] = { 0 }; + struct fts_ts_data *ts_data = fts_data; + struct ftxxxx_proc *proc = &ts_data->proc; + + if ((buflen <= 0) || (buflen > PAGE_SIZE)) { + FTS_ERROR("apk proc read count(%d>%d) fail", buflen, (int)PAGE_SIZE); + return -EINVAL; + } + + if (buflen > PROC_BUF_SIZE) { + readbuf = (u8 *)kzalloc(buflen * sizeof(u8), GFP_KERNEL); + if (NULL == readbuf) { + FTS_ERROR("apk proc wirte buf zalloc fail"); + return -ENOMEM; + } + } else { + readbuf = tmpbuf; + } + +#if FTS_ESDCHECK_EN + fts_esdcheck_proc_busy(1); +#endif + + switch (proc->opmode) { + case PROC_READ_REGISTER: + num_read_chars = 1; + ret = fts_read_reg(proc->cmd[0], &readbuf[0]); + if (ret < 0) { + FTS_ERROR("PROC_READ_REGISTER read error"); + goto proc_read_err; + } + break; + + case PROC_WRITE_REGISTER: + break; + + case PROC_READ_DATA: + num_read_chars = buflen; + ret = fts_read(NULL, 0, readbuf, num_read_chars); + if (ret < 0) { + FTS_ERROR("PROC_READ_DATA read error"); + goto proc_read_err; + } + break; + + case PROC_WRITE_DATA: + break; + + default: + break; + } + +#if FTS_ESDCHECK_EN + fts_esdcheck_proc_busy(0); +#endif + + if (copy_to_user(buff, readbuf, num_read_chars)) { + FTS_ERROR("copy to user error"); + ret = -EFAULT; + goto proc_read_err; + } + + ret = num_read_chars; +proc_read_err: + if ((buflen > PROC_BUF_SIZE) && readbuf) { + kfree(readbuf); + readbuf = NULL; + } + return ret; +} + +static const struct proc_ops fts_proc_fops = { + .proc_read = fts_debug_read, + .proc_write = fts_debug_write, +}; +#else +static int fts_debug_write(struct file *filp, + const char __user *buff, unsigned long len, void *data) +{ + u8 *writebuf = NULL; + u8 tmpbuf[PROC_BUF_SIZE] = { 0 }; + int buflen = count; + int writelen = 0; + int ret = 0; + char tmp[PROC_BUF_SIZE]; + struct fts_ts_data *ts_data = fts_data; + struct ftxxxx_proc *proc = &ts_data->proc; + + if ((buflen <= 1) || (buflen > PAGE_SIZE)) { + FTS_ERROR("apk proc wirte count(%d>%d) fail", buflen, (int)PAGE_SIZE); + return -EINVAL; + } + + if (buflen > PROC_BUF_SIZE) { + writebuf = (u8 *)kzalloc(buflen * sizeof(u8), GFP_KERNEL); + if (NULL == writebuf) { + FTS_ERROR("apk proc wirte buf zalloc fail"); + return -ENOMEM; + } + } else { + writebuf = tmpbuf; + } + + if (copy_from_user(writebuf, buff, buflen)) { + FTS_ERROR("[APK]: copy from user error!!"); + ret = -EFAULT; + goto proc_write_err; + } + + proc->opmode = writebuf[0]; + switch (proc->opmode) { + case PROC_SET_TEST_FLAG: + FTS_DEBUG("[APK]: PROC_SET_TEST_FLAG = %x", writebuf[1]); + if (writebuf[1] == 0) { +#if FTS_ESDCHECK_EN + fts_esdcheck_switch(ENABLE); +#endif + } else { +#if FTS_ESDCHECK_EN + fts_esdcheck_switch(DISABLE); +#endif + } + break; + + case PROC_READ_REGISTER: + proc->cmd[0] = writebuf[1]; + break; + + case PROC_WRITE_REGISTER: + ret = fts_write_reg(writebuf[1], writebuf[2]); + if (ret < 0) { + FTS_ERROR("PROC_WRITE_REGISTER write error"); + goto proc_write_err; + } + break; + + case PROC_READ_DATA: + writelen = buflen - 1; + if (writelen >= FTX_MAX_COMMMAND_LENGTH) { + FTS_ERROR("cmd(PROC_READ_DATA) length(%d) fail", writelen); + goto proc_write_err; + } + memcpy(proc->cmd, writebuf + 1, writelen); + proc->cmd_len = writelen; + ret = fts_write(writebuf + 1, writelen); + if (ret < 0) { + FTS_ERROR("PROC_READ_DATA write error"); + goto proc_write_err; + } + break; + + case PROC_WRITE_DATA: + writelen = buflen - 1; + ret = fts_write(writebuf + 1, writelen); + if (ret < 0) { + FTS_ERROR("PROC_WRITE_DATA write error"); + goto proc_write_err; + } + break; + + case PROC_SET_SLAVE_ADDR: + break; + + case PROC_HW_RESET: + snprintf(tmp, PROC_BUF_SIZE, "%s", writebuf + 1); + tmp[buflen - 1] = '\0'; + if (strncmp(tmp, "focal_driver", 12) == 0) { + FTS_INFO("APK execute HW Reset"); + fts_reset_proc(0); + } + break; + + case PROC_SET_BOOT_MODE: + FTS_DEBUG("[APK]: PROC_SET_BOOT_MODE = %x", writebuf[1]); + if (0 == writebuf[1]) { + ts_data->fw_is_running = true; + } else { + ts_data->fw_is_running = false; + } + break; + + case PROC_ENTER_TEST_ENVIRONMENT: + FTS_DEBUG("[APK]: PROC_ENTER_TEST_ENVIRONMENT = %x", writebuf[1]); + if (0 == writebuf[1]) { + fts_enter_test_environment(0); + } else { + fts_enter_test_environment(1); + } + break; + + default: + break; + } + + ret = buflen; +proc_write_err: + if ((buflen > PROC_BUF_SIZE) && writebuf) { + kfree(writebuf); + writebuf = NULL; + } + return ret; +} + +static int fts_debug_read( + char *page, char **start, off_t off, int count, int *eof, void *data ) +{ + int ret = 0; + int num_read_chars = 0; + int buflen = count; + u8 *readbuf = NULL; + u8 tmpbuf[PROC_BUF_SIZE] = { 0 }; + struct fts_ts_data *ts_data = fts_data; + struct ftxxxx_proc *proc = &ts_data->proc; + + if ((buflen <= 0) || (buflen > PAGE_SIZE)) { + FTS_ERROR("apk proc read count(%d>%d) fail", buflen, (int)PAGE_SIZE); + return -EINVAL; + } + + if (buflen > PROC_BUF_SIZE) { + readbuf = (u8 *)kzalloc(buflen * sizeof(u8), GFP_KERNEL); + if (NULL == readbuf) { + FTS_ERROR("apk proc wirte buf zalloc fail"); + return -ENOMEM; + } + } else { + readbuf = tmpbuf; + } + +#if FTS_ESDCHECK_EN + fts_esdcheck_proc_busy(1); +#endif + + switch (proc->opmode) { + case PROC_READ_REGISTER: + num_read_chars = 1; + ret = fts_read_reg(proc->cmd[0], &readbuf[0]); + if (ret < 0) { + FTS_ERROR("PROC_READ_REGISTER read error"); + goto proc_read_err; + } + break; + + case PROC_WRITE_REGISTER: + break; + + case PROC_READ_DATA: + num_read_chars = buflen; + ret = fts_read(NULL, 0, readbuf, num_read_chars); + if (ret < 0) { + FTS_ERROR("PROC_READ_DATA read error"); + goto proc_read_err; + } + break; + + case PROC_WRITE_DATA: + break; + + default: + break; + } + +#if FTS_ESDCHECK_EN + fts_esdcheck_proc_busy(0); +#endif + + if (copy_to_user(buff, readbuf, num_read_chars)) { + FTS_ERROR("copy to user error"); + ret = -EFAULT; + goto proc_read_err; + } + + ret = num_read_chars; +proc_read_err: + if ((buflen > PROC_BUF_SIZE) && readbuf) { + kfree(readbuf); + readbuf = NULL; + } + return ret; +} +#endif + +int fts_create_apk_debug_channel(struct fts_ts_data *ts_data) +{ + struct ftxxxx_proc *proc = &ts_data->proc; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0)) + proc->proc_entry = proc_create(PROC_NAME, 0777, NULL, &fts_proc_fops); + if (NULL == proc->proc_entry) { + FTS_ERROR("create proc entry fail"); + return -ENOMEM; + } +#else + proc->proc_entry = create_proc_entry(PROC_NAME, 0777, NULL); + if (NULL == proc->proc_entry) { + FTS_ERROR("create proc entry fail"); + return -ENOMEM; + } + proc->proc_entry->write_proc = fts_debug_write; + proc->proc_entry->read_proc = fts_debug_read; +#endif + + FTS_INFO("Create proc entry success!"); + return 0; +} + +void fts_release_apk_debug_channel(struct fts_ts_data *ts_data) +{ + struct ftxxxx_proc *proc = &ts_data->proc; + + if (proc->proc_entry) { +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0)) + proc_remove(proc->proc_entry); +#else + remove_proc_entry(PROC_NAME, NULL); +#endif + } +} + +/************************************************************************ + * sysfs interface + ***********************************************************************/ +/* fts_hw_reset interface */ +static ssize_t fts_hw_reset_show( + struct device *dev, struct device_attribute *attr, char *buf) +{ + struct input_dev *input_dev = fts_data->input_dev; + ssize_t count = 0; + + mutex_lock(&input_dev->mutex); + fts_reset_proc(0); + count = snprintf(buf, PAGE_SIZE, "hw reset executed\n"); + mutex_unlock(&input_dev->mutex); + + return count; +} + +static ssize_t fts_hw_reset_store( + struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + return -EPERM; +} + +/* fts_irq interface */ +static ssize_t fts_irq_show( + struct device *dev, struct device_attribute *attr, char *buf) +{ + ssize_t count = 0; + struct irq_desc *desc = irq_to_desc(fts_data->irq); + + count = snprintf(buf, PAGE_SIZE, "irq_depth:%d\n", desc->depth); + + return count; +} + +static ssize_t fts_irq_store( + struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct input_dev *input_dev = fts_data->input_dev; + + mutex_lock(&input_dev->mutex); + if (FTS_SYSFS_ECHO_ON(buf)) { + FTS_INFO("enable irq"); + fts_irq_enable(); + } else if (FTS_SYSFS_ECHO_OFF(buf)) { + FTS_INFO("disable irq"); + fts_irq_disable(); + } + mutex_unlock(&input_dev->mutex); + return count; +} + +/* fts_boot_mode interface */ +static ssize_t fts_bootmode_store( + struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct input_dev *input_dev = fts_data->input_dev; + + FTS_FUNC_ENTER(); + mutex_lock(&input_dev->mutex); + if (FTS_SYSFS_ECHO_ON(buf)) { + FTS_INFO("[EX-FUN]set to boot mode"); + fts_data->fw_is_running = false; + } else if (FTS_SYSFS_ECHO_OFF(buf)) { + FTS_INFO("[EX-FUN]set to fw mode"); + fts_data->fw_is_running = true; + } + mutex_unlock(&input_dev->mutex); + FTS_FUNC_EXIT(); + + return count; +} + +static ssize_t fts_bootmode_show( + struct device *dev, struct device_attribute *attr, char *buf) +{ + ssize_t count = 0; + struct input_dev *input_dev = fts_data->input_dev; + + FTS_FUNC_ENTER(); + mutex_lock(&input_dev->mutex); + if (true == fts_data->fw_is_running) { + count = snprintf(buf, PAGE_SIZE, "tp is in fw mode\n"); + } else { + count = snprintf(buf, PAGE_SIZE, "tp is in boot mode\n"); + } + mutex_unlock(&input_dev->mutex); + FTS_FUNC_EXIT(); + + return count; +} + +/* fts_tpfwver interface */ +static ssize_t fts_fw_version_show( + struct device *dev, struct device_attribute *attr, char *buf) +{ + struct fts_ts_data *ts_data = fts_data; + struct input_dev *input_dev = ts_data->input_dev; + ssize_t num_read_chars = 0; + u8 fwver = 0; + + mutex_lock(&input_dev->mutex); + +#if FTS_ESDCHECK_EN + fts_esdcheck_proc_busy(1); +#endif + fts_read_reg(FTS_REG_FW_VER, &fwver); +#if FTS_ESDCHECK_EN + fts_esdcheck_proc_busy(0); +#endif + if ((fwver == 0xFF) || (fwver == 0x00)) + num_read_chars = snprintf(buf, PAGE_SIZE, "get tp fw version fail!\n"); + else + num_read_chars = snprintf(buf, PAGE_SIZE, "%02x\n", fwver); + + mutex_unlock(&input_dev->mutex); + return num_read_chars; +} + +static ssize_t fts_fw_version_store( + struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + return -EPERM; +} + +/* fts_rw_reg */ +static ssize_t fts_tprwreg_show( + struct device *dev, struct device_attribute *attr, char *buf) +{ + int count; + int i; + struct input_dev *input_dev = fts_data->input_dev; + + mutex_lock(&input_dev->mutex); + + if (rw_op.len < 0) { + count = snprintf(buf, PAGE_SIZE, "Invalid cmd line\n"); + } else if (rw_op.len == 1) { + if (RWREG_OP_READ == rw_op.type) { + if (rw_op.res == 0) { + count = snprintf(buf, PAGE_SIZE, "Read %02X: %02X\n", rw_op.reg, rw_op.val); + } else { + count = snprintf(buf, PAGE_SIZE, "Read %02X failed, ret: %d\n", rw_op.reg, rw_op.res); + } + } else { + if (rw_op.res == 0) { + count = snprintf(buf, PAGE_SIZE, "Write %02X, %02X success\n", rw_op.reg, rw_op.val); + } else { + count = snprintf(buf, PAGE_SIZE, "Write %02X failed, ret: %d\n", rw_op.reg, rw_op.res); + } + } + } else { + if (RWREG_OP_READ == rw_op.type) { + count = snprintf(buf, PAGE_SIZE, "Read Reg: [%02X]-[%02X]\n", rw_op.reg, rw_op.reg + rw_op.len); + count += snprintf(buf + count, PAGE_SIZE, "Result: "); + if (rw_op.res) { + count += snprintf(buf + count, PAGE_SIZE, "failed, ret: %d\n", rw_op.res); + } else { + if (rw_op.opbuf) { + for (i = 0; i < rw_op.len; i++) { + count += snprintf(buf + count, PAGE_SIZE, "%02X ", rw_op.opbuf[i]); + } + count += snprintf(buf + count, PAGE_SIZE, "\n"); + } + } + } else { + count = snprintf(buf, PAGE_SIZE, "Write Reg: [%02X]-[%02X]\n", rw_op.reg, rw_op.reg + rw_op.len - 1); + count += snprintf(buf + count, PAGE_SIZE, "Write Data: "); + if (rw_op.opbuf) { + for (i = 1; i < rw_op.len; i++) { + count += snprintf(buf + count, PAGE_SIZE, "%02X ", rw_op.opbuf[i]); + } + count += snprintf(buf + count, PAGE_SIZE, "\n"); + } + if (rw_op.res) { + count += snprintf(buf + count, PAGE_SIZE, "Result: failed, ret: %d\n", rw_op.res); + } else { + count += snprintf(buf + count, PAGE_SIZE, "Result: success\n"); + } + } + /*if (rw_op.opbuf) { + * kfree(rw_op.opbuf); + * rw_op.opbuf = NULL; + *} + */ + } + mutex_unlock(&input_dev->mutex); + + return count; +} + +static int shex_to_int(const char *hex_buf, int size) +{ + int i; + int base = 1; + int value = 0; + char single; + + for (i = size - 1; i >= 0; i--) { + single = hex_buf[i]; + + if ((single >= '0') && (single <= '9')) { + value += (single - '0') * base; + } else if ((single >= 'a') && (single <= 'z')) { + value += (single - 'a' + 10) * base; + } else if ((single >= 'A') && (single <= 'Z')) { + value += (single - 'A' + 10) * base; + } else { + return -EINVAL; + } + + base *= 16; + } + + return value; +} + + +static u8 shex_to_u8(const char *hex_buf, int size) +{ + return (u8)shex_to_int(hex_buf, size); +} +/* + * Format buf: + * [0]: '0' write, '1' read(reserved) + * [1-2]: addr, hex + * [3-4]: length, hex + * [5-6]...[n-(n+1)]: data, hex + */ +static int fts_parse_buf(const char *buf, size_t cmd_len) +{ + int length; + int i; + char *tmpbuf; + + rw_op.reg = shex_to_u8(buf + 1, 2); + length = shex_to_int(buf + 3, 2); + + if (buf[0] == '1') { + rw_op.len = length; + rw_op.type = RWREG_OP_READ; + FTS_DEBUG("read %02X, %d bytes", rw_op.reg, rw_op.len); + } else { + if (cmd_len < (length * 2 + 5)) { + pr_err("data invalided!\n"); + return -EINVAL; + } + FTS_DEBUG("write %02X, %d bytes", rw_op.reg, length); + + /* first byte is the register addr */ + rw_op.type = RWREG_OP_WRITE; + rw_op.len = length + 1; + } + + if (rw_op.len > 0) { + tmpbuf = (char *)kzalloc(rw_op.len, GFP_KERNEL); + if (!tmpbuf) { + FTS_ERROR("allocate memory failed!\n"); + return -ENOMEM; + } + + if (RWREG_OP_WRITE == rw_op.type) { + tmpbuf[0] = rw_op.reg & 0xFF; + FTS_DEBUG("write buffer: "); + for (i = 1; i < rw_op.len; i++) { + tmpbuf[i] = shex_to_u8(buf + 5 + i * 2 - 2, 2); + FTS_DEBUG("buf[%d]: %02X", i, tmpbuf[i] & 0xFF); + } + } + rw_op.opbuf = tmpbuf; + } + + return rw_op.len; +} + +static ssize_t fts_tprwreg_store( + struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct input_dev *input_dev = fts_data->input_dev; + ssize_t cmd_length = 0; + + mutex_lock(&input_dev->mutex); + cmd_length = count - 1; + + if (rw_op.opbuf) { + kfree(rw_op.opbuf); + rw_op.opbuf = NULL; + } + + FTS_DEBUG("cmd len: %d, buf: %s", (int)cmd_length, buf); + /* compatible old ops */ + if (2 == cmd_length) { + rw_op.type = RWREG_OP_READ; + rw_op.len = 1; + rw_op.reg = shex_to_int(buf, 2); + } else if (4 == cmd_length) { + rw_op.type = RWREG_OP_WRITE; + rw_op.len = 1; + rw_op.reg = shex_to_int(buf, 2); + rw_op.val = shex_to_int(buf + 2, 2); + } else if (cmd_length < 5) { + FTS_ERROR("Invalid cmd buffer"); + mutex_unlock(&input_dev->mutex); + return -EINVAL; + } else { + rw_op.len = fts_parse_buf(buf, cmd_length); + } + +#if FTS_ESDCHECK_EN + fts_esdcheck_proc_busy(1); +#endif + if (rw_op.len < 0) { + FTS_ERROR("cmd buffer error!"); + goto exit; + } + + if (RWREG_OP_READ == rw_op.type) { + if (rw_op.len == 1) { + u8 reg, val; + reg = rw_op.reg & 0xFF; + rw_op.res = fts_read_reg(reg, &val); + rw_op.val = val; + } else { + char reg; + reg = rw_op.reg & 0xFF; + + rw_op.res = fts_read(®, 1, rw_op.opbuf, rw_op.len); + } + + if (rw_op.res < 0) { + FTS_ERROR("Could not read 0x%02x", rw_op.reg); + } else { + FTS_INFO("read 0x%02x, %d bytes successful", rw_op.reg, rw_op.len); + rw_op.res = 0; + } + + } else { + if (rw_op.len == 1) { + u8 reg, val; + reg = rw_op.reg & 0xFF; + val = rw_op.val & 0xFF; + rw_op.res = fts_write_reg(reg, val); + } else { + rw_op.res = fts_write(rw_op.opbuf, rw_op.len); + } + if (rw_op.res < 0) { + FTS_ERROR("Could not write 0x%02x", rw_op.reg); + + } else { + FTS_INFO("Write 0x%02x, %d bytes successful", rw_op.val, rw_op.len); + rw_op.res = 0; + } + } + +exit: +#if FTS_ESDCHECK_EN + fts_esdcheck_proc_busy(0); +#endif + mutex_unlock(&input_dev->mutex); + + return count; +} + +/* fts_driver_info interface */ +static ssize_t fts_driverinfo_show( + struct device *dev, struct device_attribute *attr, char *buf) +{ + int count = 0; + struct fts_ts_data *ts_data = fts_data; + struct fts_ts_platform_data *pdata = ts_data->pdata; + struct input_dev *input_dev = ts_data->input_dev; + + mutex_lock(&input_dev->mutex); + count += scnprintf(buf + count, PAGE_SIZE, "Driver Ver:%s\n", FTS_DRIVER_VERSION); + + count += snprintf(buf + count, PAGE_SIZE, "Resolution:(%d,%d)~(%d,%d)\n", + pdata->x_min, pdata->y_min, pdata->x_max, pdata->y_max); + + count += snprintf(buf + count, PAGE_SIZE, "Max Touchs:%d\n", pdata->max_touch_number); + + count += scnprintf(buf + count, PAGE_SIZE, "reset gpio:%d,int gpio:%d,irq:%d\n", + pdata->reset_gpio, pdata->irq_gpio, ts_data->irq); + + count += scnprintf(buf + count, PAGE_SIZE, "IC ID:0x%02x%02x\n", + ts_data->ic_info.ids.chip_idh, ts_data->ic_info.ids.chip_idl); + if (ts_data->bus_type == BUS_TYPE_I2C) + count += scnprintf(buf + count, PAGE_SIZE, "BUS:%s,addr:0x%x\n", + "I2C", ts_data->client->addr); + else + count += scnprintf(buf + count, PAGE_SIZE, + "BUS:%s,mode:%d,max_freq:%d\n", "SPI", + ts_data->spi->mode, ts_data->spi->max_speed_hz); + + mutex_unlock(&input_dev->mutex); + + return count; +} + +static ssize_t fts_driverinfo_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + return -EPERM; +} + +/* fts_dump_reg interface */ +static ssize_t fts_dumpreg_show( + struct device *dev, struct device_attribute *attr, char *buf) +{ + int count = 0; + u8 val = 0; + struct input_dev *input_dev = fts_data->input_dev; + + mutex_lock(&input_dev->mutex); +#if FTS_ESDCHECK_EN + fts_esdcheck_proc_busy(1); +#endif + fts_read_reg(FTS_REG_POWER_MODE, &val); + count += snprintf(buf + count, PAGE_SIZE, "Power Mode:0x%02x\n", val); + + fts_read_reg(FTS_REG_FW_VER, &val); + count += snprintf(buf + count, PAGE_SIZE, "FW Ver:0x%02x\n", val); + + fts_read_reg(FTS_REG_LIC_VER, &val); + count += snprintf(buf + count, PAGE_SIZE, "LCD Initcode Ver:0x%02x\n", val); + + fts_read_reg(FTS_REG_IDE_PARA_VER_ID, &val); + count += snprintf(buf + count, PAGE_SIZE, "Param Ver:0x%02x\n", val); + + fts_read_reg(FTS_REG_IDE_PARA_STATUS, &val); + count += snprintf(buf + count, PAGE_SIZE, "Param status:0x%02x\n", val); + + fts_read_reg(FTS_REG_VENDOR_ID, &val); + count += snprintf(buf + count, PAGE_SIZE, "Vendor ID:0x%02x\n", val); + + fts_read_reg(FTS_REG_LCD_BUSY_NUM, &val); + count += snprintf(buf + count, PAGE_SIZE, "LCD Busy Number:0x%02x\n", val); + + fts_read_reg(FTS_REG_GESTURE_EN, &val); + count += snprintf(buf + count, PAGE_SIZE, "Gesture Mode:0x%02x\n", val); + + fts_read_reg(FTS_REG_CHARGER_MODE_EN, &val); + count += snprintf(buf + count, PAGE_SIZE, "charge stat:0x%02x\n", val); + + fts_read_reg(FTS_REG_INT_CNT, &val); + count += snprintf(buf + count, PAGE_SIZE, "INT count:0x%02x\n", val); + + fts_read_reg(FTS_REG_FLOW_WORK_CNT, &val); + count += snprintf(buf + count, PAGE_SIZE, "ESD count:0x%02x\n", val); +#if FTS_ESDCHECK_EN + fts_esdcheck_proc_busy(0); +#endif + + mutex_unlock(&input_dev->mutex); + + return count; +} + +static ssize_t fts_dumpreg_store( + struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + return -EPERM; +} + +/* fts_dump_reg interface */ +static ssize_t fts_tpbuf_show( + struct device *dev, struct device_attribute *attr, char *buf) +{ + int count = 0; + int i = 0; + struct input_dev *input_dev = fts_data->input_dev; + + mutex_lock(&input_dev->mutex); + count += snprintf(buf + count, PAGE_SIZE, "touch point buffer:\n"); + for (i = 0; i < fts_data->pnt_buf_size; i++) { + count += snprintf(buf + count, PAGE_SIZE, "%02x ", + fts_data->point_buf[i]); + } + count += snprintf(buf + count, PAGE_SIZE, "\n"); + mutex_unlock(&input_dev->mutex); + + return count; +} + +static ssize_t fts_tpbuf_store( + struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + return -EPERM; +} + +/* fts_log_level interface */ +static ssize_t fts_log_level_show( + struct device *dev, struct device_attribute *attr, char *buf) +{ + int count = 0; + struct input_dev *input_dev = fts_data->input_dev; + + mutex_lock(&input_dev->mutex); + count += snprintf(buf + count, PAGE_SIZE, "log level:%d\n", + fts_data->log_level); + mutex_unlock(&input_dev->mutex); + + return count; +} + +static ssize_t fts_log_level_store( + struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int value = 0; + struct input_dev *input_dev = fts_data->input_dev; + + FTS_FUNC_ENTER(); + mutex_lock(&input_dev->mutex); + sscanf(buf, "%d", &value); + FTS_DEBUG("log level:%d->%d", fts_data->log_level, value); + fts_data->log_level = value; + mutex_unlock(&input_dev->mutex); + FTS_FUNC_EXIT(); + + return count; +} + +#ifdef CONFIG_FTS_TRUSTED_TOUCH + +static ssize_t trusted_touch_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fts_ts_data *info = fts_data; + + return scnprintf(buf, PAGE_SIZE, "%d", + atomic_read(&info->trusted_touch_enabled)); +} + +static ssize_t trusted_touch_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct fts_ts_data *info = fts_data; + unsigned long value; + int err = 0; + + if (count > 2) + return -EINVAL; + err = kstrtoul(buf, 10, &value); + if (err != 0) + return err; + + if (!atomic_read(&info->trusted_touch_initialized)) + return -EIO; + +#ifdef CONFIG_ARCH_QTI_VM + err = fts_ts_handle_trusted_touch_tvm(info, value); + if (err) { + pr_err("Failed to handle trusted touch in tvm\n"); + return -EINVAL; + } +#else + err = fts_ts_handle_trusted_touch_pvm(info, value); + if (err) { + pr_err("Failed to handle trusted touch in pvm\n"); + return -EINVAL; + } +#endif + err = count; + return err; +} + +static ssize_t trusted_touch_event_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fts_ts_data *info = fts_data; + + return scnprintf(buf, PAGE_SIZE, "%d", + atomic_read(&info->trusted_touch_event)); +} + +static ssize_t trusted_touch_event_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct fts_ts_data *info = fts_data; + unsigned long value; + int err = 0; + + if (count > 2) + return -EINVAL; + + err = kstrtoul(buf, 10, &value); + if (err != 0) + return err; + + if (!atomic_read(&info->trusted_touch_initialized)) + return -EIO; + + if (value) + return -EIO; + + atomic_set(&info->trusted_touch_event, value); + + return count; +} + +static ssize_t trusted_touch_type_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fts_ts_data *info = fts_data; + + return scnprintf(buf, PAGE_SIZE, "%s", info->vm_info->trusted_touch_type); +} + +#endif + +/* get the fw version example:cat fw_version */ +static DEVICE_ATTR_RW(fts_fw_version); + +/* read and write register(s) +* All data type is **HEX** +* Single Byte: +* read: echo 88 > rw_reg ---read register 0x88 +* write: echo 8807 > rw_reg ---write 0x07 into register 0x88 +* Multi-bytes: +* [0:rw-flag][1-2: reg addr, hex][3-4: length, hex][5-6...n-n+1: write data, hex] +* rw-flag: 0, write; 1, read +* read: echo 10005 > rw_reg ---read reg 0x00-0x05 +* write: echo 000050102030405 > rw_reg ---write reg 0x00-0x05 as 01,02,03,04,05 +* Get result: +* cat rw_reg +*/ +static DEVICE_ATTR(fts_rw_reg, S_IRUGO | S_IWUSR, fts_tprwreg_show, fts_tprwreg_store); +static DEVICE_ATTR(fts_driver_info, S_IRUGO | S_IWUSR, fts_driverinfo_show, fts_driverinfo_store); +static DEVICE_ATTR(fts_dump_reg, S_IRUGO | S_IWUSR, fts_dumpreg_show, fts_dumpreg_store); +static DEVICE_ATTR(fts_hw_reset, S_IRUGO | S_IWUSR, fts_hw_reset_show, fts_hw_reset_store); +static DEVICE_ATTR(fts_irq, S_IRUGO | S_IWUSR, fts_irq_show, fts_irq_store); +static DEVICE_ATTR(fts_boot_mode, S_IRUGO | S_IWUSR, fts_bootmode_show, fts_bootmode_store); +static DEVICE_ATTR(fts_touch_point, S_IRUGO | S_IWUSR, fts_tpbuf_show, fts_tpbuf_store); +static DEVICE_ATTR(fts_log_level, S_IRUGO | S_IWUSR, fts_log_level_show, fts_log_level_store); +#ifdef CONFIG_FTS_TRUSTED_TOUCH +static DEVICE_ATTR_RW(trusted_touch_enable); +static DEVICE_ATTR_RW(trusted_touch_event); +static DEVICE_ATTR_RO(trusted_touch_type); +#endif + +/* add your attr in here*/ +static struct attribute *fts_attributes[] = { + &dev_attr_fts_fw_version.attr, + &dev_attr_fts_rw_reg.attr, + &dev_attr_fts_dump_reg.attr, + &dev_attr_fts_driver_info.attr, + &dev_attr_fts_hw_reset.attr, + &dev_attr_fts_irq.attr, + &dev_attr_fts_boot_mode.attr, + &dev_attr_fts_touch_point.attr, + &dev_attr_fts_log_level.attr, +#ifdef CONFIG_FTS_TRUSTED_TOUCH + &dev_attr_trusted_touch_enable.attr, + &dev_attr_trusted_touch_event.attr, + &dev_attr_trusted_touch_type.attr, +#endif + NULL +}; + +static struct attribute_group fts_attribute_group = { + .attrs = fts_attributes +}; + +int fts_create_sysfs(struct fts_ts_data *ts_data) +{ + int ret = 0; + + ret = sysfs_create_group(&ts_data->dev->kobj, &fts_attribute_group); + if (ret) { + FTS_ERROR("[EX]: sysfs_create_group() failed!!"); + sysfs_remove_group(&ts_data->dev->kobj, &fts_attribute_group); + return -ENOMEM; + } else { + FTS_INFO("[EX]: sysfs_create_group() succeeded!!"); + } + + return ret; +} + +int fts_remove_sysfs(struct fts_ts_data *ts_data) +{ + sysfs_remove_group(&ts_data->dev->kobj, &fts_attribute_group); + return 0; +} diff --git a/qcom/opensource/touch-drivers/focaltech_touch/focaltech_ex_mode.c b/qcom/opensource/touch-drivers/focaltech_touch/focaltech_ex_mode.c new file mode 100644 index 0000000000..03cec68d1a --- /dev/null +++ b/qcom/opensource/touch-drivers/focaltech_touch/focaltech_ex_mode.c @@ -0,0 +1,359 @@ +/* + * + * FocalTech ftxxxx TouchScreen driver. + * + * Copyright (c) 2012-2019, Focaltech Ltd. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/***************************************************************************** +* +* File Name: focaltech_ex_mode.c +* +* Author: Focaltech Driver Team +* +* Created: 2016-08-31 +* +* Abstract: +* +* Reference: +* +*****************************************************************************/ + +/***************************************************************************** +* 1.Included header files +*****************************************************************************/ +#include "focaltech_core.h" + +/***************************************************************************** +* 2.Private constant and macro definitions using #define +*****************************************************************************/ + +/***************************************************************************** +* 3.Private enumerations, structures and unions using typedef +*****************************************************************************/ +enum _ex_mode { + MODE_GLOVE = 0, + MODE_COVER, + MODE_CHARGER, + REPORT_RATE, +}; + +/***************************************************************************** +* 4.Static variables +*****************************************************************************/ + +/***************************************************************************** +* 5.Global variable or extern global variabls/functions +*****************************************************************************/ + +/***************************************************************************** +* 6.Static function prototypes +*******************************************************************************/ +static int fts_ex_mode_switch(enum _ex_mode mode, u8 value) +{ + int ret = 0; + + switch (mode) { + case MODE_GLOVE: + ret = fts_write_reg(FTS_REG_GLOVE_MODE_EN, value > 0 ? 1 : 0); + if (ret < 0) + FTS_ERROR("MODE_GLOVE switch to %d fail", value); + break; + + case MODE_COVER: + ret = fts_write_reg(FTS_REG_COVER_MODE_EN, value > 0 ? 1 : 0); + if (ret < 0) + FTS_ERROR("MODE_COVER switch to %d fail", value); + break; + + case MODE_CHARGER: + ret = fts_write_reg(FTS_REG_CHARGER_MODE_EN, value > 0 ? 1 : 0); + if (ret < 0) + FTS_ERROR("MODE_CHARGER switch to %d fail", value); + break; + + case REPORT_RATE: + ret = fts_write_reg(FTS_REG_REPORT_RATE, value); + if (ret < 0) + FTS_ERROR("REPORT_RATE switch to %d fail", value); + break; + + default: + FTS_ERROR("mode(%d) unsupport", mode); + ret = -EINVAL; + break; + } + + return ret; +} + +static ssize_t fts_glove_mode_show( + struct device *dev, struct device_attribute *attr, char *buf) +{ + int count = 0; + u8 val = 0; + struct fts_ts_data *ts_data = fts_data; + struct input_dev *input_dev = ts_data->input_dev; + + mutex_lock(&input_dev->mutex); + fts_read_reg(FTS_REG_GLOVE_MODE_EN, &val); + count = scnprintf(buf + count, PAGE_SIZE, "Glove Mode:%s\n", + ts_data->glove_mode ? "On" : "Off"); + count += scnprintf(buf + count, PAGE_SIZE - count, + "Glove Reg(0xC0):%d\n", val); + mutex_unlock(&input_dev->mutex); + + return count; +} + +static ssize_t fts_glove_mode_store( + struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int ret = 0; + struct fts_ts_data *ts_data = fts_data; + + if (FTS_SYSFS_ECHO_ON(buf)) { + if (!ts_data->glove_mode) { + FTS_DEBUG("enter glove mode"); + ret = fts_ex_mode_switch(MODE_GLOVE, ENABLE); + if (ret >= 0) { + ts_data->glove_mode = ENABLE; + } + } + } else if (FTS_SYSFS_ECHO_OFF(buf)) { + if (ts_data->glove_mode) { + FTS_DEBUG("exit glove mode"); + ret = fts_ex_mode_switch(MODE_GLOVE, DISABLE); + if (ret >= 0) { + ts_data->glove_mode = DISABLE; + } + } + } + + FTS_DEBUG("glove mode:%d", ts_data->glove_mode); + return count; +} + + +static ssize_t fts_cover_mode_show( + struct device *dev, struct device_attribute *attr, char *buf) +{ + int count = 0; + u8 val = 0; + struct fts_ts_data *ts_data = fts_data; + struct input_dev *input_dev = ts_data->input_dev; + + mutex_lock(&input_dev->mutex); + fts_read_reg(FTS_REG_COVER_MODE_EN, &val); + count = scnprintf(buf + count, PAGE_SIZE, "Cover Mode:%s\n", + ts_data->cover_mode ? "On" : "Off"); + count += scnprintf(buf + count, PAGE_SIZE - count, + "Cover Reg(0xC1):%d\n", val); + mutex_unlock(&input_dev->mutex); + + return count; +} + +static ssize_t fts_cover_mode_store( + struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int ret = 0; + struct fts_ts_data *ts_data = fts_data; + + if (FTS_SYSFS_ECHO_ON(buf)) { + if (!ts_data->cover_mode) { + FTS_DEBUG("enter cover mode"); + ret = fts_ex_mode_switch(MODE_COVER, ENABLE); + if (ret >= 0) { + ts_data->cover_mode = ENABLE; + } + } + } else if (FTS_SYSFS_ECHO_OFF(buf)) { + if (ts_data->cover_mode) { + FTS_DEBUG("exit cover mode"); + ret = fts_ex_mode_switch(MODE_COVER, DISABLE); + if (ret >= 0) { + ts_data->cover_mode = DISABLE; + } + } + } + + FTS_DEBUG("cover mode:%d", ts_data->cover_mode); + return count; +} + +static ssize_t fts_charger_mode_show( + struct device *dev, struct device_attribute *attr, char *buf) +{ + int count = 0; + u8 val = 0; + struct fts_ts_data *ts_data = fts_data; + struct input_dev *input_dev = ts_data->input_dev; + + mutex_lock(&input_dev->mutex); + fts_read_reg(FTS_REG_CHARGER_MODE_EN, &val); + count = scnprintf(buf + count, PAGE_SIZE, "Charger Mode:%s\n", + ts_data->charger_mode ? "On" : "Off"); + count += scnprintf(buf + count, PAGE_SIZE - count, + "Charger Reg(0x8B):%d\n", val); + mutex_unlock(&input_dev->mutex); + + return count; +} + +static ssize_t fts_charger_mode_store( + struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int ret = 0; + struct fts_ts_data *ts_data = fts_data; + + if (FTS_SYSFS_ECHO_ON(buf)) { + if (!ts_data->charger_mode) { + FTS_DEBUG("enter charger mode"); + ret = fts_ex_mode_switch(MODE_CHARGER, ENABLE); + if (ret >= 0) { + ts_data->charger_mode = ENABLE; + } + } + } else if (FTS_SYSFS_ECHO_OFF(buf)) { + if (ts_data->charger_mode) { + FTS_DEBUG("exit charger mode"); + ret = fts_ex_mode_switch(MODE_CHARGER, DISABLE); + if (ret >= 0) { + ts_data->charger_mode = DISABLE; + } + } + } + + FTS_DEBUG("charger mode:%d", ts_data->glove_mode); + return count; +} + +static ssize_t fts_report_rate_show( + struct device *dev, struct device_attribute *attr, char *buf) +{ + int count = 0; + u8 val = 0; + struct fts_ts_data *ts_data = fts_data; + struct input_dev *input_dev = ts_data->input_dev; + + mutex_lock(&input_dev->mutex); + fts_read_reg(FTS_REG_REPORT_RATE, &val); + count = scnprintf(buf + count, PAGE_SIZE, + "Report Rate:%d\n", ts_data->report_rate); + count += scnprintf(buf + count, PAGE_SIZE - count, + "Report Rate Reg(0x88):%d\n", val); + mutex_unlock(&input_dev->mutex); + + return count; +} + +static ssize_t fts_report_rate_store( + struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int ret = 0; + struct fts_ts_data *ts_data = fts_data; + int rate; + + ret = kstrtoint(buf, 16, &rate); + if (ret) + return ret; + + if (rate != ts_data->report_rate) { + ret = fts_ex_mode_switch(REPORT_RATE, (u8)rate); + if (ret >= 0) + ts_data->report_rate = rate; + } + + FTS_DEBUG("report rate:%d", ts_data->report_rate); + return count; +} + + +/* read and write charger mode + * read example: cat fts_glove_mode ---read glove mode + * write example:echo 1 > fts_glove_mode ---write glove mode to 01 + */ +static DEVICE_ATTR(fts_glove_mode, S_IRUGO | S_IWUSR, + fts_glove_mode_show, fts_glove_mode_store); + +static DEVICE_ATTR(fts_cover_mode, S_IRUGO | S_IWUSR, + fts_cover_mode_show, fts_cover_mode_store); + +static DEVICE_ATTR(fts_charger_mode, S_IRUGO | S_IWUSR, + fts_charger_mode_show, fts_charger_mode_store); + +static DEVICE_ATTR_RW(fts_report_rate); + +static struct attribute *fts_touch_mode_attrs[] = { + &dev_attr_fts_glove_mode.attr, + &dev_attr_fts_cover_mode.attr, + &dev_attr_fts_charger_mode.attr, + &dev_attr_fts_report_rate.attr, + NULL, +}; + +static struct attribute_group fts_touch_mode_group = { + .attrs = fts_touch_mode_attrs, +}; + +int fts_ex_mode_recovery(struct fts_ts_data *ts_data) +{ + if (ts_data->glove_mode) { + fts_ex_mode_switch(MODE_GLOVE, ENABLE); + } + + if (ts_data->cover_mode) { + fts_ex_mode_switch(MODE_COVER, ENABLE); + } + + if (ts_data->charger_mode) { + fts_ex_mode_switch(MODE_CHARGER, ENABLE); + } + + if (ts_data->report_rate > 0) + fts_ex_mode_switch(REPORT_RATE, ts_data->report_rate); + + return 0; +} + +int fts_ex_mode_init(struct fts_ts_data *ts_data) +{ + int ret = 0; + + ts_data->glove_mode = DISABLE; + ts_data->cover_mode = DISABLE; + ts_data->charger_mode = DISABLE; + ts_data->report_rate = 0; + + ret = sysfs_create_group(&ts_data->dev->kobj, &fts_touch_mode_group); + if (ret < 0) { + FTS_ERROR("create sysfs(ex_mode) fail"); + sysfs_remove_group(&ts_data->dev->kobj, &fts_touch_mode_group); + return ret; + } else { + FTS_DEBUG("create sysfs(ex_mode) succeedfully"); + } + + return 0; +} + +int fts_ex_mode_exit(struct fts_ts_data *ts_data) +{ + sysfs_remove_group(&ts_data->dev->kobj, &fts_touch_mode_group); + return 0; +} diff --git a/qcom/opensource/touch-drivers/focaltech_touch/focaltech_flash.c b/qcom/opensource/touch-drivers/focaltech_touch/focaltech_flash.c new file mode 100644 index 0000000000..c3319d8636 --- /dev/null +++ b/qcom/opensource/touch-drivers/focaltech_touch/focaltech_flash.c @@ -0,0 +1,2093 @@ +/* + * + * FocalTech fts TouchScreen driver. + * + * Copyright (c) 2012-2019, Focaltech Ltd. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/***************************************************************************** +* +* File Name: focaltech_flash.c +* +* Author: Focaltech Driver Team +* +* Created: 2016-08-08 +* +* Abstract: +* +* Reference: +* +*****************************************************************************/ + +/***************************************************************************** +* 1.Included header files +*****************************************************************************/ +#include "focaltech_core.h" +#include "focaltech_flash.h" + +/***************************************************************************** +* Private constant and macro definitions using #define +*****************************************************************************/ +#define FTS_FW_REQUEST_SUPPORT 1 +/* Example: focaltech_ts_fw_tianma.bin */ +#define FTS_FW_NAME_PREX_WITH_REQUEST "focaltech_ts_fw_" + +/***************************************************************************** +* Global variable or extern global variabls/functions +*****************************************************************************/ +u8 fw_file[1] = { +0, +}; + +struct upgrade_module module_list[] = { + {FTS_MODULE_ID, FTS_MODULE_NAME, fw_file, sizeof(fw_file)}, + {FTS_MODULE2_ID, FTS_MODULE2_NAME, fw_file, sizeof(fw_file)}, +}; + +struct upgrade_func upgrade_func_ft8720 = { + .ctype = {0x1C}, + .fwveroff = 0x210E, + .fwcfgoff = 0x1000, + .appoff = 0x2000, + .licoff = 0x0000, + .appoff_handle_in_ic = true, + .pramboot_supported = false, + .new_return_value_from_ic = true, + .hid_supported = false, +}; + +struct upgrade_func *upgrade_func_list[] = { + &upgrade_func_ft5452, + &upgrade_func_ft5652, + &upgrade_func_ft8720, +}; + +struct fts_upgrade *fwupgrade; + +/***************************************************************************** +* Static function prototypes +*****************************************************************************/ +static bool fts_fwupg_check_state( + struct fts_upgrade *upg, enum FW_STATUS rstate); + +/************************************************************************ +* Name: fts_fwupg_get_boot_state +* Brief: read boot id(rom/pram/bootloader), confirm boot environment +* Input: +* Output: +* Return: return 0 if success, otherwise return error code +***********************************************************************/ +static int fts_fwupg_get_boot_state( + struct fts_upgrade *upg, + enum FW_STATUS *fw_sts) +{ + int ret = 0; + u8 cmd[4] = { 0 }; + u32 cmd_len = 0; + u8 val[2] = { 0 }; + struct ft_chip_t *ids = NULL; + + FTS_INFO("**********read boot id**********"); + if ((!upg) || (!upg->func) || (!upg->ts_data) || (!fw_sts)) { + FTS_ERROR("upg/func/ts_data/fw_sts is null"); + return -EINVAL; + } + + if (upg->func->hid_supported) + fts_hid2std(); + + cmd[0] = FTS_CMD_START1; + cmd[1] = FTS_CMD_START2; + if (upg->func->upgspec_version >= UPGRADE_SPEC_V_1_0) + cmd_len = 1; + else + cmd_len = 2; + ret = fts_write(cmd, cmd_len); + if (ret < 0) { + FTS_ERROR("write 55 cmd fail"); + return ret; + } + + msleep(FTS_CMD_START_DELAY); + cmd[0] = FTS_CMD_READ_ID; + cmd[1] = cmd[2] = cmd[3] = 0x00; + if (fts_data->ic_info.is_incell || + (upg->func->upgspec_version >= UPGRADE_SPEC_V_1_0)) + cmd_len = FTS_CMD_READ_ID_LEN_INCELL; + else + cmd_len = FTS_CMD_READ_ID_LEN; + ret = fts_read(cmd, cmd_len, val, 2); + if (ret < 0) { + FTS_ERROR("write 90 cmd fail"); + return ret; + } + FTS_INFO("read boot id:0x%02x%02x", val[0], val[1]); + + ids = &upg->ts_data->ic_info.ids; + if ((val[0] == ids->rom_idh) && (val[1] == ids->rom_idl)) { + FTS_INFO("tp run in romboot"); + *fw_sts = FTS_RUN_IN_ROM; + } else if ((val[0] == ids->pb_idh) && (val[1] == ids->pb_idl)) { + FTS_INFO("tp run in pramboot"); + *fw_sts = FTS_RUN_IN_PRAM; + } else if ((val[0] == ids->bl_idh) && (val[1] == ids->bl_idl)) { + FTS_INFO("tp run in bootloader"); + *fw_sts = FTS_RUN_IN_BOOTLOADER; + } + + return 0; +} + +static int fts_fwupg_reset_to_boot(struct fts_upgrade *upg) +{ + int ret = 0; + u8 reg = FTS_REG_UPGRADE; + + FTS_INFO("send 0xAA and 0x55 to FW, reset to boot environment"); + if (upg && upg->func && upg->func->is_reset_register_BC) { + reg = FTS_REG_UPGRADE2; + } + + ret = fts_write_reg(reg, FTS_UPGRADE_AA); + if (ret < 0) { + FTS_ERROR("write FC=0xAA fail"); + return ret; + } + msleep(FTS_DELAY_UPGRADE_AA); + + ret = fts_write_reg(reg, FTS_UPGRADE_55); + if (ret < 0) { + FTS_ERROR("write FC=0x55 fail"); + return ret; + } + + msleep(FTS_DELAY_UPGRADE_RESET); + return 0; +} + +/************************************************************************ +* Name: fts_fwupg_reset_to_romboot +* Brief: reset to romboot, to load pramboot +* Input: +* Output: +* Return: return 0 if success, otherwise return error code +***********************************************************************/ +static int fts_fwupg_reset_to_romboot(struct fts_upgrade *upg) +{ + int ret = 0; + int i = 0; + u8 cmd = FTS_CMD_RESET; + enum FW_STATUS state = FTS_RUN_IN_ERROR; + + ret = fts_write(&cmd, 1); + if (ret < 0) { + FTS_ERROR("pram/rom/bootloader reset cmd write fail"); + return ret; + } + mdelay(10); + + for (i = 0; i < FTS_UPGRADE_LOOP; i++) { + ret = fts_fwupg_get_boot_state(upg, &state); + if (FTS_RUN_IN_ROM == state) + break; + mdelay(5); + } + if (i >= FTS_UPGRADE_LOOP) { + FTS_ERROR("reset to romboot fail"); + return -EIO; + } + + return 0; +} + +static u16 fts_crc16_calc_host(u8 *pbuf, u32 length) +{ + u16 ecc = 0; + u32 i = 0; + u32 j = 0; + + for ( i = 0; i < length; i += 2 ) { + ecc ^= ((pbuf[i] << 8) | (pbuf[i + 1])); + for (j = 0; j < 16; j ++) { + if (ecc & 0x01) + ecc = (u16)((ecc >> 1) ^ AL2_FCS_COEF); + else + ecc >>= 1; + } + } + + return ecc; +} + +static u16 fts_pram_ecc_calc_host(u8 *pbuf, u32 length) +{ + return fts_crc16_calc_host(pbuf, length); +} + +static int fts_pram_ecc_cal_algo( + struct fts_upgrade *upg, + u32 start_addr, + u32 ecc_length) +{ + int ret = 0; + int i = 0; + int ecc = 0; + u8 val[2] = { 0 }; + u8 tmp = 0; + u8 cmd[FTS_ROMBOOT_CMD_ECC_NEW_LEN] = { 0 }; + + FTS_INFO("read out pramboot checksum"); + if ((!upg) || (!upg->func)) { + FTS_ERROR("upg/func is null"); + return -EINVAL; + } + + cmd[0] = FTS_ROMBOOT_CMD_ECC; + cmd[1] = BYTE_OFF_16(start_addr); + cmd[2] = BYTE_OFF_8(start_addr); + cmd[3] = BYTE_OFF_0(start_addr); + cmd[4] = BYTE_OFF_16(ecc_length); + cmd[5] = BYTE_OFF_8(ecc_length); + cmd[6] = BYTE_OFF_0(ecc_length); + ret = fts_write(cmd, FTS_ROMBOOT_CMD_ECC_NEW_LEN); + if (ret < 0) { + FTS_ERROR("write pramboot ecc cal cmd fail"); + return ret; + } + + cmd[0] = FTS_ROMBOOT_CMD_ECC_FINISH; + for (i = 0; i < FTS_ECC_FINISH_TIMEOUT; i++) { + msleep(1); + ret = fts_read(cmd, 1, val, 1); + if (ret < 0) { + FTS_ERROR("ecc_finish read cmd fail"); + return ret; + } + if (upg->func->new_return_value_from_ic || + (upg->func->upgspec_version >= UPGRADE_SPEC_V_1_0)) { + tmp = FTS_ROMBOOT_CMD_ECC_FINISH_OK_A5; + } else { + tmp = FTS_ROMBOOT_CMD_ECC_FINISH_OK_00; + } + if (tmp == val[0]) + break; + } + if (i >= FTS_ECC_FINISH_TIMEOUT) { + FTS_ERROR("wait ecc finish fail"); + return -EIO; + } + + cmd[0] = FTS_ROMBOOT_CMD_ECC_READ; + ret = fts_read(cmd, 1, val, 2); + if (ret < 0) { + FTS_ERROR("read pramboot ecc fail"); + return ret; + } + + ecc = ((u16)(val[0] << 8) + val[1]) & 0x0000FFFF; + return ecc; +} + +static int fts_pram_ecc_cal_xor(void) +{ + int ret = 0; + u8 reg_val = 0; + + FTS_INFO("read out pramboot checksum"); + + ret = fts_read_reg(FTS_ROMBOOT_CMD_ECC, ®_val); + if (ret < 0) { + FTS_ERROR("read pramboot ecc fail"); + return ret; + } + + return (int)reg_val; +} + +static int fts_pram_ecc_cal(struct fts_upgrade *upg, u32 saddr, u32 len) +{ + if ((!upg) || (!upg->func)) { + FTS_ERROR("upg/func is null"); + return -EINVAL; + } + + if ((upg->func->pram_ecc_check_mode == ECC_CHECK_MODE_CRC16) || + (upg->func->upgspec_version >= UPGRADE_SPEC_V_1_0)) + return fts_pram_ecc_cal_algo(upg, saddr, len); + + return fts_pram_ecc_cal_xor(); +} + +static int fts_pram_write_buf(struct fts_upgrade *upg, u8 *buf, u32 len) +{ + int ret = 0; + u32 i = 0; + u32 j = 0; + u32 offset = 0; + u32 remainder = 0; + u32 packet_number; + u32 packet_len = 0; + u8 packet_buf[FTS_FLASH_PACKET_LENGTH + FTS_CMD_WRITE_LEN] = { 0 }; + u8 ecc_tmp = 0; + int ecc_in_host = 0; + u32 cmdlen = 0; + + FTS_INFO("write pramboot to pram"); + if ((!upg) || (!upg->func) || !buf) { + FTS_ERROR("upg/func/buf is null"); + return -EINVAL; + } + + FTS_INFO("pramboot len=%d", len); + if ((len < PRAMBOOT_MIN_SIZE) || (len > PRAMBOOT_MAX_SIZE)) { + FTS_ERROR("pramboot length(%d) fail", len); + return -EINVAL; + } + + packet_number = len / FTS_FLASH_PACKET_LENGTH; + remainder = len % FTS_FLASH_PACKET_LENGTH; + if (remainder > 0) + packet_number++; + packet_len = FTS_FLASH_PACKET_LENGTH; + + for (i = 0; i < packet_number; i++) { + offset = i * FTS_FLASH_PACKET_LENGTH; + /* last packet */ + if ((i == (packet_number - 1)) && remainder) + packet_len = remainder; + + if (upg->ts_data->bus_type == BUS_TYPE_SPI_V2) { + packet_buf[0] = FTS_ROMBOOT_CMD_SET_PRAM_ADDR; + packet_buf[1] = BYTE_OFF_16(offset); + packet_buf[2] = BYTE_OFF_8(offset); + packet_buf[3] = BYTE_OFF_0(offset); + + ret = fts_write(packet_buf, FTS_ROMBOOT_CMD_SET_PRAM_ADDR_LEN); + if (ret < 0) { + FTS_ERROR("pramboot set write address(%d) fail", i); + return ret; + } + + packet_buf[0] = FTS_ROMBOOT_CMD_WRITE; + cmdlen = 1; + } else { + packet_buf[0] = FTS_ROMBOOT_CMD_WRITE; + packet_buf[1] = BYTE_OFF_16(offset); + packet_buf[2] = BYTE_OFF_8(offset); + packet_buf[3] = BYTE_OFF_0(offset); + + packet_buf[4] = BYTE_OFF_8(packet_len); + packet_buf[5] = BYTE_OFF_0(packet_len); + cmdlen = 6; + } + + for (j = 0; j < packet_len; j++) { + packet_buf[cmdlen + j] = buf[offset + j]; + if (upg->func->pram_ecc_check_mode == ECC_CHECK_MODE_XOR) + ecc_tmp ^= packet_buf[cmdlen + j]; + } + + ret = fts_write(packet_buf, packet_len + cmdlen); + if (ret < 0) { + FTS_ERROR("pramboot write data(%d) fail", i); + return ret; + } + } + + if ((upg->func->pram_ecc_check_mode == ECC_CHECK_MODE_CRC16) || + (upg->func->upgspec_version >= UPGRADE_SPEC_V_1_0)) + ecc_in_host = (int)fts_pram_ecc_calc_host(buf, len); + else + ecc_in_host = (int)ecc_tmp; + + return ecc_in_host; +} + +static int fts_pram_start(void) +{ + u8 cmd = FTS_ROMBOOT_CMD_START_APP; + int ret = 0; + + FTS_INFO("remap to start pramboot"); + + ret = fts_write(&cmd, 1); + if (ret < 0) { + FTS_ERROR("write start pram cmd fail"); + return ret; + } + msleep(FTS_DELAY_PRAMBOOT_START); + + return 0; +} + +static int fts_pram_write_remap(struct fts_upgrade *upg) +{ + int ret = 0; + int ecc_in_host = 0; + int ecc_in_tp = 0; + u8 *pb_buf = NULL; + u32 pb_len = 0; + + FTS_INFO("write pram and remap"); + if (!upg || !upg->func || !upg->func->pramboot) { + FTS_ERROR("upg/func/pramboot is null"); + return -EINVAL; + } + + if (upg->func->pb_length < FTS_MIN_LEN) { + FTS_ERROR("pramboot length(%d) fail", upg->func->pb_length); + return -EINVAL; + } + + pb_buf = upg->func->pramboot; + pb_len = upg->func->pb_length; + + /* write pramboot to pram */ + ecc_in_host = fts_pram_write_buf(upg, pb_buf, pb_len); + if (ecc_in_host < 0) { + FTS_ERROR( "write pramboot fail"); + return ecc_in_host; + } + + /* read out checksum */ + ecc_in_tp = fts_pram_ecc_cal(upg, 0, pb_len); + if (ecc_in_tp < 0) { + FTS_ERROR( "read pramboot ecc fail"); + return ecc_in_tp; + } + + FTS_INFO("pram ecc in tp:%x, host:%x", ecc_in_tp, ecc_in_host); + /* pramboot checksum != fw checksum, upgrade fail */ + if (ecc_in_host != ecc_in_tp) { + FTS_ERROR("pramboot ecc check fail"); + return -EIO; + } + + /*start pram*/ + ret = fts_pram_start(); + if (ret < 0) { + FTS_ERROR("pram start fail"); + return ret; + } + + return 0; +} + +static int fts_pram_init(void) +{ + int ret = 0; + u8 reg_val = 0; + u8 wbuf[3] = { 0 }; + + FTS_INFO("pramboot initialization"); + + /* read flash ID */ + wbuf[0] = FTS_CMD_FLASH_TYPE; + ret = fts_read(wbuf, 1, ®_val, 1); + if (ret < 0) { + FTS_ERROR("read flash type fail"); + return ret; + } + + /* set flash clk */ + wbuf[0] = FTS_CMD_FLASH_TYPE; + wbuf[1] = reg_val; + wbuf[2] = 0x00; + ret = fts_write(wbuf, 3); + if (ret < 0) { + FTS_ERROR("write flash type fail"); + return ret; + } + + return 0; +} + +static int fts_pram_write_init(struct fts_upgrade *upg) +{ + int ret = 0; + bool state = 0; + enum FW_STATUS status = FTS_RUN_IN_ERROR; + + FTS_INFO("**********pram write and init**********"); + if ((NULL == upg) || (NULL == upg->func)) { + FTS_ERROR("upgrade/func is null"); + return -EINVAL; + } + + if (!upg->func->pramboot_supported) { + FTS_ERROR("ic not support pram"); + return -EINVAL; + } + + FTS_DEBUG("check whether tp is in romboot or not "); + /* need reset to romboot when non-romboot state */ + ret = fts_fwupg_get_boot_state(upg, &status); + if (status != FTS_RUN_IN_ROM) { + if (FTS_RUN_IN_PRAM == status) { + FTS_INFO("tp is in pramboot, need send reset cmd before upgrade"); + ret = fts_pram_init(); + if (ret < 0) { + FTS_ERROR("pramboot(before) init fail"); + return ret; + } + } + + FTS_INFO("tp isn't in romboot, need send reset to romboot"); + ret = fts_fwupg_reset_to_romboot(upg); + if (ret < 0) { + FTS_ERROR("reset to romboot fail"); + return ret; + } + } + + /* check the length of the pramboot */ + ret = fts_pram_write_remap(upg); + if (ret < 0) { + FTS_ERROR("pram write fail, ret=%d", ret); + return ret; + } + + FTS_DEBUG("after write pramboot, confirm run in pramboot"); + state = fts_fwupg_check_state(upg, FTS_RUN_IN_PRAM); + if (!state) { + FTS_ERROR("not in pramboot"); + return -EIO; + } + + ret = fts_pram_init(); + if (ret < 0) { + FTS_ERROR("pramboot init fail"); + return ret; + } + + return 0; +} + +static bool fts_fwupg_check_fw_valid(void) +{ + int ret = 0; + + ret = fts_wait_tp_to_valid(); + if (ret < 0) { + FTS_INFO("tp fw invaild"); + return false; + } + + FTS_INFO("tp fw vaild"); + return true; +} + +/************************************************************************ +* Name: fts_fwupg_check_state +* Brief: confirm tp run in which mode: romboot/pramboot/bootloader +* Input: +* Output: +* Return: return true if state is match, otherwise return false +***********************************************************************/ +static bool fts_fwupg_check_state( + struct fts_upgrade *upg, enum FW_STATUS rstate) +{ + int ret = 0; + int i = 0; + enum FW_STATUS cstate = FTS_RUN_IN_ERROR; + + for (i = 0; i < FTS_UPGRADE_LOOP; i++) { + ret = fts_fwupg_get_boot_state(upg, &cstate); + /* FTS_DEBUG("fw state=%d, retries=%d", cstate, i); */ + if (cstate == rstate) + return true; + msleep(FTS_DELAY_READ_ID); + } + + return false; +} + +/************************************************************************ +* Name: fts_fwupg_reset_in_boot +* Brief: RST CMD(07), reset to romboot(bootloader) in boot environment +* Input: +* Output: +* Return: return 0 if success, otherwise return error code +***********************************************************************/ +int fts_fwupg_reset_in_boot(void) +{ + int ret = 0; + u8 cmd = FTS_CMD_RESET; + + FTS_INFO("reset in boot environment"); + ret = fts_write(&cmd, 1); + if (ret < 0) { + FTS_ERROR("pram/rom/bootloader reset cmd write fail"); + return ret; + } + + msleep(FTS_DELAY_UPGRADE_RESET); + return 0; +} + +static int fts_fwupg_enter_into_boot_old(struct fts_upgrade *upg) +{ + int ret = 0; + bool fwvalid = false; + bool state = false; + + FTS_INFO("***********enter into pramboot/bootloader***********"); + if ((!upg) || (NULL == upg->func)) { + FTS_ERROR("upgrade/func is null"); + return -EINVAL; + } + + fwvalid = fts_fwupg_check_fw_valid(); + if (fwvalid) { + ret = fts_fwupg_reset_to_boot(upg); + if (ret < 0) { + FTS_ERROR("enter into romboot/bootloader fail"); + return ret; + } + } else if (upg->func->read_boot_id_need_reset) { + ret = fts_fwupg_reset_in_boot(); + if (ret < 0) { + FTS_ERROR("reset before read boot id when fw invalid fail"); + return ret; + } + } + + if (upg->func->pramboot_supported) { + FTS_INFO("pram supported, write pramboot and init"); + /* pramboot */ + ret = fts_pram_write_init(upg); + if (ret < 0) { + FTS_ERROR("pram write_init fail"); + return ret; + } + } else { + FTS_DEBUG("pram not supported, confirm in bootloader"); + /* bootloader */ + state = fts_fwupg_check_state(upg, FTS_RUN_IN_BOOTLOADER); + if (!state) { + FTS_ERROR("fw not in bootloader, fail"); + return -EIO; + } + } + + return 0; +} + +static int fts_hardware_reset_to_boot(void) +{ + u8 cmd[2] = {0}; + int i = 0; + u8 chip_id[2] = { 0 }; + u8 id_cmd[4] = { 0 }; + int ret = 0; + + fts_reset_proc(0); + FTS_INFO("reset finish!!"); + usleep_range(10000, 11000); + + for (i = 0; i < 20; i++) { + FTS_INFO("start send 55 AA"); + cmd[0] = 0x55; + cmd[1] = 0xAA; + ret = fts_write(cmd, 2); + if (ret < 0) + FTS_ERROR("send 55 AA cmd fail"); + + usleep_range(8000, 9000); + FTS_INFO("start send 90 00 00 00"); + id_cmd[0] = 0x90; + id_cmd[1] = id_cmd[2] = id_cmd[3] = 0x00; + ret = fts_read(id_cmd, 1, chip_id, 2); + if (ret < 0) + FTS_ERROR("send 90 cmd fail"); + + FTS_INFO("read boot id = %x--%x", chip_id[0], chip_id[1]); + if (chip_id[1] == 0xB2) + break; + + usleep_range(1000, 2000); + + if (i == 10) { + fts_reset_proc(0); + FTS_INFO("reset again finish!!"); + usleep_range(10000, 11000); + } + + } + + if (i >= 20) { + FTS_INFO("enter into romboot fail"); + return -EIO; + } + return 0; + +} + +static int fts_fwupg_enter_into_boot_new(struct fts_upgrade *upg) +{ + int ret = 0; + bool state = false; + + FTS_INFO("***********enter into pramboot/bootloader***********"); + if ((!upg) || (upg->func == NULL)) { + FTS_ERROR("upgrade/func is null"); + return -EINVAL; + } + + ret = fts_hardware_reset_to_boot(); + if (!ret) { + FTS_INFO("tp run in bootloader success"); + return ret; + } + + if (upg->func->pramboot_supported) { + FTS_INFO("pram supported, write pramboot and init"); + /* pramboot */ + if (upg->func->write_pramboot_private) + ret = upg->func->write_pramboot_private(); + else + ret = fts_pram_write_init(upg); + + if (ret < 0) { + FTS_ERROR("pram write_init fail"); + return ret; + } + } else { + FTS_DEBUG("pram not supported, confirm in bootloader"); + /* bootloader */ + state = fts_fwupg_check_state(upg, FTS_RUN_IN_BOOTLOADER); + if (!state) { + FTS_ERROR("fw not in bootloader, fail"); + return -EIO; + } + } + + return 0; +} + +int fts_fwupg_enter_into_boot(void) +{ + struct fts_upgrade *upg = fwupgrade; + + if (upg->func->upgspec_version >= UPGRADE_SPEC_V_1_0) + return fts_fwupg_enter_into_boot_new(upg); + + return fts_fwupg_enter_into_boot_old(upg); +} + +/************************************************************************ + * Name: fts_fwupg_check_flash_status + * Brief: read status from tp + * Input: flash_status: correct value from tp + * retries: read retry times + * retries_delay: retry delay + * Output: + * Return: return true if flash status check pass, otherwise return false + ***********************************************************************/ +static bool fts_fwupg_check_flash_status( + u16 flash_status, + int retries, + int retries_delay) +{ + int ret = 0; + int i = 0; + u8 cmd = 0; + u8 val[FTS_CMD_FLASH_STATUS_LEN] = { 0 }; + u16 read_status = 0; + + for (i = 0; i < retries; i++) { + cmd = FTS_CMD_FLASH_STATUS; + ret = fts_read(&cmd , 1, val, FTS_CMD_FLASH_STATUS_LEN); + read_status = (((u16)val[0]) << 8) + val[1]; + if (flash_status == read_status) { + /* FTS_DEBUG("[UPGRADE]flash status ok"); */ + return true; + } + /* FTS_DEBUG("flash status fail,ok:%04x read:%04x, retries:%d", flash_status, read_status, i); */ + msleep(retries_delay); + } + + return false; +} + +/************************************************************************ + * Name: fts_fwupg_erase + * Brief: erase flash area + * Input: delay - delay after erase + * Output: + * Return: return 0 if success, otherwise return error code + ***********************************************************************/ +int fts_fwupg_erase(u32 delay) +{ + int ret = 0; + u8 cmd = 0; + bool flag = false; + + FTS_INFO("**********erase now**********"); + + /*send to erase flash*/ + cmd = FTS_CMD_ERASE_APP; + ret = fts_write(&cmd, 1); + if (ret < 0) { + FTS_ERROR("erase cmd fail"); + return ret; + } + msleep(delay); + + /* read status 0xF0AA: success */ + flag = fts_fwupg_check_flash_status(FTS_CMD_FLASH_STATUS_ERASE_OK, + FTS_RETRIES_REASE, + FTS_RETRIES_DELAY_REASE); + if (!flag) { + FTS_ERROR("ecc flash status check fail"); + return -EIO; + } + + return 0; +} + +/************************************************************************ + * Name: fts_fwupg_ecc_cal + * Brief: calculate and get ecc from tp + * Input: saddr - start address need calculate ecc + * len - length need calculate ecc + * Output: + * Return: return data ecc of tp if success, otherwise return error code + ***********************************************************************/ +int fts_fwupg_ecc_cal(u32 saddr, u32 len) +{ + int ret = 0; + u32 i = 0; + u32 cmdlen = FTS_CMD_ECC_CAL_LEN; + u8 wbuf[FTS_CMD_ECC_CAL_LEN] = { 0 }; + u8 val[FTS_CMD_FLASH_STATUS_LEN] = { 0 }; + int ecc = 0; + int ecc_len = 0; + u32 packet_num = 0; + u32 packet_len = 0; + u32 remainder = 0; + u32 addr = 0; + u32 offset = 0; + bool bflag = false; + struct fts_upgrade *upg = fwupgrade; + + FTS_INFO( "**********read out checksum**********"); + if ((NULL == upg) || (NULL == upg->func)) { + FTS_ERROR("upgrade/func is null"); + return -EINVAL; + } + + /* check sum init */ + wbuf[0] = FTS_CMD_ECC_INIT; + ret = fts_write(wbuf, 1); + if (ret < 0) { + FTS_ERROR("ecc init cmd write fail"); + return ret; + } + + if (upg->func->upgspec_version >= UPGRADE_SPEC_V_1_0) { + packet_num = 1; + remainder = 0; + packet_len = len; + } else { + packet_num = len / FTS_MAX_LEN_ECC_CALC; + remainder = len % FTS_MAX_LEN_ECC_CALC; + if (remainder) + packet_num++; + packet_len = FTS_MAX_LEN_ECC_CALC; + } + FTS_INFO("ecc calc num:%d, remainder:%d", packet_num, remainder); + + /* send commond to start checksum */ + wbuf[0] = FTS_CMD_ECC_CAL; + for (i = 0; i < packet_num; i++) { + offset = FTS_MAX_LEN_ECC_CALC * i; + addr = saddr + offset; + wbuf[1] = BYTE_OFF_16(addr); + wbuf[2] = BYTE_OFF_8(addr); + wbuf[3] = BYTE_OFF_0(addr); + + if (upg->func->upgspec_version >= UPGRADE_SPEC_V_1_0) { + wbuf[4] = BYTE_OFF_16(packet_len); + wbuf[5] = BYTE_OFF_8(packet_len); + wbuf[6] = BYTE_OFF_0(packet_len); + cmdlen = FTS_CMD_ECC_CAL_LEN; + } else { + if ((i == (packet_num - 1)) && remainder) + packet_len = remainder; + wbuf[4] = BYTE_OFF_8(packet_len); + wbuf[5] = BYTE_OFF_0(packet_len); + cmdlen = FTS_CMD_ECC_CAL_LEN - 1; + } + + FTS_DEBUG("ecc calc startaddr:0x%04x, len:%d", addr, packet_len); + ret = fts_write(wbuf, cmdlen); + if (ret < 0) { + FTS_ERROR("ecc calc cmd write fail"); + return ret; + } + + msleep(packet_len / 256); + + /* read status if check sum is finished */ + bflag = fts_fwupg_check_flash_status(FTS_CMD_FLASH_STATUS_ECC_OK, + FTS_RETRIES_ECC_CAL, + FTS_RETRIES_DELAY_ECC_CAL); + if (!bflag) { + FTS_ERROR("ecc flash status read fail"); + return -EIO; + } + } + + ecc_len = 1; + if ((upg->func->fw_ecc_check_mode == ECC_CHECK_MODE_CRC16) || + (upg->func->upgspec_version >= UPGRADE_SPEC_V_1_0)) + ecc_len = 2; + + /* read out check sum */ + wbuf[0] = FTS_CMD_ECC_READ; + ret = fts_read(wbuf, 1, val, ecc_len); + if (ret < 0) { + FTS_ERROR( "ecc read cmd write fail"); + return ret; + } + + if ((upg->func->fw_ecc_check_mode == ECC_CHECK_MODE_CRC16) || + (upg->func->upgspec_version >= UPGRADE_SPEC_V_1_0)) + ecc = (int)((u16)(val[0] << 8) + val[1]); + else + ecc = (int)val[0]; + + return ecc; +} + +/************************************************************************ + * Name: fts_flash_write_buf + * Brief: write buf data to flash address + * Input: saddr - start address data write to flash + * buf - data buffer + * len - data length + * delay - delay after write + * Output: + * Return: return data ecc of host if success, otherwise return error code + ***********************************************************************/ +int fts_flash_write_buf( + u32 saddr, + u8 *buf, + u32 len, + u32 delay) +{ + int ret = 0; + u32 i = 0; + u32 j = 0; + u32 packet_number = 0; + u32 packet_len = 0; + u32 addr = 0; + u32 offset = 0; + u32 remainder = 0; + u32 cmdlen = 0; + u8 packet_buf[FTS_FLASH_PACKET_LENGTH + FTS_CMD_WRITE_LEN] = { 0 }; + u8 ecc_tmp = 0; + int ecc_in_host = 0; + u8 cmd = 0; + u8 val[FTS_CMD_FLASH_STATUS_LEN] = { 0 }; + u16 read_status = 0; + u16 wr_ok = 0; + struct fts_upgrade *upg = fwupgrade; + + FTS_INFO( "**********write data to flash**********"); + if ((!upg) || (!upg->func || !buf || !len)) { + FTS_ERROR("upgrade/func/buf/len is invalid"); + return -EINVAL; + } + + FTS_INFO("data buf start addr=0x%x, len=0x%x", saddr, len); + packet_number = len / FTS_FLASH_PACKET_LENGTH; + remainder = len % FTS_FLASH_PACKET_LENGTH; + if (remainder > 0) + packet_number++; + packet_len = FTS_FLASH_PACKET_LENGTH; + FTS_INFO("write data, num:%d remainder:%d", packet_number, remainder); + + for (i = 0; i < packet_number; i++) { + offset = i * FTS_FLASH_PACKET_LENGTH; + addr = saddr + offset; + + /* last packet */ + if ((i == (packet_number - 1)) && remainder) + packet_len = remainder; + + if (upg->ts_data->bus_type == BUS_TYPE_SPI_V2) { + packet_buf[0] = FTS_CMD_SET_WFLASH_ADDR; + packet_buf[1] = BYTE_OFF_16(addr); + packet_buf[2] = BYTE_OFF_8(addr); + packet_buf[3] = BYTE_OFF_0(addr); + ret = fts_write(packet_buf, FTS_LEN_SET_ADDR); + if (ret < 0) { + FTS_ERROR("set flash address fail"); + return ret; + } + + packet_buf[0] = FTS_CMD_WRITE; + cmdlen = 1; + } else { + packet_buf[0] = FTS_CMD_WRITE; + packet_buf[1] = BYTE_OFF_16(addr); + packet_buf[2] = BYTE_OFF_8(addr); + packet_buf[3] = BYTE_OFF_0(addr); + packet_buf[4] = BYTE_OFF_8(packet_len); + packet_buf[5] = BYTE_OFF_0(packet_len); + cmdlen = 6; + } + + for (j = 0; j < packet_len; j++) { + packet_buf[cmdlen + j] = buf[offset + j]; + ecc_tmp ^= packet_buf[cmdlen + j]; + } + + ret = fts_write(packet_buf, packet_len + cmdlen); + if (ret < 0) { + FTS_ERROR("app write fail"); + return ret; + } + mdelay(delay); + + /* read status */ + wr_ok = FTS_CMD_FLASH_STATUS_WRITE_OK + addr / packet_len; + for (j = 0; j < FTS_RETRIES_WRITE; j++) { + cmd = FTS_CMD_FLASH_STATUS; + ret = fts_read(&cmd , 1, val, FTS_CMD_FLASH_STATUS_LEN); + read_status = (((u16)val[0]) << 8) + val[1]; + /* FTS_INFO("%x %x", wr_ok, read_status); */ + if (read_status == wr_ok) + break; + + mdelay(FTS_RETRIES_DELAY_WRITE); + } + } + + ecc_in_host = (int)ecc_tmp; + if ((upg->func->fw_ecc_check_mode == ECC_CHECK_MODE_CRC16) || + (upg->func->upgspec_version >= UPGRADE_SPEC_V_1_0)) + ecc_in_host = (int)fts_crc16_calc_host(buf, len); + + return ecc_in_host; +} + +/************************************************************************ + * Name: fts_flash_read_buf + * Brief: read data from flash + * Input: saddr - start address data write to flash + * buf - buffer to store data read from flash + * len - read length + * Output: + * Return: return 0 if success, otherwise return error code + * + * Warning: can't call this function directly, need call in boot environment + ***********************************************************************/ +static int fts_flash_read_buf(u32 saddr, u8 *buf, u32 len) +{ + int ret = 0; + u32 i = 0; + u32 packet_number = 0; + u32 packet_len = 0; + u32 addr = 0; + u32 offset = 0; + u32 remainder = 0; + u8 wbuf[FTS_CMD_READ_LEN_SPI] = { 0 }; + struct fts_upgrade *upg = fwupgrade; + + if (!upg || !buf || !len) { + FTS_ERROR("upgrade/buf is NULL or len is 0"); + return -EINVAL; + } + + packet_number = len / FTS_FLASH_PACKET_LENGTH; + remainder = len % FTS_FLASH_PACKET_LENGTH; + if (remainder > 0) { + packet_number++; + } + packet_len = FTS_FLASH_PACKET_LENGTH; + FTS_INFO("read packet_number:%d, remainder:%d", packet_number, remainder); + + + for (i = 0; i < packet_number; i++) { + offset = i * FTS_FLASH_PACKET_LENGTH; + addr = saddr + offset; + /* last packet */ + if ((i == (packet_number - 1)) && remainder) + packet_len = remainder; + + if (upg->ts_data->bus_type == BUS_TYPE_I2C) { + wbuf[0] = FTS_CMD_READ; + wbuf[1] = BYTE_OFF_16(addr); + wbuf[2] = BYTE_OFF_8(addr); + wbuf[3] = BYTE_OFF_0(addr); + ret = fts_write(wbuf, FTS_CMD_READ_LEN); + if (ret < 0) { + FTS_ERROR("pram/bootloader write 03 command fail"); + return ret; + } + + msleep(FTS_CMD_READ_DELAY); /* must wait, otherwise read wrong data */ + ret = fts_read(NULL, 0, buf + offset, packet_len); + if (ret < 0) { + FTS_ERROR("pram/bootloader read 03 command fail"); + return ret; + } + } else if (upg->ts_data->bus_type == BUS_TYPE_SPI_V2) { + wbuf[0] = FTS_CMD_SET_RFLASH_ADDR; + wbuf[1] = BYTE_OFF_16(addr); + wbuf[2] = BYTE_OFF_8(addr); + wbuf[3] = BYTE_OFF_0(addr); + ret = fts_write(wbuf, FTS_LEN_SET_ADDR); + if (ret < 0) { + FTS_ERROR("set flash address fail"); + return ret; + } + + msleep(FTS_CMD_READ_DELAY); + wbuf[0] = FTS_CMD_READ; + ret = fts_read(wbuf, 1, buf + offset, packet_len); + if (ret < 0) { + FTS_ERROR("pram/bootloader read 03(SPI_V2) command fail"); + return ret; + } + } else if (upg->ts_data->bus_type == BUS_TYPE_SPI) { + wbuf[0] = FTS_CMD_READ; + wbuf[1] = BYTE_OFF_16(addr); + wbuf[2] = BYTE_OFF_8(addr); + wbuf[3] = BYTE_OFF_0(addr); + wbuf[4] = BYTE_OFF_8(packet_len); + wbuf[5] = BYTE_OFF_0(packet_len); + ret = fts_read(wbuf, FTS_CMD_READ_LEN_SPI, + buf + offset, packet_len); + if (ret < 0) { + FTS_ERROR("pram/bootloader read 03(SPI) command fail"); + return ret; + } + } + } + + return 0; +} + +/************************************************************************ + * Name: fts_flash_read + * Brief: + * Input: addr - address of flash + * len - length of read + * Output: buf - data read from flash + * Return: return 0 if success, otherwise return error code + ***********************************************************************/ +static int fts_flash_read(u32 addr, u8 *buf, u32 len) +{ + int ret = 0; + + FTS_INFO("***********read flash***********"); + if ((NULL == buf) || (0 == len)) { + FTS_ERROR("buf is NULL or len is 0"); + return -EINVAL; + } + + ret = fts_fwupg_enter_into_boot(); + if (ret < 0) { + FTS_ERROR("enter into pramboot/bootloader fail"); + goto read_flash_err; + } + + ret = fts_flash_read_buf(addr, buf, len); + if (ret < 0) { + FTS_ERROR("read flash fail"); + goto read_flash_err; + } + +read_flash_err: + /* reset to normal boot */ + ret = fts_fwupg_reset_in_boot(); + if (ret < 0) + FTS_ERROR("reset to normal boot fail"); + + return ret; +} + +int fts_enter_test_environment(bool test_state) +{ + return 0; +} +#if FTS_AUTO_LIC_UPGRADE_EN +static int fts_lic_get_vid_in_tp(u16 *vid) +{ + int ret = 0; + u8 val[2] = { 0 }; + + if (NULL == vid) { + FTS_ERROR("vid is NULL"); + return -EINVAL; + } + + ret = fts_read_reg(FTS_REG_VENDOR_ID, &val[0]); + if (fts_data->ic_info.is_incell) + ret = fts_read_reg(FTS_REG_MODULE_ID, &val[1]); + if (ret < 0) { + FTS_ERROR("read vid from tp fail"); + return ret; + } + + *vid = *(u16 *)val; + return 0; +} + +static int fts_lic_get_vid_in_host(struct fts_upgrade *upg, u16 *vid) +{ + u8 val[2] = { 0 }; + u8 *licbuf = NULL; + u32 conf_saddr = 0; + + if (!upg || !upg->func || !upg->lic || !vid) { + FTS_ERROR("upgrade/func/get_hlic_ver/lic/vid is null"); + return -EINVAL; + } + + if (upg->lic_length < FTS_MAX_LEN_SECTOR) { + FTS_ERROR("lic length(%x) fail", upg->lic_length); + return -EINVAL; + } + + licbuf = upg->lic; + conf_saddr = upg->func->fwcfgoff; + val[0] = licbuf[conf_saddr + FTS_CONIFG_VENDORID_OFF]; + if (fts_data->ic_info.is_incell) + val[1] = licbuf[conf_saddr + FTS_CONIFG_MODULEID_OFF]; + + *vid = *(u16 *)val; + return 0; +} + +static int fts_lic_get_ver_in_tp(u8 *ver) +{ + int ret = 0; + + if (NULL == ver) { + FTS_ERROR("ver is NULL"); + return -EINVAL; + } + + ret = fts_read_reg(FTS_REG_LIC_VER, ver); + if (ret < 0) { + FTS_ERROR("read lcd initcode ver from tp fail"); + return ret; + } + + return 0; +} + +static int fts_lic_get_ver_in_host(struct fts_upgrade *upg, u8 *ver) +{ + int ret = 0; + + if (!upg || !upg->func || !upg->func->get_hlic_ver || !upg->lic) { + FTS_ERROR("upgrade/func/get_hlic_ver/lic is null"); + return -EINVAL; + } + + ret = upg->func->get_hlic_ver(upg->lic); + if (ret < 0) { + FTS_ERROR("get host lcd initial code version fail"); + return ret; + } + + *ver = (u8)ret; + return ret; +} + +static bool fts_lic_need_upgrade(struct fts_upgrade *upg) +{ + int ret = 0; + u8 initcode_ver_in_tp = 0; + u8 initcode_ver_in_host = 0; + u16 vid_in_tp = 0; + u16 vid_in_host = 0; + bool fwvalid = false; + + fwvalid = fts_fwupg_check_fw_valid(); + if ( !fwvalid) { + FTS_INFO("fw is invalid, no upgrade lcd init code"); + return false; + } + + ret = fts_lic_get_vid_in_host(upg, &vid_in_host); + if (ret < 0) { + FTS_ERROR("vendor id in host invalid"); + return false; + } + + ret = fts_lic_get_vid_in_tp(&vid_in_tp); + if (ret < 0) { + FTS_ERROR("vendor id in tp invalid"); + return false; + } + + FTS_DEBUG("vid in tp:0x%04x, host:0x%04x", vid_in_tp, vid_in_host); + if (vid_in_tp != vid_in_host) { + FTS_INFO("vendor id in tp&host are different, no upgrade lic"); + return false; + } + + ret = fts_lic_get_ver_in_host(upg, &initcode_ver_in_host); + if (ret < 0) { + FTS_ERROR("init code in host invalid"); + return false; + } + + ret = fts_lic_get_ver_in_tp(&initcode_ver_in_tp); + if (ret < 0) { + FTS_ERROR("read reg0xE4 fail"); + return false; + } + + FTS_DEBUG("lcd initial code version in tp:%x, host:%x", + initcode_ver_in_tp, initcode_ver_in_host); + if (0xA5 == initcode_ver_in_tp) { + FTS_INFO("lcd init code ver is 0xA5, don't upgade init code"); + return false; + } else if (0xFF == initcode_ver_in_tp) { + FTS_DEBUG("lcd init code in tp is invalid, need upgrade init code"); + return true; + } else if (initcode_ver_in_tp < initcode_ver_in_host) + return true; + else + return false; +} + +static int fts_lic_upgrade(struct fts_upgrade *upg) +{ + int ret = 0; + bool hlic_upgrade = false; + int upgrade_count = 0; + u8 ver = 0; + + FTS_INFO("lcd initial code auto upgrade function"); + if ((!upg) || (!upg->func) || (!upg->func->lic_upgrade)) { + FTS_ERROR("lcd upgrade function is null"); + return -EINVAL; + } + + hlic_upgrade = fts_lic_need_upgrade(upg); + FTS_INFO("lcd init code upgrade flag:%d", hlic_upgrade); + if (hlic_upgrade) { + FTS_INFO("lcd initial code need upgrade, upgrade begin..."); + do { + FTS_INFO("lcd initial code upgrade times:%d", upgrade_count); + upgrade_count++; + + ret = upg->func->lic_upgrade(upg->lic, upg->lic_length); + if (ret < 0) { + fts_fwupg_reset_in_boot(); + } else { + fts_lic_get_ver_in_tp(&ver); + FTS_INFO("success upgrade to lcd initcode ver:%02x", ver); + break; + } + } while (upgrade_count < 2); + } else { + FTS_INFO("lcd initial code don't need upgrade"); + } + + return ret; +} +#endif /* FTS_AUTO_LIC_UPGRADE_EN */ + + +static int fts_param_get_ver_in_tp(u8 *ver) +{ + int ret = 0; + + if (NULL == ver) { + FTS_ERROR("ver is NULL"); + return -EINVAL; + } + + ret = fts_read_reg(FTS_REG_IDE_PARA_VER_ID, ver); + if (ret < 0) { + FTS_ERROR("read fw param ver from tp fail"); + return ret; + } + + if ((0x00 == *ver) || (0xFF == *ver)) { + FTS_INFO("param version in tp invalid"); + return -EIO; + } + + return 0; +} + +static int fts_param_get_ver_in_host(struct fts_upgrade *upg, u8 *ver) +{ + if ((!upg) || (!upg->func) || (!upg->fw) || (!ver)) { + FTS_ERROR("fts_data/upgrade/func/fw/ver is NULL"); + return -EINVAL; + } + + if (upg->fw_length < upg->func->paramcfgveroff) { + FTS_ERROR("fw len(%x) < paramcfg ver offset(%x)", + upg->fw_length, upg->func->paramcfgveroff); + return -EINVAL; + } + + FTS_INFO("fw paramcfg version offset:%x", upg->func->paramcfgveroff); + *ver = upg->fw[upg->func->paramcfgveroff]; + + if ((0x00 == *ver) || (0xFF == *ver)) { + FTS_INFO("param version in host invalid"); + return -EIO; + } + + return 0; +} + +/* + * return: < 0 : error + * == 0: no ide + * == 1: ide + */ +static int fts_param_ide_in_host(struct fts_upgrade *upg) +{ + u32 off = 0; + + if ((!upg) || (!upg->func) || (!upg->fw)) { + FTS_ERROR("fts_data/upgrade/func/fw is NULL"); + return -EINVAL; + } + + if (upg->fw_length < upg->func->paramcfgoff + FTS_FW_IDE_SIG_LEN) { + FTS_INFO("fw len(%x) < paramcfg offset(%x), no IDE", + upg->fw_length, upg->func->paramcfgoff + FTS_FW_IDE_SIG_LEN); + return 0; + } + + off = upg->func->paramcfgoff; + if (0 == memcmp(&upg->fw[off], FTS_FW_IDE_SIG, FTS_FW_IDE_SIG_LEN)) { + FTS_INFO("fw in host is IDE version"); + return 1; + } + + FTS_INFO("fw in host isn't IDE version"); + return 0; +} + +/* + * return: < 0 : error + * 0 : no ide + * 1 : ide + */ +static int fts_param_ide_in_tp(u8 *val) +{ + int ret = 0; + + ret = fts_read_reg(FTS_REG_IDE_PARA_STATUS, val); + if (ret < 0) { + FTS_ERROR("read IDE PARAM STATUS in tp fail"); + return ret; + } + + if ((*val != 0xFF) && ((*val & 0x80) == 0x80)) { + FTS_INFO("fw in tp is IDE version"); + return 1; + } + + FTS_INFO("fw in tp isn't IDE version"); + return 0; +} + +/************************************************************************ + * fts_param_need_upgrade - check fw paramcfg need upgrade or not + * + * Return: < 0 : error if paramcfg need upgrade + * 0 : no need upgrade + * 1 : need upgrade app + param + * 2 : need upgrade param + ***********************************************************************/ +static int fts_param_need_upgrade(struct fts_upgrade *upg) +{ + int ret = 0; + u8 val = 0; + int ide_in_host = 0; + int ide_in_tp = 0; + u8 ver_in_host = 0; + u8 ver_in_tp = 0; + bool fwvalid = false; + + fwvalid = fts_fwupg_check_fw_valid(); + if ( !fwvalid) { + FTS_INFO("fw is invalid, upgrade app+param"); + return 1; + } + + ide_in_host = fts_param_ide_in_host(upg); + if (ide_in_host < 0) { + FTS_INFO("fts_param_ide_in_host fail"); + return ide_in_host; + } + + ide_in_tp = fts_param_ide_in_tp(&val); + if (ide_in_tp < 0) { + FTS_INFO("fts_param_ide_in_tp fail"); + return ide_in_tp; + } + + if ((0 == ide_in_host) && (0 == ide_in_tp)) { + FTS_INFO("fw in host&tp are both no ide"); + return 0; + } else if (ide_in_host != ide_in_tp) { + FTS_INFO("fw in host&tp not equal, need upgrade app+param"); + return 1; + } else if ((1 == ide_in_host) && (1 == ide_in_tp)) { + FTS_INFO("fw in host&tp are both ide"); + if ((val & 0x7F) != 0x00) { + FTS_INFO("param invalid, need upgrade param"); + return 2; + } + + ret = fts_param_get_ver_in_host(upg, &ver_in_host); + if (ret < 0) { + FTS_ERROR("param version in host invalid"); + return ret; + } + + ret = fts_param_get_ver_in_tp(&ver_in_tp); + if (ret < 0) { + FTS_ERROR("get IDE param ver in tp fail"); + return ret; + } + + FTS_INFO("fw paramcfg version in tp:%x, host:%x", + ver_in_tp, ver_in_host); + if (ver_in_tp != ver_in_host) { + return 2; + } + } + + return 0; +} + +static int fts_fwupg_get_ver_in_tp(u8 *ver) +{ + int ret = 0; + + if (NULL == ver) { + FTS_ERROR("ver is NULL"); + return -EINVAL; + } + + ret = fts_read_reg(FTS_REG_FW_VER, ver); + if (ret < 0) { + FTS_ERROR("read fw ver from tp fail"); + return ret; + } + + return 0; +} + +static int fts_fwupg_get_ver_in_host(struct fts_upgrade *upg, u8 *ver) +{ + if ((!upg) || (!upg->func) || (!upg->fw) || (!ver)) { + FTS_ERROR("fts_data/upgrade/func/fw/ver is NULL"); + return -EINVAL; + } + + if (upg->fw_length < upg->func->fwveroff) { + FTS_ERROR("fw len(0x%0x) < fw ver offset(0x%x)", + upg->fw_length, upg->func->fwveroff); + return -EINVAL; + } + + FTS_INFO("fw version offset:0x%x", upg->func->fwveroff); + *ver = upg->fw[upg->func->fwveroff]; + return 0; +} + +static bool fts_fwupg_need_upgrade(struct fts_upgrade *upg) +{ + int ret = 0; + bool fwvalid = false; + u8 fw_ver_in_host = 0; + u8 fw_ver_in_tp = 0; + + fwvalid = fts_fwupg_check_fw_valid(); + if (fwvalid) { + ret = fts_fwupg_get_ver_in_host(upg, &fw_ver_in_host); + if (ret < 0) { + FTS_ERROR("get fw ver in host fail"); + return false; + } + + ret = fts_fwupg_get_ver_in_tp(&fw_ver_in_tp); + if (ret < 0) { + FTS_ERROR("get fw ver in tp fail"); + return false; + } + + FTS_INFO("fw version in tp:%x, host:%x", fw_ver_in_tp, fw_ver_in_host); + if (fw_ver_in_tp != fw_ver_in_host) { + return true; + } + } else { + FTS_INFO("fw invalid, need upgrade fw"); + return true; + } + + return false; +} + +/************************************************************************ + * Name: fts_fw_upgrade + * Brief: fw upgrade main entry, run in following steps + * 1. check fw version(A6), not equal, will upgrade app(+param) + * 2. if fw version equal, will check ide, will upgrade app(+param) + * in the follow situation + * a. host&tp IDE's type are not equal, will upgrade app+param + * b. host&tp are both IDE's type, and param's version are not + * equal, will upgrade param + * Input: + * Output: + * Return: return 0 if success, otherwise return error code + ***********************************************************************/ +int fts_fwupg_upgrade(struct fts_upgrade *upg) +{ + int ret = 0; + bool upgrade_flag = false; + int upgrade_count = 0; + u8 ver = 0; + + FTS_INFO("fw auto upgrade function"); + if ((NULL == upg) || (NULL == upg->func)) { + FTS_ERROR("upg/upg->func is null"); + return -EINVAL; + } + + upgrade_flag = fts_fwupg_need_upgrade(upg); + FTS_INFO("fw upgrade flag:%d", upgrade_flag); + do { + upgrade_count++; + if (upgrade_flag) { + FTS_INFO("upgrade fw app(times:%d)", upgrade_count); + if (upg->func->upgrade) { + ret = upg->func->upgrade(upg->fw, upg->fw_length); + if (ret < 0) { + fts_fwupg_reset_in_boot(); + } else { + fts_fwupg_get_ver_in_tp(&ver); + FTS_INFO("success upgrade to fw version %02x", ver); + break; + } + } else { + FTS_ERROR("upgrade func/upgrade is null, return immediately"); + ret = -ENODATA; + break; + } + } else { + if (upg->func->param_upgrade) { + ret = fts_param_need_upgrade(upg); + if (ret <= 0) { + FTS_INFO("param don't need upgrade"); + break; + } else if (1 == ret) { + FTS_INFO("force upgrade fw app(times:%d)", upgrade_count); + if (upg->func->upgrade) { + ret = upg->func->upgrade(upg->fw, upg->fw_length); + if (ret < 0) { + fts_fwupg_reset_in_boot(); + } else { + break; + } + } + } else if (2 == ret) { + FTS_INFO("upgrade param area(times:%d)", upgrade_count); + ret = upg->func->param_upgrade(upg->fw, upg->fw_length); + if (ret < 0) { + fts_fwupg_reset_in_boot(); + } else { + fts_param_get_ver_in_tp(&ver); + FTS_INFO("success upgrade to fw param version %02x", ver); + break; + } + } else + break; + } else { + break; + } + } + } while (upgrade_count < 2); + + return ret; +} + +/************************************************************************ + * fts_fwupg_auto_upgrade - upgrade main entry + ***********************************************************************/ +static void fts_fwupg_auto_upgrade(struct fts_upgrade *upg) +{ + int ret = 0; + + FTS_INFO("********************FTS enter upgrade********************"); + if (!upg || !upg->ts_data) { + FTS_ERROR("upg/ts_data is null"); + return ; + } + + ret = fts_fwupg_upgrade(upg); + if (ret < 0) + FTS_ERROR("**********tp fw(app/param) upgrade failed**********"); + else + FTS_INFO("**********tp fw(app/param) no upgrade/upgrade success**********"); + +#if FTS_AUTO_LIC_UPGRADE_EN + ret = fts_lic_upgrade(upg); + if (ret < 0) + FTS_ERROR("**********lcd init code upgrade failed**********"); + else + FTS_INFO("**********lcd init code no upgrade/upgrade success**********"); +#endif + + FTS_INFO("********************FTS exit upgrade********************"); +} + +static int fts_fwupg_get_vendorid(struct fts_upgrade *upg, int *vid) +{ + int ret = 0; + bool fwvalid = false; + u8 vendor_id = 0; + u8 module_id = 0; + u32 fwcfg_addr = 0; + u8 cmd = 0; + u8 cfgbuf[FTS_HEADER_LEN] = { 0 }; + + FTS_INFO("read vendor id from tp"); + if ((!upg) || (!upg->func) || (!upg->ts_data) || (!vid)) { + FTS_ERROR("upgrade/func/ts_data/vid is null"); + return -EINVAL; + } + + fwvalid = fts_fwupg_check_fw_valid(); + if (fwvalid) { + ret = fts_read_reg(FTS_REG_VENDOR_ID, &vendor_id); + if (upg->ts_data->ic_info.is_incell) + ret = fts_read_reg(FTS_REG_MODULE_ID, &module_id); + } else { + if (upg->func->upgspec_version >= UPGRADE_SPEC_V_1_0) { + cmd = FTS_CMD_READ_FW_CONF; + ret = fts_read(&cmd, 1, cfgbuf, FTS_HEADER_LEN); + } else { + fwcfg_addr = upg->func->fwcfgoff; + ret = fts_flash_read(fwcfg_addr, cfgbuf, FTS_HEADER_LEN); + } + + if ((cfgbuf[FTS_CONIFG_VENDORID_OFF] + + cfgbuf[FTS_CONIFG_VENDORID_OFF + 1]) == 0xFF) + vendor_id = cfgbuf[FTS_CONIFG_VENDORID_OFF]; + if (upg->ts_data->ic_info.is_incell) { + if ((cfgbuf[FTS_CONIFG_MODULEID_OFF] + + cfgbuf[FTS_CONIFG_MODULEID_OFF + 1]) == 0xFF) + module_id = cfgbuf[FTS_CONIFG_MODULEID_OFF]; + } + } + + if (ret < 0) { + FTS_ERROR("fail to get vendor id from tp"); + return ret; + } + + *vid = (int)((module_id << 8) + vendor_id); + return 0; +} + +static int fts_fwupg_get_module_info(struct fts_upgrade *upg) +{ + int ret = 0; + int i = 0; + struct upgrade_module *info = &module_list[0]; + + if (!upg || !upg->ts_data) { + FTS_ERROR("upg/ts_data is null"); + return -EINVAL; + } + + if (FTS_GET_MODULE_NUM > 1) { + /* support multi modules, must read correct module id(vendor id) */ + ret = fts_fwupg_get_vendorid(upg, &upg->module_id); + if (ret < 0) { + FTS_ERROR("get vendor id failed"); + return ret; + } + FTS_INFO("module id:%04x", upg->module_id); + for (i = 0; i < FTS_GET_MODULE_NUM; i++) { + info = &module_list[i]; + if (upg->module_id == info->id) { + FTS_INFO("module id match, get module info pass"); + break; + } + } + if (i >= FTS_GET_MODULE_NUM) { + info = &module_list[0]; + FTS_ERROR("no module id match, default to use first module"); + } + } + + upg->module_info = info; + return 0; +} + +static int fts_get_fw_file_via_request_firmware(struct fts_upgrade *upg) +{ + int ret = 0; + const struct firmware *fw = NULL; + u8 *tmpbuf = NULL; + char fwname[FILE_NAME_LENGTH] = { 0 }; + + if (!upg || !upg->ts_data || !upg->ts_data->dev) { + FTS_ERROR("upg/ts_data/dev is null"); + return -EINVAL; + } + + if (upg->ts_data->pdata->type == _FT3658U) + snprintf(fwname, FILE_NAME_LENGTH, "%s%s_ft3658.bin", + FTS_FW_NAME_PREX_WITH_REQUEST, + upg->module_info->vendor_name); + else + snprintf(fwname, FILE_NAME_LENGTH, "%s%s.bin", + FTS_FW_NAME_PREX_WITH_REQUEST, + upg->module_info->vendor_name); + + ret = request_firmware(&fw, fwname, upg->ts_data->dev); + if (ret == 0) { + FTS_INFO("firmware(%s) request successfully", fwname); + tmpbuf = vmalloc(fw->size); + if (NULL == tmpbuf) { + FTS_ERROR("fw buffer vmalloc fail"); + ret = -ENOMEM; + } else { + memcpy(tmpbuf, fw->data, fw->size); + upg->fw = tmpbuf; + upg->fw_length = fw->size; + upg->fw_from_request = 1; + } + } else { + FTS_INFO("firmware(%s) request fail,ret=%d", fwname, ret); + } + + if (fw != NULL) { + release_firmware(fw); + fw = NULL; + } + + return ret; +} + +static int fts_get_fw_file_via_i(struct fts_upgrade *upg) +{ + upg->fw = upg->module_info->fw_file; + upg->fw_length = upg->module_info->fw_len; + upg->fw_from_request = 0; + + return 0; +} + +/***************************************************************************** + * Name: fts_fwupg_get_fw_file + * Brief: get fw image/file, + * If support muitl modules, please set FTS_GET_MODULE_NUM, and FTS_- + * MODULE_ID/FTS_MODULE_NAME; + * If get fw via .i file, please set FTS_FW_REQUEST_SUPPORT=0, and F- + * TS_MODULE_ID; will use module id to distingwish different modules; + * If get fw via reques_firmware(), please set FTS_FW_REQUEST_SUPPORT + * =1, and FTS_MODULE_NAME; fw file name will be composed of "focalt- + * ech_ts_fw_" & FTS_VENDOR_NAME; + * + * If have flash, module_id=vendor_id, If non-flash,module_id need + * transfer from LCD driver(gpio or lcm_id or ...); + * Input: + * Output: + * Return: return 0 if success, otherwise return error code + *****************************************************************************/ +static int fts_fwupg_get_fw_file(struct fts_upgrade *upg) +{ + int ret = 0; + bool get_fw_i_flag = false; + + FTS_DEBUG("get upgrade fw file"); + if (!upg || !upg->ts_data) { + FTS_ERROR("upg/ts_data is null"); + return -EINVAL; + } + + ret = fts_fwupg_get_module_info(upg); + if ((ret < 0) || (!upg->module_info)) { + FTS_ERROR("get module info fail"); + return ret; + } + + if (FTS_FW_REQUEST_SUPPORT) { + ret = fts_get_fw_file_via_request_firmware(upg); + if (ret != 0) { + get_fw_i_flag = true; + } + } else { + get_fw_i_flag = true; + } + + if (get_fw_i_flag) { + ret = fts_get_fw_file_via_i(upg); + } + + upg->lic = upg->fw; + upg->lic_length = upg->fw_length; + + FTS_INFO("upgrade fw file len:%d", upg->fw_length); + if ((upg->fw_length < FTS_MIN_LEN) + || (upg->fw_length > FTS_MAX_LEN_FILE)) { + FTS_ERROR("fw file len(%d) fail", upg->fw_length); + return -ENODATA; + } + + return ret; +} + +static void fts_fwupg_init_ic_detail(struct fts_upgrade *upg) +{ + if (upg && upg->func && upg->func->init) { + upg->func->init(upg->fw, upg->fw_length); + } +} + +/***************************************************************************** + * Name: fts_fwupg_work + * Brief: 1. get fw image/file + * 2. ic init if have + * 3. call upgrade main function(fts_fwupg_auto_upgrade) + * Input: + * Output: + * Return: + *****************************************************************************/ +static void fts_fwupg_work(struct work_struct *work) +{ + int ret = 0; + struct fts_upgrade *upg = fwupgrade; + +#if !FTS_AUTO_UPGRADE_EN + FTS_INFO("FTS_AUTO_UPGRADE_EN is disabled, not upgrade when power on"); + return ; +#endif + + FTS_INFO("fw upgrade work function"); + if (!upg || !upg->ts_data) { + FTS_ERROR("upg/ts_data is null"); + return ; + } + + upg->ts_data->fw_loading = 1; + fts_irq_disable(); +#if FTS_ESDCHECK_EN + fts_esdcheck_switch(DISABLE); +#endif + + /* get fw */ + ret = fts_fwupg_get_fw_file(upg); + if (ret < 0) { + FTS_ERROR("get file fail, can't upgrade"); + } else { + /* ic init if have */ + fts_fwupg_init_ic_detail(upg); + /* run auto upgrade */ + fts_fwupg_auto_upgrade(upg); + } + +#if FTS_ESDCHECK_EN + fts_esdcheck_switch(ENABLE); +#endif + fts_irq_enable(); + upg->ts_data->fw_loading = 0; +} + +int fts_fwupg_init(struct fts_ts_data *ts_data) +{ + int i = 0; + int j = 0; + int ic_stype = 0; + struct upgrade_func *func = upgrade_func_list[0]; + int func_count = sizeof(upgrade_func_list) / sizeof(upgrade_func_list[0]); + + FTS_INFO("fw upgrade init function"); + + if (!ts_data || !ts_data->ts_workqueue) { + FTS_ERROR("ts_data/workqueue is NULL, can't run upgrade function"); + return -EINVAL; + } + + if (0 == func_count) { + FTS_ERROR("no upgrade function in tp driver"); + return -ENODATA; + } + + fwupgrade = (struct fts_upgrade *)kzalloc(sizeof(*fwupgrade), GFP_KERNEL); + if (NULL == fwupgrade) { + FTS_ERROR("malloc memory for upgrade fail"); + return -ENOMEM; + } + + ic_stype = ts_data->ic_info.ids.type; + if (1 == func_count) { + fwupgrade->func = func; + } else { + for (i = 0; i < func_count; i++) { + func = upgrade_func_list[i]; + for (j = 0; j < FTX_MAX_COMPATIBLE_TYPE; j++) { + if (0 == func->ctype[j]) + break; + else if (func->ctype[j] == ic_stype) { + FTS_INFO("match upgrade function,type:%x", (int)func->ctype[j]); + fwupgrade->func = func; + } + } + } + } + + if (NULL == fwupgrade->func) { + FTS_ERROR("no upgrade function match, can't upgrade"); + kfree(fwupgrade); + fwupgrade = NULL; + return -ENODATA; + } + + fwupgrade->ts_data = ts_data; + INIT_WORK(&ts_data->fwupg_work, fts_fwupg_work); + queue_work(ts_data->ts_workqueue, &ts_data->fwupg_work); + + return 0; +} + +int fts_fwupg_exit(struct fts_ts_data *ts_data) +{ + FTS_FUNC_ENTER(); + if (fwupgrade) { + if (fwupgrade->fw_from_request) { + vfree(fwupgrade->fw); + fwupgrade->fw = NULL; + } + + kfree(fwupgrade); + fwupgrade = NULL; + } + FTS_FUNC_EXIT(); + return 0; +} diff --git a/qcom/opensource/touch-drivers/focaltech_touch/focaltech_flash.h b/qcom/opensource/touch-drivers/focaltech_touch/focaltech_flash.h new file mode 100644 index 0000000000..8518ef460a --- /dev/null +++ b/qcom/opensource/touch-drivers/focaltech_touch/focaltech_flash.h @@ -0,0 +1,216 @@ +/************************************************************************ +* Copyright (C) 2012-2019, Focaltech Systems (R)£¬All Rights Reserved. +* +* File Name: focaltech_flash.h +* +* Author: Focaltech Driver Team +* +* Created: 2016-08-07 +* +* Abstract: +* +************************************************************************/ +#ifndef __LINUX_FOCALTECH_FLASH_H__ +#define __LINUX_FOCALTECH_FLASH_H__ + +/***************************************************************************** +* 1.Included header files +*****************************************************************************/ +#include "focaltech_core.h" + +/***************************************************************************** +* Private constant and macro definitions using #define +*****************************************************************************/ +#define FTS_CMD_RESET 0x07 +#define FTS_ROMBOOT_CMD_SET_PRAM_ADDR 0xAD +#define FTS_ROMBOOT_CMD_SET_PRAM_ADDR_LEN 4 +#define FTS_ROMBOOT_CMD_WRITE 0xAE +#define FTS_ROMBOOT_CMD_START_APP 0x08 +#define FTS_DELAY_PRAMBOOT_START 100 +#define FTS_ROMBOOT_CMD_ECC 0xCC +#define FTS_PRAM_SADDR 0x000000 +#define FTS_DRAM_SADDR 0xD00000 + +#define FTS_CMD_READ 0x03 +#define FTS_CMD_READ_DELAY 1 +#define FTS_CMD_READ_LEN 4 +#define FTS_CMD_READ_LEN_SPI 6 +#define FTS_CMD_FLASH_TYPE 0x05 +#define FTS_CMD_FLASH_MODE 0x09 +#define FLASH_MODE_WRITE_FLASH_VALUE 0x0A +#define FLASH_MODE_UPGRADE_VALUE 0x0B +#define FLASH_MODE_LIC_VALUE 0x0C +#define FLASH_MODE_PARAM_VALUE 0x0D +#define FTS_CMD_ERASE_APP 0x61 +#define FTS_REASE_APP_DELAY 1350 +#define FTS_ERASE_SECTOR_DELAY 60 +#define FTS_RETRIES_REASE 50 +#define FTS_RETRIES_DELAY_REASE 400 +#define FTS_CMD_FLASH_STATUS 0x6A +#define FTS_CMD_FLASH_STATUS_LEN 2 +#define FTS_CMD_FLASH_STATUS_NOP 0x0000 +#define FTS_CMD_FLASH_STATUS_ECC_OK 0xF055 +#define FTS_CMD_FLASH_STATUS_ERASE_OK 0xF0AA +#define FTS_CMD_FLASH_STATUS_WRITE_OK 0x1000 +#define FTS_CMD_ECC_INIT 0x64 +#define FTS_CMD_ECC_CAL 0x65 +#define FTS_CMD_ECC_CAL_LEN 7 +#define FTS_RETRIES_ECC_CAL 10 +#define FTS_RETRIES_DELAY_ECC_CAL 50 +#define FTS_CMD_ECC_READ 0x66 +#define FTS_CMD_SET_WFLASH_ADDR 0xAB +#define FTS_CMD_SET_RFLASH_ADDR 0xAC +#define FTS_LEN_SET_ADDR 4 +#define FTS_CMD_DATA_LEN 0xB0 +#define FTS_CMD_APP_DATA_LEN_INCELL 0x7A +#define FTS_CMD_DATA_LEN_LEN 4 +#define FTS_CMD_WRITE 0xBF +#define FTS_RETRIES_WRITE 100 +#define FTS_RETRIES_DELAY_WRITE 1 +#define FTS_CMD_WRITE_LEN 6 +#define FTS_DELAY_READ_ID 20 +#define FTS_DELAY_UPGRADE_RESET 80 +#define PRAMBOOT_MIN_SIZE 0x120 +#define PRAMBOOT_MAX_SIZE (64*1024) +#define FTS_FLASH_PACKET_LENGTH 32 /* max=128 */ +#define FTS_MAX_LEN_ECC_CALC 0xFFFE /* must be even */ +#define FTS_MIN_LEN 0x120 +#define FTS_MAX_LEN_FILE (128 * 1024) +#define FTS_MAX_LEN_APP (64 * 1024) +#define FTS_MAX_LEN_SECTOR (4 * 1024) +#define FTS_CONIFG_VENDORID_OFF 0x04 +#define FTS_CONIFG_MODULEID_OFF 0x1E +#define FTS_CONIFG_PROJECTID_OFF 0x20 +#define FTS_APPINFO_OFF 0x100 +#define FTS_APPINFO_APPLEN_OFF 0x00 +#define FTS_APPINFO_APPLEN2_OFF 0x12 +#define FTS_REG_UPGRADE 0xFC +#define FTS_REG_UPGRADE2 0xBC +#define FTS_UPGRADE_AA 0xAA +#define FTS_UPGRADE_55 0x55 +#define FTS_DELAY_UPGRADE_AA 10 +#define FTS_UPGRADE_LOOP 30 +#define FTS_HEADER_LEN 32 +#define FTS_FW_BIN_FILEPATH "/sdcard/" +#define FTS_FW_IDE_SIG "IDE_" +#define FTS_FW_IDE_SIG_LEN 4 +#define MAX_MODULE_VENDOR_NAME_LEN 16 + +#define FTS_ROMBOOT_CMD_ECC_NEW_LEN 7 +#define FTS_ECC_FINISH_TIMEOUT 100 +#define FTS_ROMBOOT_CMD_ECC_FINISH 0xCE +#define FTS_ROMBOOT_CMD_ECC_FINISH_OK_A5 0xA5 +#define FTS_ROMBOOT_CMD_ECC_FINISH_OK_00 0x00 +#define FTS_ROMBOOT_CMD_ECC_READ 0xCD +#define AL2_FCS_COEF ((1 << 15) + (1 << 10) + (1 << 3)) + +#define FTS_APP_INFO_OFFSET 0x100 + +enum FW_STATUS { + FTS_RUN_IN_ERROR, + FTS_RUN_IN_APP, + FTS_RUN_IN_ROM, + FTS_RUN_IN_PRAM, + FTS_RUN_IN_BOOTLOADER, +}; + +enum FW_FLASH_MODE { + FLASH_MODE_APP, + FLASH_MODE_LIC, + FLASH_MODE_PARAM, + FLASH_MODE_ALL, +}; + +enum ECC_CHECK_MODE { + ECC_CHECK_MODE_XOR, + ECC_CHECK_MODE_CRC16, +}; + +enum UPGRADE_SPEC { + UPGRADE_SPEC_V_1_0 = 0x0100, +}; + +/***************************************************************************** +* Private enumerations, structures and unions using typedef +*****************************************************************************/ +/* IC info */ +struct upgrade_func { + u64 ctype[FTX_MAX_COMPATIBLE_TYPE]; + u32 fwveroff; + u32 fwcfgoff; + u32 appoff; + u32 licoff; + u32 paramcfgoff; + u32 paramcfgveroff; + u32 paramcfg2off; + int pram_ecc_check_mode; + int fw_ecc_check_mode; + int upgspec_version; + bool new_return_value_from_ic; + bool appoff_handle_in_ic; + bool is_reset_register_BC; + bool read_boot_id_need_reset; + bool hid_supported; + bool pramboot_supported; + u8 *pramboot; + u32 pb_length; + int (*init)(u8 *, u32); + int (*write_pramboot_private)(void); + int (*upgrade)(u8 *, u32); + int (*get_hlic_ver)(u8 *); + int (*lic_upgrade)(u8 *, u32); + int (*param_upgrade)(u8 *, u32); + int (*force_upgrade)(u8 *, u32); +}; + +struct upgrade_setting_nf { + u8 rom_idh; + u8 rom_idl; + u16 reserved; + u32 app2_offset; + u32 ecclen_max; + u8 eccok_val; + u8 upgsts_boot; + u8 delay_init; + bool spi_pe; + bool half_length; + bool fd_check; + bool drwr_support; +}; + +struct upgrade_module { + int id; + char vendor_name[MAX_MODULE_VENDOR_NAME_LEN]; + u8 *fw_file; + u32 fw_len; +}; + +struct fts_upgrade { + struct fts_ts_data *ts_data; + struct upgrade_module *module_info; + struct upgrade_func *func; + struct upgrade_setting_nf *setting_nf; + int module_id; + bool fw_from_request; + u8 *fw; + u32 fw_length; + u8 *lic; + u32 lic_length; +}; + +/***************************************************************************** +* Global variable or extern global variabls/functions +*****************************************************************************/ +extern struct upgrade_func upgrade_func_ft5452; +extern struct upgrade_func upgrade_func_ft5652; + +/***************************************************************************** +* Static function prototypes +*****************************************************************************/ +int fts_fwupg_reset_in_boot(void); +int fts_fwupg_enter_into_boot(void); +int fts_fwupg_erase(u32 delay); +int fts_fwupg_ecc_cal(u32 saddr, u32 len); +int fts_flash_write_buf(u32 saddr, u8 *buf, u32 len, u32 delay); +int fts_fwupg_upgrade(struct fts_upgrade *upg); +#endif diff --git a/qcom/opensource/touch-drivers/focaltech_touch/focaltech_flash/focaltech_upgrade_ft3518.c b/qcom/opensource/touch-drivers/focaltech_touch/focaltech_flash/focaltech_upgrade_ft3518.c new file mode 100644 index 0000000000..c42e7b5ad9 --- /dev/null +++ b/qcom/opensource/touch-drivers/focaltech_touch/focaltech_flash/focaltech_upgrade_ft3518.c @@ -0,0 +1,292 @@ +/* + * + * FocalTech fts TouchScreen driver. + * + * Copyright (c) 2012-2019, Focaltech Ltd. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/***************************************************************************** +* +* File Name: focaltech_upgrade_ft5452.c +* +* Author: Focaltech Driver Team +* +* Created: 2016-08-15 +* +* Abstract: +* +* Reference: +* +*****************************************************************************/ +/***************************************************************************** +* 1.Included header files +*****************************************************************************/ +#include "../focaltech_flash.h" + +/************************************************************************ +* Name: fts_ft5452_upgrade +* Brief: +* Input: +* Output: +* Return: return 0 if success, otherwise return error code +***********************************************************************/ +static int fts_ft5452_upgrade(u8 *buf, u32 len) +{ + int ret = 0; + u32 start_addr = 0; + u8 cmd[4] = { 0 }; + int ecc_in_host = 0; + int ecc_in_tp = 0; + + int i = 0; + u8 wbuf[7] = { 0 }; + u8 reg_val[4] = {0}; + + if (NULL == buf) { + FTS_ERROR("fw buf is null"); + return -EINVAL; + } + + if ((len < FTS_MIN_LEN) || (len > (60 * 1024))) { + FTS_ERROR("fw buffer len(%x) fail", len); + return -EINVAL; + } + + /* enter into upgrade environment */ + ret = fts_fwupg_enter_into_boot(); + if (ret < 0) { + FTS_ERROR("enter into pramboot/bootloader fail,ret=%d", ret); + goto fw_reset; + } + + cmd[0] = FTS_CMD_FLASH_MODE; + cmd[1] = FLASH_MODE_UPGRADE_VALUE; + ret = fts_write(cmd, 2); + if (ret < 0) { + FTS_ERROR("upgrade mode(09) cmd write fail"); + goto fw_reset; + } + + cmd[0] = FTS_CMD_DATA_LEN; + cmd[1] = BYTE_OFF_16(len); + cmd[2] = BYTE_OFF_8(len); + cmd[3] = BYTE_OFF_0(len); + ret = fts_write(cmd, FTS_CMD_DATA_LEN_LEN); + if (ret < 0) { + FTS_ERROR("data len cmd write fail"); + goto fw_reset; + } + + ret = fts_fwupg_erase(FTS_REASE_APP_DELAY); + if (ret < 0) { + FTS_ERROR("erase cmd write fail"); + goto fw_reset; + } + + /* write app */ + start_addr = upgrade_func_ft5452.appoff; + ecc_in_host = fts_flash_write_buf(start_addr, buf, len, 1); + if (ecc_in_host < 0 ) { + FTS_ERROR("lcd initial code write fail"); + goto fw_reset; + } + + FTS_INFO( "**********read out checksum**********"); + + /* check sum init */ + wbuf[0] = FTS_CMD_ECC_INIT; + ret = fts_write(wbuf, 1); + if (ret < 0) { + FTS_ERROR("ecc init cmd write fail"); + return ret; + } + + /* send commond to start checksum */ + wbuf[0] = FTS_CMD_ECC_CAL; + wbuf[1] = BYTE_OFF_16(start_addr); + wbuf[2] = BYTE_OFF_8(start_addr); + wbuf[3] = BYTE_OFF_0(start_addr); + + wbuf[4] = BYTE_OFF_16(len); + wbuf[5] = BYTE_OFF_8(len); + wbuf[6] = BYTE_OFF_0(len); + + FTS_DEBUG("ecc calc startaddr:0x%04x, len:%d", start_addr, len); + ret = fts_write(wbuf, 7); + if (ret < 0) { + FTS_ERROR("ecc calc cmd write fail"); + return ret; + } + + msleep(len / 256); + + /* read status if check sum is finished */ + for (i = 0; i < FTS_RETRIES_ECC_CAL; i++) { + wbuf[0] = FTS_CMD_FLASH_STATUS; + reg_val[0] = reg_val[1] = 0x00; + fts_read(wbuf, 1, reg_val, 2); + FTS_DEBUG("[UPGRADE]: reg_val[0]=%02x reg_val[0]=%02x!!", reg_val[0], reg_val[1]); + if ((0xF0 == reg_val[0]) && (0x55 == reg_val[1])) { + break; + } + msleep(FTS_RETRIES_DELAY_ECC_CAL); + } + + /* read out check sum */ + wbuf[0] = FTS_CMD_ECC_READ; + ret = fts_read(wbuf, 1, reg_val, 1); + if (ret < 0) { + FTS_ERROR( "ecc read cmd write fail"); + return ret; + } + ecc_in_tp = reg_val[0]; + + FTS_INFO("ecc in tp:%x, host:%x", ecc_in_tp, ecc_in_host); + if (ecc_in_tp != ecc_in_host) { + FTS_ERROR("ecc check fail"); + goto fw_reset; + } + + FTS_INFO("upgrade success, reset to normal boot"); + ret = fts_fwupg_reset_in_boot(); + if (ret < 0) { + FTS_ERROR("reset to normal boot fail"); + } + + msleep(200); + return 0; + +fw_reset: + FTS_INFO("upgrade fail, reset to normal boot"); + ret = fts_fwupg_reset_in_boot(); + if (ret < 0) { + FTS_ERROR("reset to normal boot fail"); + } + return -EIO; +} + +struct upgrade_func upgrade_func_ft5452 = { + .ctype = {0x81}, + .fwveroff = 0x010E, + .fwcfgoff = 0x1FFB0, + .appoff = 0x0000, + .pramboot_supported = false, + .hid_supported = true, + .upgrade = fts_ft5452_upgrade, +}; + + +#define FTS_DELAY_ERASE_PAGE_2K 80 +#define FTS_SIZE_PAGE_2K 2048 + +/************************************************************************ + * Name: fts_ft5652_upgrade + * Brief: + * Input: + * Output: + * Return: return 0 if success, otherwise return error code + **********************************************************************/ +static int fts_ft5652_upgrade(u8 *buf, u32 len) +{ + int ret = 0; + u32 start_addr = 0; + u8 cmd[4] = { 0 }; + u32 delay = 0; + int ecc_in_host = 0; + int ecc_in_tp = 0; + + if ((buf == NULL) || (len < FTS_MIN_LEN)) { + FTS_ERROR("buffer/len(%x) is invalid", len); + return -EINVAL; + } + + /* enter into upgrade environment */ + ret = fts_fwupg_enter_into_boot(); + if (ret < 0) { + FTS_ERROR("enter into pramboot/bootloader fail,ret=%d", ret); + goto fw_reset; + } + + cmd[0] = FTS_CMD_APP_DATA_LEN_INCELL; + cmd[1] = BYTE_OFF_16(len); + cmd[2] = BYTE_OFF_8(len); + cmd[3] = BYTE_OFF_0(len); + ret = fts_write(cmd, FTS_CMD_DATA_LEN_LEN); + if (ret < 0) { + FTS_ERROR("data len cmd write fail"); + goto fw_reset; + } + + cmd[0] = FTS_CMD_FLASH_MODE; + cmd[1] = FLASH_MODE_UPGRADE_VALUE; + ret = fts_write(cmd, 2); + if (ret < 0) { + FTS_ERROR("upgrade mode(09) cmd write fail"); + goto fw_reset; + } + + delay = FTS_DELAY_ERASE_PAGE_2K * (len / FTS_SIZE_PAGE_2K); + ret = fts_fwupg_erase(delay); + if (ret < 0) { + FTS_ERROR("erase cmd write fail"); + goto fw_reset; + } + + /* write app */ + start_addr = upgrade_func_ft5652.appoff; + ecc_in_host = fts_flash_write_buf(start_addr, buf, len, 1); + if (ecc_in_host < 0) { + FTS_ERROR("flash write fail"); + goto fw_reset; + } + + /* ecc */ + ecc_in_tp = fts_fwupg_ecc_cal(start_addr, len); + if (ecc_in_tp < 0) { + FTS_ERROR("ecc read fail"); + goto fw_reset; + } + + FTS_INFO("ecc in tp:%x, host:%x", ecc_in_tp, ecc_in_host); + if (ecc_in_tp != ecc_in_host) { + FTS_ERROR("ecc check fail"); + goto fw_reset; + } + + FTS_INFO("upgrade success, reset to normal boot"); + ret = fts_fwupg_reset_in_boot(); + if (ret < 0) + FTS_ERROR("reset to normal boot fail"); + + msleep(200); + return 0; + +fw_reset: + FTS_INFO("upgrade fail, reset to normal boot"); + ret = fts_fwupg_reset_in_boot(); + if (ret < 0) + FTS_ERROR("reset to normal boot fail"); + + return -EIO; +} + +struct upgrade_func upgrade_func_ft5652 = { + .ctype = {0x88}, + .fwveroff = 0x010E, + .fwcfgoff = 0x1F80, + .appoff = 0x0000, + .upgspec_version = UPGRADE_SPEC_V_1_0, + .pramboot_supported = false, + .hid_supported = true, + .upgrade = fts_ft5652_upgrade, +}; diff --git a/qcom/opensource/touch-drivers/focaltech_touch/focaltech_gesture.c b/qcom/opensource/touch-drivers/focaltech_touch/focaltech_gesture.c new file mode 100644 index 0000000000..92fe990967 --- /dev/null +++ b/qcom/opensource/touch-drivers/focaltech_touch/focaltech_gesture.c @@ -0,0 +1,477 @@ +/* + * + * FocalTech TouchScreen driver. + * + * Copyright (c) 2012-2019, Focaltech Ltd. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/***************************************************************************** +* +* File Name: focaltech_gestrue.c +* +* Author: Focaltech Driver Team +* +* Created: 2016-08-08 +* +* Abstract: +* +* Reference: +* +*****************************************************************************/ + +/***************************************************************************** +* 1.Included header files +*****************************************************************************/ +#include "focaltech_core.h" + +/****************************************************************************** +* Private constant and macro definitions using #define +*****************************************************************************/ +#define KEY_GESTURE_U KEY_U +#define KEY_GESTURE_UP KEY_UP +#define KEY_GESTURE_DOWN KEY_DOWN +#define KEY_GESTURE_LEFT KEY_LEFT +#define KEY_GESTURE_RIGHT KEY_RIGHT +#define KEY_GESTURE_O KEY_O +#define KEY_GESTURE_E KEY_E +#define KEY_GESTURE_M KEY_M +#define KEY_GESTURE_L KEY_L +#define KEY_GESTURE_W KEY_W +#define KEY_GESTURE_S KEY_S +#define KEY_GESTURE_V KEY_V +#define KEY_GESTURE_C KEY_C +#define KEY_GESTURE_Z KEY_Z + +#define GESTURE_LEFT 0x20 +#define GESTURE_RIGHT 0x21 +#define GESTURE_UP 0x22 +#define GESTURE_DOWN 0x23 +#define GESTURE_DOUBLECLICK 0x24 +#define GESTURE_O 0x30 +#define GESTURE_W 0x31 +#define GESTURE_M 0x32 +#define GESTURE_E 0x33 +#define GESTURE_L 0x44 +#define GESTURE_S 0x46 +#define GESTURE_V 0x54 +#define GESTURE_Z 0x41 +#define GESTURE_C 0x34 + +/***************************************************************************** +* Private enumerations, structures and unions using typedef +*****************************************************************************/ +/* +* gesture_id - mean which gesture is recognised +* point_num - points number of this gesture +* coordinate_x - All gesture point x coordinate +* coordinate_y - All gesture point y coordinate +* mode - gesture enable/disable, need enable by host +* - 1:enable gesture function(default) 0:disable +* active - gesture work flag, +* always set 1 when suspend, set 0 when resume +*/ +struct fts_gesture_st { + u8 gesture_id; + u8 point_num; + u16 coordinate_x[FTS_GESTURE_POINTS_MAX]; + u16 coordinate_y[FTS_GESTURE_POINTS_MAX]; +}; + +/***************************************************************************** +* Static variables +*****************************************************************************/ +static struct fts_gesture_st fts_gesture_data; + +/***************************************************************************** +* Global variable or extern global variabls/functions +*****************************************************************************/ + +/***************************************************************************** +* Static function prototypes +*****************************************************************************/ +static ssize_t fts_gesture_show( + struct device *dev, struct device_attribute *attr, char *buf) +{ + int count = 0; + u8 val = 0; + struct fts_ts_data *ts_data = fts_data; + + mutex_lock(&ts_data->input_dev->mutex); + fts_read_reg(FTS_REG_GESTURE_EN, &val); + count = snprintf(buf, PAGE_SIZE, "Gesture Mode:%s\n", + ts_data->gesture_mode ? "On" : "Off"); + count += snprintf(buf + count, PAGE_SIZE, "Reg(0xD0)=%d\n", val); + mutex_unlock(&ts_data->input_dev->mutex); + + return count; +} + +static ssize_t fts_gesture_store( + struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct fts_ts_data *ts_data = fts_data; + + mutex_lock(&ts_data->input_dev->mutex); + if (FTS_SYSFS_ECHO_ON(buf)) { + FTS_DEBUG("enable gesture"); + ts_data->gesture_mode = ENABLE; + } else if (FTS_SYSFS_ECHO_OFF(buf)) { + FTS_DEBUG("disable gesture"); + ts_data->gesture_mode = DISABLE; + } + mutex_unlock(&ts_data->input_dev->mutex); + + return count; +} + +static ssize_t fts_gesture_buf_show( + struct device *dev, struct device_attribute *attr, char *buf) +{ + int count = 0; + int i = 0; + struct input_dev *input_dev = fts_data->input_dev; + struct fts_gesture_st *gesture = &fts_gesture_data; + + mutex_lock(&input_dev->mutex); + count = snprintf(buf, PAGE_SIZE, "Gesture ID:%d\n", gesture->gesture_id); + count += snprintf(buf + count, PAGE_SIZE, "Gesture PointNum:%d\n", + gesture->point_num); + count += snprintf(buf + count, PAGE_SIZE, "Gesture Points Buffer:\n"); + + /* save point data,max:6 */ + for (i = 0; i < FTS_GESTURE_POINTS_MAX; i++) { + count += snprintf(buf + count, PAGE_SIZE, "%3d(%4d,%4d) ", i, + gesture->coordinate_x[i], gesture->coordinate_y[i]); + + if ((i + 1) % 4 == 0) + count += snprintf(buf + count, PAGE_SIZE, "\n"); + } + count += snprintf(buf + count, PAGE_SIZE, "\n"); + mutex_unlock(&input_dev->mutex); + + return count; +} + +static ssize_t fts_gesture_buf_store( + struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + return -EPERM; +} + + +/* sysfs gesture node + * read example: cat fts_gesture_mode ---read gesture mode + * write example:echo 1 > fts_gesture_mode --- write gesture mode to 1 + * + */ +static DEVICE_ATTR(fts_gesture_mode, S_IRUGO | S_IWUSR, fts_gesture_show, + fts_gesture_store); +/* + * read example: cat fts_gesture_buf --- read gesture buf + */ +static DEVICE_ATTR(fts_gesture_buf, S_IRUGO | S_IWUSR, + fts_gesture_buf_show, fts_gesture_buf_store); + +static struct attribute *fts_gesture_mode_attrs[] = { + &dev_attr_fts_gesture_mode.attr, + &dev_attr_fts_gesture_buf.attr, + NULL, +}; + +static struct attribute_group fts_gesture_group = { + .attrs = fts_gesture_mode_attrs, +}; + +static int fts_create_gesture_sysfs(struct device *dev) +{ + int ret = 0; + + ret = sysfs_create_group(&dev->kobj, &fts_gesture_group); + if (ret) { + FTS_ERROR("gesture sys node create fail"); + sysfs_remove_group(&dev->kobj, &fts_gesture_group); + return ret; + } + + return 0; +} + +static void fts_gesture_report(struct input_dev *input_dev, int gesture_id) +{ + int gesture; + + FTS_DEBUG("gesture_id:0x%x", gesture_id); + switch (gesture_id) { + case GESTURE_LEFT: + gesture = KEY_GESTURE_LEFT; + break; + + case GESTURE_RIGHT: + gesture = KEY_GESTURE_RIGHT; + break; + + case GESTURE_UP: + gesture = KEY_GESTURE_UP; + break; + + case GESTURE_DOWN: + gesture = KEY_GESTURE_DOWN; + break; + + case GESTURE_DOUBLECLICK: + gesture = KEY_POWER; + + break; + + case GESTURE_O: + gesture = KEY_GESTURE_O; + break; + + case GESTURE_W: + gesture = KEY_GESTURE_W; + break; + + case GESTURE_M: + gesture = KEY_GESTURE_M; + break; + + case GESTURE_E: + gesture = KEY_GESTURE_E; + break; + + case GESTURE_L: + gesture = KEY_GESTURE_L; + break; + + case GESTURE_S: + gesture = KEY_GESTURE_S; + break; + + case GESTURE_V: + gesture = KEY_GESTURE_V; + break; + + case GESTURE_Z: + gesture = KEY_GESTURE_Z; + break; + + case GESTURE_C: + gesture = KEY_GESTURE_C; + break; + + default: + gesture = -1; + break; + } + + /* report event key */ + if (gesture != -1) { + FTS_DEBUG("Gesture Code=%d", gesture); + input_report_key(input_dev, gesture, 1); + input_sync(input_dev); + input_report_key(input_dev, gesture, 0); + input_sync(input_dev); + } +} + +/***************************************************************************** +* Name: fts_gesture_readdata +* Brief: Read information about gesture: enable flag/gesture points..., if ges- +* ture enable, save gesture points' information, and report to OS. +* It will be called this function every intrrupt when FTS_GESTURE_EN = 1 +* +* gesture data length: 1(enable) + 1(reserve) + 2(header) + 6 * 4 +* Input: ts_data - global struct data +* data - gesture data buffer if non-flash, else NULL +* Output: +* Return: 0 - read gesture data successfully, the report data is gesture data +* 1 - tp not in suspend/gesture not enable in TP FW +* -Exx - error +*****************************************************************************/ +int fts_gesture_readdata(struct fts_ts_data *ts_data, u8 *data) +{ + int ret = 0; + int i = 0; + int index = 0; + u8 buf[FTS_GESTURE_DATA_LEN] = { 0 }; + struct input_dev *input_dev = ts_data->input_dev; + struct fts_gesture_st *gesture = &fts_gesture_data; + + if (!ts_data->suspended || !ts_data->gesture_mode) { + return 1; + } + + + ret = fts_read_reg(FTS_REG_GESTURE_EN, &buf[0]); + if ((ret < 0) || (buf[0] != ENABLE)) { + FTS_DEBUG("gesture not enable in fw, don't process gesture"); + return 1; + } + + buf[2] = FTS_REG_GESTURE_OUTPUT_ADDRESS; + ret = fts_read(&buf[2], 1, &buf[2], FTS_GESTURE_DATA_LEN - 2); + if (ret < 0) { + FTS_ERROR("read gesture header data fail"); + return ret; + } + + /* init variable before read gesture point */ + memset(gesture->coordinate_x, 0, FTS_GESTURE_POINTS_MAX * sizeof(u16)); + memset(gesture->coordinate_y, 0, FTS_GESTURE_POINTS_MAX * sizeof(u16)); + gesture->gesture_id = buf[2]; + gesture->point_num = buf[3]; + FTS_DEBUG("gesture_id=%d, point_num=%d", + gesture->gesture_id, gesture->point_num); + + /* save point data,max:6 */ + for (i = 0; i < FTS_GESTURE_POINTS_MAX; i++) { + index = 4 * i + 4; + gesture->coordinate_x[i] = (u16)(((buf[0 + index] & 0x0F) << 8) + + buf[1 + index]); + gesture->coordinate_y[i] = (u16)(((buf[2 + index] & 0x0F) << 8) + + buf[3 + index]); + } + + /* report gesture to OS */ + fts_gesture_report(input_dev, gesture->gesture_id); + return 0; +} + +void fts_gesture_recovery(struct fts_ts_data *ts_data) +{ + if (ts_data->gesture_mode && ts_data->suspended) { + FTS_DEBUG("gesture recovery..."); + fts_write_reg(0xD1, 0xFF); + fts_write_reg(0xD2, 0xFF); + fts_write_reg(0xD5, 0xFF); + fts_write_reg(0xD6, 0xFF); + fts_write_reg(0xD7, 0xFF); + fts_write_reg(0xD8, 0xFF); + fts_write_reg(FTS_REG_GESTURE_EN, ENABLE); + } +} + +int fts_gesture_suspend(struct fts_ts_data *ts_data) +{ + int i = 0; + u8 state = 0xFF; + + FTS_FUNC_ENTER(); + if (enable_irq_wake(ts_data->irq)) { + FTS_DEBUG("enable_irq_wake(irq:%d) fail", ts_data->irq); + } + + for (i = 0; i < 5; i++) { + fts_write_reg(0xD1, 0xFF); + fts_write_reg(0xD2, 0xFF); + fts_write_reg(0xD5, 0xFF); + fts_write_reg(0xD6, 0xFF); + fts_write_reg(0xD7, 0xFF); + fts_write_reg(0xD8, 0xFF); + fts_write_reg(FTS_REG_GESTURE_EN, ENABLE); + msleep(1); + fts_read_reg(FTS_REG_GESTURE_EN, &state); + if (state == ENABLE) + break; + } + + if (i >= 5) + FTS_ERROR("make IC enter into gesture(suspend) fail,state:%x", state); + else + FTS_INFO("Enter into gesture(suspend) successfully"); + + FTS_FUNC_EXIT(); + return 0; +} + +int fts_gesture_resume(struct fts_ts_data *ts_data) +{ + int i = 0; + u8 state = 0xFF; + + FTS_FUNC_ENTER(); + if (disable_irq_wake(ts_data->irq)) { + FTS_DEBUG("disable_irq_wake(irq:%d) fail", ts_data->irq); + } + + for (i = 0; i < 5; i++) { + fts_write_reg(FTS_REG_GESTURE_EN, DISABLE); + msleep(1); + fts_read_reg(FTS_REG_GESTURE_EN, &state); + if (state == DISABLE) + break; + } + + if (i >= 5) + FTS_ERROR("make IC exit gesture(resume) fail,state:%x", state); + else + FTS_INFO("resume from gesture successfully"); + + FTS_FUNC_EXIT(); + return 0; +} + +int fts_gesture_init(struct fts_ts_data *ts_data) +{ + struct input_dev *input_dev = ts_data->input_dev; + + FTS_FUNC_ENTER(); + input_set_capability(input_dev, EV_KEY, KEY_POWER); + input_set_capability(input_dev, EV_KEY, KEY_GESTURE_U); + input_set_capability(input_dev, EV_KEY, KEY_GESTURE_UP); + input_set_capability(input_dev, EV_KEY, KEY_GESTURE_DOWN); + input_set_capability(input_dev, EV_KEY, KEY_GESTURE_LEFT); + input_set_capability(input_dev, EV_KEY, KEY_GESTURE_RIGHT); + input_set_capability(input_dev, EV_KEY, KEY_GESTURE_O); + input_set_capability(input_dev, EV_KEY, KEY_GESTURE_E); + input_set_capability(input_dev, EV_KEY, KEY_GESTURE_M); + input_set_capability(input_dev, EV_KEY, KEY_GESTURE_L); + input_set_capability(input_dev, EV_KEY, KEY_GESTURE_W); + input_set_capability(input_dev, EV_KEY, KEY_GESTURE_S); + input_set_capability(input_dev, EV_KEY, KEY_GESTURE_V); + input_set_capability(input_dev, EV_KEY, KEY_GESTURE_Z); + input_set_capability(input_dev, EV_KEY, KEY_GESTURE_C); + + __set_bit(KEY_GESTURE_RIGHT, input_dev->keybit); + __set_bit(KEY_GESTURE_LEFT, input_dev->keybit); + __set_bit(KEY_GESTURE_UP, input_dev->keybit); + __set_bit(KEY_GESTURE_DOWN, input_dev->keybit); + __set_bit(KEY_GESTURE_U, input_dev->keybit); + __set_bit(KEY_GESTURE_O, input_dev->keybit); + __set_bit(KEY_GESTURE_E, input_dev->keybit); + __set_bit(KEY_GESTURE_M, input_dev->keybit); + __set_bit(KEY_GESTURE_W, input_dev->keybit); + __set_bit(KEY_GESTURE_L, input_dev->keybit); + __set_bit(KEY_GESTURE_S, input_dev->keybit); + __set_bit(KEY_GESTURE_V, input_dev->keybit); + __set_bit(KEY_GESTURE_C, input_dev->keybit); + __set_bit(KEY_GESTURE_Z, input_dev->keybit); + + fts_create_gesture_sysfs(ts_data->dev); + + memset(&fts_gesture_data, 0, sizeof(struct fts_gesture_st)); + ts_data->gesture_mode = FTS_GESTURE_EN; + + FTS_FUNC_EXIT(); + return 0; +} + +int fts_gesture_exit(struct fts_ts_data *ts_data) +{ + FTS_FUNC_ENTER(); + sysfs_remove_group(&ts_data->dev->kobj, &fts_gesture_group); + FTS_FUNC_EXIT(); + return 0; +} diff --git a/qcom/opensource/touch-drivers/focaltech_touch/focaltech_i2c.c b/qcom/opensource/touch-drivers/focaltech_touch/focaltech_i2c.c new file mode 100644 index 0000000000..8d439d3ac7 --- /dev/null +++ b/qcom/opensource/touch-drivers/focaltech_touch/focaltech_i2c.c @@ -0,0 +1,548 @@ +/* + * + * FocalTech TouchScreen driver. + * + * Copyright (c) 2012-2019, FocalTech Systems, Ltd., all rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/************************************************************************ +* +* File Name: focaltech_i2c.c +* +* Author: Focaltech Driver Team +* +* Created: 2016-08-04 +* +* Abstract: i2c communication with TP +* +* Version: v1.0 +* +* Revision History: +* +************************************************************************/ + +/***************************************************************************** +* Included header files +*****************************************************************************/ +#include "focaltech_core.h" +#include + +/***************************************************************************** +* Private constant and macro definitions using #define +*****************************************************************************/ +#define I2C_RETRY_NUMBER 3 +#define I2C_BUF_LENGTH 256 + +/***************************************************************************** +* Private enumerations, structures and unions using typedef +*****************************************************************************/ + +/***************************************************************************** +* Static variables +*****************************************************************************/ +static struct fts_ts_data *ts_data; + +/***************************************************************************** +* Global variable or extern global variabls/functions +*****************************************************************************/ + +/***************************************************************************** +* Static function prototypes +*****************************************************************************/ + +/***************************************************************************** +* functions body +*****************************************************************************/ +static int fts_i2c_read(u8 *cmd, u32 cmdlen, u8 *data, u32 datalen) +{ + int ret = 0; + int i = 0; + struct i2c_msg msg_list[2]; + struct i2c_msg *msg = NULL; + int msg_num = 0; + + /* must have data when read */ + if (!ts_data || !ts_data->client || !data || !datalen + || (datalen >= I2C_BUF_LENGTH) || (cmdlen >= I2C_BUF_LENGTH)) { + FTS_ERROR("fts_data/client/cmdlen(%d)/data/datalen(%d) is invalid", + cmdlen, datalen); + return -EINVAL; + } + + mutex_lock(&ts_data->bus_lock); + memset(&msg_list[0], 0, sizeof(struct i2c_msg)); + memset(&msg_list[1], 0, sizeof(struct i2c_msg)); + memcpy(ts_data->bus_tx_buf, cmd, cmdlen); + msg_list[0].addr = ts_data->client->addr; + msg_list[0].flags = 0; + msg_list[0].len = cmdlen; + msg_list[0].buf = ts_data->bus_tx_buf; + msg_list[1].addr = ts_data->client->addr; + msg_list[1].flags = I2C_M_RD; + msg_list[1].len = datalen; + msg_list[1].buf = ts_data->bus_rx_buf; + if (cmd && cmdlen) { + msg = &msg_list[0]; + msg_num = 2; + } else { + msg = &msg_list[1]; + msg_num = 1; + } + + for (i = 0; i < I2C_RETRY_NUMBER; i++) { + ret = i2c_transfer(ts_data->client->adapter, msg, msg_num); + if (ret < 0) { +#ifdef CONFIG_FTS_TRUSTED_TOUCH +#ifdef CONFIG_ARCH_QTI_VM + if (atomic_read(&ts_data->trusted_touch_enabled) && + ret == -ECONNRESET) { + pr_err("failed i2c read reacquiring session\n"); + pm_runtime_put_sync( + ts_data->client->adapter->dev.parent); + pm_runtime_get_sync( + ts_data->client->adapter->dev.parent); + } +#endif +#endif + FTS_ERROR("i2c_transfer(read) fail,ret:%d", ret); + } else { + memcpy(data, ts_data->bus_rx_buf, datalen); + break; + } + } + + if (ret < 0) { +#ifdef CONFIG_FTS_TRUSTED_TOUCH +#ifdef CONFIG_ARCH_QTI_VM + pr_err("initiating abort due to i2c xfer failure\n"); + fts_ts_trusted_touch_tvm_i2c_failure_report(ts_data); +#endif +#endif + } + + mutex_unlock(&ts_data->bus_lock); + return ret; +} + +static int fts_i2c_write(u8 *writebuf, u32 writelen) +{ + int ret = 0; + int i = 0; + struct i2c_msg msgs; + + if (!ts_data || !ts_data->client || !writebuf || !writelen + || (writelen >= I2C_BUF_LENGTH)) { + FTS_ERROR("fts_data/client/data/datalen(%d) is invalid", writelen); + return -EINVAL; + } + + mutex_lock(&ts_data->bus_lock); + memset(&msgs, 0, sizeof(struct i2c_msg)); + memcpy(ts_data->bus_tx_buf, writebuf, writelen); + msgs.addr = ts_data->client->addr; + msgs.flags = 0; + msgs.len = writelen; + msgs.buf = ts_data->bus_tx_buf; + for (i = 0; i < I2C_RETRY_NUMBER; i++) { + ret = i2c_transfer(ts_data->client->adapter, &msgs, 1); + if (ret < 0) { +#ifdef CONFIG_FTS_TRUSTED_TOUCH +#ifdef CONFIG_ARCH_QTI_VM + if (atomic_read(&ts_data->trusted_touch_enabled) && + ret == -ECONNRESET){ + pr_err("failed i2c write reacquiring session\n"); + pm_runtime_put_sync( + ts_data->client->adapter->dev.parent); + pm_runtime_get_sync( + ts_data->client->adapter->dev.parent); + } +#endif +#endif + FTS_ERROR("i2c_transfer(write) fail,ret:%d", ret); + } else { + break; + } + } + + if (ret < 0) { +#ifdef CONFIG_FTS_TRUSTED_TOUCH +#ifdef CONFIG_ARCH_QTI_VM + pr_err("initiating abort due to i2c xfer failure\n"); + fts_ts_trusted_touch_tvm_i2c_failure_report(ts_data); +#endif +#endif + } + mutex_unlock(&ts_data->bus_lock); + return ret; +} + +static int fts_i2c_init(struct fts_ts_data *ts_data) +{ + FTS_FUNC_ENTER(); + ts_data->bus_tx_buf = kzalloc(I2C_BUF_LENGTH, GFP_KERNEL); + if (ts_data->bus_tx_buf == NULL) { + FTS_ERROR("failed to allocate memory for bus_tx_buf"); + return -ENOMEM; + } + + ts_data->bus_rx_buf = kzalloc(I2C_BUF_LENGTH, GFP_KERNEL); + if (ts_data->bus_rx_buf == NULL) { + FTS_ERROR("failed to allocate memory for bus_rx_buf"); + return -ENOMEM; + } + FTS_FUNC_EXIT(); + return 0; +} + +static int fts_i2c_exit(struct fts_ts_data *ts_data) +{ + FTS_FUNC_ENTER(); + if (ts_data && ts_data->bus_tx_buf) { + kfree(ts_data->bus_tx_buf); + ts_data->bus_tx_buf = NULL; + } + + if (ts_data && ts_data->bus_rx_buf) { + kfree(ts_data->bus_rx_buf); + ts_data->bus_rx_buf = NULL; + } + FTS_FUNC_EXIT(); + return 0; +} + +/***************************************************************************** + * Private constant and macro definitions using #define + ****************************************************************************/ +#define SPI_RETRY_NUMBER 3 +#define CS_HIGH_DELAY 150 /* unit: us */ +#define SPI_BUF_LENGTH 256 + +#define DATA_CRC_EN 0x20 +#define WRITE_CMD 0x00 +#define READ_CMD (0x80 | DATA_CRC_EN) + +#define SPI_DUMMY_BYTE 3 +#define SPI_HEADER_LENGTH 6 /*CRC*/ + +/***************************************************************************** + * functions body + ****************************************************************************/ +/* spi interface */ +static int fts_spi_transfer(u8 *tx_buf, u8 *rx_buf, u32 len) +{ + int ret = 0; + struct spi_device *spi = fts_data->spi; + struct spi_message msg; + struct spi_transfer xfer = { + .tx_buf = tx_buf, + .rx_buf = rx_buf, + .len = len, + }; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + + ret = spi_sync(spi, &msg); + if (ret) { + FTS_ERROR("spi_sync fail,ret:%d", ret); + return ret; + } + + return ret; +} + +static void crckermit(u8 *data, u32 len, u16 *crc_out) +{ + u32 i = 0; + u32 j = 0; + u16 crc = 0xFFFF; + + for (i = 0; i < len; i++) { + crc ^= data[i]; + for (j = 0; j < 8; j++) { + if (crc & 0x01) + crc = (crc >> 1) ^ 0x8408; + else + crc = (crc >> 1); + } + } + + *crc_out = crc; +} + +static int rdata_check(u8 *rdata, u32 rlen) +{ + u16 crc_calc = 0; + u16 crc_read = 0; + + crckermit(rdata, rlen - 2, &crc_calc); + crc_read = (u16)(rdata[rlen - 1] << 8) + rdata[rlen - 2]; + if (crc_calc != crc_read) + return -EIO; + + return 0; +} + +static int fts_spi_write(u8 *writebuf, u32 writelen) +{ + int ret = 0; + int i = 0; + struct fts_ts_data *ts_data = fts_data; + u8 *txbuf = NULL; + u8 *rxbuf = NULL; + u32 txlen = 0; + u32 txlen_need = writelen + SPI_HEADER_LENGTH + ts_data->dummy_byte; + u32 datalen = writelen - 1; + + if (!writebuf || !writelen) { + FTS_ERROR("writebuf/len is invalid"); + return -EINVAL; + } + + mutex_lock(&ts_data->bus_lock); + if (txlen_need > SPI_BUF_LENGTH) { + txbuf = kzalloc(txlen_need, GFP_KERNEL); + if (txbuf == NULL) { + FTS_ERROR("txbuf malloc fail"); + ret = -ENOMEM; + goto err_write; + } + + rxbuf = kzalloc(txlen_need, GFP_KERNEL); + if (rxbuf == NULL) { + FTS_ERROR("rxbuf malloc fail"); + ret = -ENOMEM; + goto err_write; + } + } else { + txbuf = ts_data->bus_tx_buf; + rxbuf = ts_data->bus_rx_buf; + memset(txbuf, 0x0, SPI_BUF_LENGTH); + memset(rxbuf, 0x0, SPI_BUF_LENGTH); + } + + txbuf[txlen++] = writebuf[0]; + txbuf[txlen++] = WRITE_CMD; + txbuf[txlen++] = (datalen >> 8) & 0xFF; + txbuf[txlen++] = datalen & 0xFF; + if (datalen > 0) { + txlen = txlen + SPI_DUMMY_BYTE; + memcpy(&txbuf[txlen], &writebuf[1], datalen); + txlen = txlen + datalen; + } + + for (i = 0; i < SPI_RETRY_NUMBER; i++) { + ret = fts_spi_transfer(txbuf, rxbuf, txlen); + if ((ret == 0) && ((rxbuf[3] & 0xA0) == 0)) + break; + + FTS_DEBUG("data write(addr:%x),status:%x,retry:%d,ret:%d", + writebuf[0], rxbuf[3], i, ret); + ret = -EIO; + udelay(CS_HIGH_DELAY); + } + + if (ret < 0) { + FTS_ERROR("data write(addr:%x) fail,status:%x,ret:%d", + writebuf[0], rxbuf[3], ret); + } + +err_write: + if (txlen_need > SPI_BUF_LENGTH) { + kfree(txbuf); + kfree(rxbuf); + } + + udelay(CS_HIGH_DELAY); + mutex_unlock(&ts_data->bus_lock); + return ret; +} + +static int fts_spi_read(u8 *cmd, u32 cmdlen, u8 *data, u32 datalen) +{ + int ret = 0; + int i = 0; + u8 *txbuf = NULL; + u8 *rxbuf = NULL; + u32 txlen = 0; + u32 txlen_need = datalen + SPI_HEADER_LENGTH + ts_data->dummy_byte; + u8 ctrl = READ_CMD; + u32 dp = 0; + + if (!cmd || !cmdlen || !data || !datalen) { + FTS_ERROR("cmd/cmdlen/data/datalen is invalid"); + return -EINVAL; + } + + mutex_lock(&ts_data->bus_lock); + if (txlen_need > SPI_BUF_LENGTH) { + txbuf = kzalloc(txlen_need, GFP_KERNEL); + if (txbuf == NULL) { + FTS_ERROR("txbuf malloc fail"); + ret = -ENOMEM; + goto err_read; + } + + rxbuf = kzalloc(txlen_need, GFP_KERNEL); + if (rxbuf == NULL) { + FTS_ERROR("rxbuf malloc fail"); + ret = -ENOMEM; + goto err_read; + } + } else { + txbuf = ts_data->bus_tx_buf; + rxbuf = ts_data->bus_rx_buf; + memset(txbuf, 0x0, SPI_BUF_LENGTH); + memset(rxbuf, 0x0, SPI_BUF_LENGTH); + } + + txbuf[txlen++] = cmd[0]; + txbuf[txlen++] = ctrl; + txbuf[txlen++] = (datalen >> 8) & 0xFF; + txbuf[txlen++] = datalen & 0xFF; + dp = txlen + SPI_DUMMY_BYTE; + txlen = dp + datalen; + if (ctrl & DATA_CRC_EN) + txlen = txlen + 2; + + for (i = 0; i < SPI_RETRY_NUMBER; i++) { + ret = fts_spi_transfer(txbuf, rxbuf, txlen); + if ((ret == 0) && ((rxbuf[3] & 0xA0) == 0)) { + memcpy(data, &rxbuf[dp], datalen); + /* crc check */ + if (ctrl & DATA_CRC_EN) { + ret = rdata_check(&rxbuf[dp], txlen - dp); + if (ret < 0) { + FTS_DEBUG("data read(addr:%x) crc abnormal,retry:%d", + cmd[0], i); + udelay(CS_HIGH_DELAY); + continue; + } + } + break; + } + + FTS_DEBUG("data read(addr:%x) status:%x,retry:%d,ret:%d", + cmd[0], rxbuf[3], i, ret); + ret = -EIO; + udelay(CS_HIGH_DELAY); + } + + if (ret < 0) { + FTS_ERROR("data read(addr:%x) %s,status:%x,ret:%d", cmd[0], + (i >= SPI_RETRY_NUMBER) ? "crc abnormal" : "fail", + rxbuf[3], ret); + } + +err_read: + if (txlen_need > SPI_BUF_LENGTH) { + kfree(txbuf); + kfree(rxbuf); + } + + udelay(CS_HIGH_DELAY); + mutex_unlock(&ts_data->bus_lock); + return ret; +} + +static int fts_spi_init(struct fts_ts_data *ts_data) +{ + FTS_FUNC_ENTER(); + ts_data->bus_tx_buf = kzalloc(SPI_BUF_LENGTH, GFP_KERNEL); + if (ts_data->bus_tx_buf == NULL) { + FTS_ERROR("failed to allocate memory for bus_tx_buf"); + return -ENOMEM; + } + + ts_data->bus_rx_buf = kzalloc(SPI_BUF_LENGTH, GFP_KERNEL); + if (ts_data->bus_rx_buf == NULL) { + FTS_ERROR("failed to allocate memory for bus_rx_buf"); + return -ENOMEM; + } + + ts_data->dummy_byte = SPI_DUMMY_BYTE; + FTS_FUNC_EXIT(); + return 0; +} + +static int fts_spi_exit(struct fts_ts_data *ts_data) +{ + FTS_FUNC_ENTER(); + if (ts_data && ts_data->bus_tx_buf) { + kfree(ts_data->bus_tx_buf); + ts_data->bus_tx_buf = NULL; + } + + if (ts_data && ts_data->bus_rx_buf) { + kfree(ts_data->bus_rx_buf); + ts_data->bus_rx_buf = NULL; + } + FTS_FUNC_EXIT(); + return 0; +} + +int fts_read(u8 *cmd, u32 cmdlen, u8 *data, u32 datalen) +{ + int ret = 0; + + if (ts_data->bus_type == BUS_TYPE_I2C) + ret = fts_i2c_read(cmd, cmdlen, data, datalen); + else + ret = fts_spi_read(cmd, cmdlen, data, datalen); + + return ret; +} + +int fts_write(u8 *writebuf, u32 writelen) +{ + int ret = 0; + + if (ts_data->bus_type == BUS_TYPE_I2C) + ret = fts_i2c_write(writebuf, writelen); + else + ret = fts_spi_write(writebuf, writelen); + + return ret; +} + +int fts_read_reg(u8 addr, u8 *value) +{ + return fts_read(&addr, 1, value, 1); +} + +int fts_write_reg(u8 addr, u8 value) +{ + u8 buf[2] = { 0 }; + + buf[0] = addr; + buf[1] = value; + return fts_write(buf, sizeof(buf)); +} + +int fts_bus_init(struct fts_ts_data *_ts_data) +{ + ts_data = _ts_data; + + if (ts_data->bus_type == BUS_TYPE_I2C) + return fts_i2c_init(ts_data); + + return fts_spi_init(ts_data); +} + +int fts_bus_exit(struct fts_ts_data *ts_data) +{ + if (ts_data->bus_type == BUS_TYPE_I2C) + return fts_i2c_exit(ts_data); + + return fts_spi_exit(ts_data); +} \ No newline at end of file diff --git a/qcom/opensource/touch-drivers/focaltech_touch/focaltech_point_report_check.c b/qcom/opensource/touch-drivers/focaltech_touch/focaltech_point_report_check.c new file mode 100644 index 0000000000..0f626408a9 --- /dev/null +++ b/qcom/opensource/touch-drivers/focaltech_touch/focaltech_point_report_check.c @@ -0,0 +1,135 @@ +/* + * + * FocalTech TouchScreen driver. + * + * Copyright (c) 2012-2019, FocalTech Systems, Ltd., all rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/***************************************************************************** +* +* File Name: focaltech_point_report_check.c +* +* Author: Focaltech Driver Team +* +* Created: 2016-11-16 +* +* Abstract: point report check function +* +* Version: v1.0 +* +* Revision History: +* +*****************************************************************************/ + +/***************************************************************************** +* Included header files +*****************************************************************************/ +#include "focaltech_core.h" + +#if FTS_POINT_REPORT_CHECK_EN +/***************************************************************************** +* Private constant and macro definitions using #define +*****************************************************************************/ +#define POINT_REPORT_CHECK_WAIT_TIME 200 /* unit:ms */ + +/***************************************************************************** +* functions body +*****************************************************************************/ +/***************************************************************************** +* Name: fts_prc_func +* Brief: fts point report check work func, report whole up of points +* Input: +* Output: +* Return: +*****************************************************************************/ +static void fts_prc_func(struct work_struct *work) +{ + struct fts_ts_data *ts_data = container_of(work, + struct fts_ts_data, prc_work.work); + struct input_dev *input_dev = ts_data->input_dev; +#if FTS_MT_PROTOCOL_B_EN + u32 finger_count = 0; + u32 max_touches = fts_data->pdata->max_touch_number; +#endif + + FTS_FUNC_ENTER(); + mutex_lock(&ts_data->report_mutex); + +#if FTS_MT_PROTOCOL_B_EN + for (finger_count = 0; finger_count < max_touches; finger_count++) { + input_mt_slot(input_dev, finger_count); + input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, false); + } +#else + input_mt_sync(input_dev); +#endif + input_report_key(input_dev, BTN_TOUCH, 0); + input_sync(input_dev); + + mutex_unlock(&ts_data->report_mutex); + + FTS_FUNC_EXIT(); +} + +/***************************************************************************** +* Name: fts_prc_queue_work +* Brief: fts point report check queue work, call it when interrupt comes +* Input: +* Output: +* Return: +*****************************************************************************/ +void fts_prc_queue_work(struct fts_ts_data *ts_data) +{ + cancel_delayed_work_sync(&ts_data->prc_work); + queue_delayed_work(ts_data->ts_workqueue, &ts_data->prc_work, + msecs_to_jiffies(POINT_REPORT_CHECK_WAIT_TIME)); +} + +/***************************************************************************** +* Name: fts_point_report_check_init +* Brief: +* Input: +* Output: +* Return: < 0: Fail to create esd check queue +*****************************************************************************/ +int fts_point_report_check_init(struct fts_ts_data *ts_data) +{ + FTS_FUNC_ENTER(); + + if (ts_data->ts_workqueue) { + INIT_DELAYED_WORK(&ts_data->prc_work, fts_prc_func); + } else { + FTS_ERROR("fts workqueue is NULL, can't run point report check function"); + return -EINVAL; + } + + FTS_FUNC_EXIT(); + return 0; +} + +/***************************************************************************** +* Name: fts_point_report_check_exit +* Brief: +* Input: +* Output: +* Return: +*****************************************************************************/ +int fts_point_report_check_exit(struct fts_ts_data *ts_data) +{ + FTS_FUNC_ENTER(); + + FTS_FUNC_EXIT(); + return 0; +} +#endif /* FTS_POINT_REPORT_CHECK_EN */ + diff --git a/qcom/opensource/touch-drivers/glink_interface_ts/glink_interface.c b/qcom/opensource/touch-drivers/glink_interface_ts/glink_interface.c new file mode 100644 index 0000000000..400aa327e6 --- /dev/null +++ b/qcom/opensource/touch-drivers/glink_interface_ts/glink_interface.c @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include + +static struct glink_touch_dev *touch_pdev = NULL; +struct touch_channel_ops touch_ops; + + +void glink_touch_channel_init(void (*fn1)(bool), void (*fn2)(void *data, int len)) +{ + touch_ops.glink_channel_state = fn1; + touch_ops.rx_msg = fn2; +} +EXPORT_SYMBOL(glink_touch_channel_init); + +int glink_touch_tx_msg(void *msg, size_t len) +{ + int ret = 0; + + if (touch_pdev == NULL || !touch_pdev->chnl_state) { + pr_err("pmsg_device is null, channel is closed\n"); + return -ENETRESET; + } + + touch_pdev->message = msg; + touch_pdev->message_length = len; + if (touch_pdev->message) { + ret = rpmsg_send(touch_pdev->channel, + touch_pdev->message, touch_pdev->message_length); + if (ret) + pr_err("rpmsg_send failed: %d\n", ret); + + } + + return ret; +} +EXPORT_SYMBOL(glink_touch_tx_msg); + +static int glink_touch_probe(struct rpmsg_device *touch_rpdev) +{ + int ret = 0; + void *msg = NULL; + + if (!touch_rpdev) { + pr_err("rpmsg_device is null\n"); + return -ENODEV; + } + + pr_info("%s Start of glink_touch_probe\n", __func__); + touch_pdev = devm_kzalloc(&touch_rpdev->dev, sizeof(*touch_pdev), GFP_KERNEL); + if (!touch_pdev) + return -ENOMEM; + + touch_pdev->channel = touch_rpdev->ept; + touch_pdev->dev = &touch_rpdev->dev; + if (touch_pdev->channel == NULL) + return -ENOMEM; + + touch_pdev->chnl_state = true; + dev_set_drvdata(&touch_rpdev->dev, touch_pdev); + + /* send a callback to slate-MSM touch driver*/ + touch_ops.glink_channel_state(true); + if (touch_pdev->message == NULL) + ret = glink_touch_tx_msg(msg, 0); + + pr_info("%s End of glink_touch_probe\n", __func__); + return 0; +} + +static void glink_touch_remove(struct rpmsg_device *touch_rpdev) +{ + touch_pdev->chnl_state = false; + touch_pdev->message = NULL; + dev_dbg(&touch_rpdev->dev, "rpmsg client driver is removed\n"); + touch_ops.glink_channel_state(false); + dev_set_drvdata(&touch_rpdev->dev, NULL); + +} + +static int glink_touch_cb(struct rpmsg_device *touch_rpdev, + void *data, int len, void *priv, u32 src) +{ + struct glink_touch_dev *touch_dev = + dev_get_drvdata(&touch_rpdev->dev); + + if (!touch_dev) + return -ENODEV; + touch_ops.rx_msg(data, len); + + return 0; +} + +static const struct rpmsg_device_id glink_touch_driver_id_table[] = { + { "touch-ctrl" }, + {}, +}; +MODULE_DEVICE_TABLE(rpmsg, glink_touch_driver_id_table); + +static const struct of_device_id glink_touch_driver_of_match[] = { + { .compatible = "qcom,slatetouch-rpmsg" }, + {}, +}; + +static struct rpmsg_driver glink_touch_client = { + .id_table = glink_touch_driver_id_table, + .probe = glink_touch_probe, + .callback = glink_touch_cb, + .remove = glink_touch_remove, + .drv = { + .name = "glink_interface", + .of_match_table = glink_touch_driver_of_match, + }, +}; +module_rpmsg_driver(glink_touch_client); + +MODULE_DESCRIPTION("Interface Driver for MSM TOUCH and RPMSG"); +MODULE_LICENSE("GPL v2"); diff --git a/qcom/opensource/touch-drivers/glink_interface_ts/glink_interface.h b/qcom/opensource/touch-drivers/glink_interface_ts/glink_interface.h new file mode 100644 index 0000000000..89031fae82 --- /dev/null +++ b/qcom/opensource/touch-drivers/glink_interface_ts/glink_interface.h @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef GLINKINTERFACE_H +#define GLINKINTERFACE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define TOUCH_GLINK_INTENT_SIZE 0x0c +#define TOUCH_MSG_SIZE 0x08 +#define TIMEOUT_MS 2000 + +struct glink_touch_priv { + void *handle; + struct mutex glink_mutex; + struct mutex touch_state_mutex; + void *lhndl; + char rx_buf[TOUCH_GLINK_INTENT_SIZE]; + bool glink_touch_cmplt; + wait_queue_head_t link_state_wait; + bool msm_touch_rpmsg; +}; + +static void *glink_touch_drv; + +enum touch_slate_cmds { + TOUCH_ENTER_PREPARE = 0x1100, + TOUCH_ENTER, + TOUCH_EXIT_PREPARE, + TOUCH_EXIT +}; + +struct glink_touch_dev { + struct rpmsg_endpoint *channel; + struct device *dev; + bool chnl_state; + void *message; + size_t message_length; +}; + +struct touch_channel_ops { + void (*glink_channel_state)(bool state); + void (*rx_msg)(void *data, int len); +}; + +void glink_touch_channel_init(void (*fn1)(bool), void (*fn2)(void *, int)); + + + +#if IS_ENABLED(CONFIG_MSM_SLATERSB_RPMSG) +int glink_touch_tx_msg(void *msg, size_t len); +#else +static inline int glink_touch_tx_msg(void *msg, size_t len) +{ + return -EIO; +} +#endif + +#endif diff --git a/qcom/opensource/touch-drivers/goodix_berlin_driver/goodix_brl_fwupdate.c b/qcom/opensource/touch-drivers/goodix_berlin_driver/goodix_brl_fwupdate.c new file mode 100644 index 0000000000..f319ab752c --- /dev/null +++ b/qcom/opensource/touch-drivers/goodix_berlin_driver/goodix_brl_fwupdate.c @@ -0,0 +1,1360 @@ +/* + * Goodix Touchscreen Driver + * Copyright (C) 2020 - 2021 Goodix, Inc. + * + * 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 a reference + * to you, when you are integrating the GOODiX's CTP IC into your system, + * 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. + * + */ +#include "goodix_ts_core.h" + +#define BUS_TYPE_SPI 1 +#define BUS_TYPE_I2C 0 + +#define GOODIX_BUS_RETRY_TIMES 3 + +#define FW_HEADER_SIZE_BRA 256 +#define FW_HEADER_SIZE 512 +#define FW_SUBSYS_INFO_SIZE 10 +#define FW_SUBSYS_INFO_OFFSET_BRA 36 +#define FW_SUBSYS_INFO_OFFSET 42 +#define FW_SUBSYS_MAX_NUM 47 + +#define ISP_MAX_BUFFERSIZE 4096 + +#define FW_PID_LEN 8 +#define FW_VID_LEN 4 +#define FLASH_CMD_LEN 11 + +#define FW_FILE_CHECKSUM_OFFSET 8 +#define CONFIG_DATA_TYPE 4 + +#define ISP_RAM_ADDR_BRA 0x18400 +#define ISP_RAM_ADDR_BRB 0x57000 +#define ISP_RAM_ADDR_BRD 0x23800 +#define HW_REG_CPU_RUN_FROM 0x10000 +#define FLASH_CMD_REG_BRA 0x10400 +#define FLASH_CMD_REG_BRB 0x13400 +#define FLASH_CMD_REG_BRD 0x12400 +#define HW_REG_ISP_BUFFER_BRA 0x10410 +#define HW_REG_ISP_BUFFER_BRB 0x13410 +#define HW_REG_ISP_BUFFER_BRD 0x12410 +#define CONFIG_DATA_ADDR_BRA 0x3E000 +#define CONFIG_DATA_ADDR_BRB 0x40000 +#define CONFIG_DATA_ADDR_BRD 0x3E000 +#define GOODIX_CFG_ID_ADDR_BRA 0x1006E +#define GOODIX_CFG_ID_ADDR_BRB 0x10076 +#define GOODIX_CFG_ID_ADDR_BRD 0x10076 + +#define HOLD_CPU_REG_W 0x0002 +#define HOLD_CPU_REG_R 0x2000 +#define MISCTL_REG_BRA 0xD807 +#define MISCTL_REG_BRB 0xD80B +#define MISCTL_REG_BRD 0xD804 +#define ENABLE_MISCTL_BRA 0x08 +#define ENABLE_MISCTL_BRB 0x40 +#define ENABLE_MISCTL_BRD 0x20700000 +#define ESD_KEY_REG 0xCC58 +#define WATCH_DOG_REG_BRA 0xCC54 +#define WATCH_DOG_REG_BRB 0xD054 +#define WATCH_DOG_REG_BRD 0xD040 + +#define FLASH_CMD_TYPE_READ 0xAA +#define FLASH_CMD_TYPE_WRITE 0xBB +#define FLASH_CMD_ACK_CHK_PASS 0xEE +#define FLASH_CMD_ACK_CHK_ERROR 0x33 +#define FLASH_CMD_ACK_IDLE 0x11 +#define FLASH_CMD_W_STATUS_CHK_PASS 0x22 +#define FLASH_CMD_W_STATUS_CHK_FAIL 0x33 +#define FLASH_CMD_W_STATUS_ADDR_ERR 0x44 +#define FLASH_CMD_W_STATUS_WRITE_ERR 0x55 +#define FLASH_CMD_W_STATUS_WRITE_OK 0xEE + +#define CHIP_TYPE_BRA 0x96 +#define CHIP_TYPE_BRB 0x97 +#define CHIP_TYPE_BRD 0x98 + + +struct update_info_t { + int header_size; + int subsys_info_offset; + u32 isp_ram_reg; + u32 flash_cmd_reg; + u32 isp_buffer_reg; + u32 config_data_reg; + u32 misctl_reg; + u32 watch_dog_reg; + u32 config_id_reg; + u32 enable_misctl_val; +}; + +/* berlinA update into */ +struct update_info_t update_bra = { + FW_HEADER_SIZE_BRA, + FW_SUBSYS_INFO_OFFSET_BRA, + ISP_RAM_ADDR_BRA, + FLASH_CMD_REG_BRA, + HW_REG_ISP_BUFFER_BRA, + CONFIG_DATA_ADDR_BRA, + MISCTL_REG_BRA, + WATCH_DOG_REG_BRA, + GOODIX_CFG_ID_ADDR_BRA, + ENABLE_MISCTL_BRA, +}; + +/* berlinB update info */ +struct update_info_t update_brb = { + FW_HEADER_SIZE, + FW_SUBSYS_INFO_OFFSET, + ISP_RAM_ADDR_BRB, + FLASH_CMD_REG_BRB, + HW_REG_ISP_BUFFER_BRB, + CONFIG_DATA_ADDR_BRB, + MISCTL_REG_BRB, + WATCH_DOG_REG_BRB, + GOODIX_CFG_ID_ADDR_BRB, + ENABLE_MISCTL_BRB, +}; + +/* berlinD update info */ +struct update_info_t update_brd = { + FW_HEADER_SIZE, + FW_SUBSYS_INFO_OFFSET, + ISP_RAM_ADDR_BRD, + FLASH_CMD_REG_BRD, + HW_REG_ISP_BUFFER_BRD, + CONFIG_DATA_ADDR_BRD, + MISCTL_REG_BRD, + WATCH_DOG_REG_BRD, + GOODIX_CFG_ID_ADDR_BRD, + ENABLE_MISCTL_BRD, +}; + +/** + * fw_subsys_info - subsytem firmware information + * @type: sybsystem type + * @size: firmware size + * @flash_addr: flash address + * @data: firmware data + */ +struct fw_subsys_info { + u8 type; + u32 size; + u32 flash_addr; + const u8 *data; +}; + +/** + * firmware_summary + * @size: fw total length + * @checksum: checksum of fw + * @hw_pid: mask pid string + * @hw_pid: mask vid code + * @fw_pid: fw pid string + * @fw_vid: fw vid code + * @subsys_num: number of fw subsystem + * @chip_type: chip type + * @protocol_ver: firmware packing + * protocol version + * @bus_type: 0 represent I2C, 1 for SPI + * @subsys: sybsystem info + */ +#pragma pack(1) +struct firmware_summary { + u32 size; + u32 checksum; + u8 hw_pid[6]; + u8 hw_vid[3]; + u8 fw_pid[FW_PID_LEN]; + u8 fw_vid[FW_VID_LEN]; + u8 subsys_num; + u8 chip_type; + u8 protocol_ver; + u8 bus_type; + u8 flash_protect; + // u8 reserved[8]; + struct fw_subsys_info subsys[FW_SUBSYS_MAX_NUM]; +}; +#pragma pack() + +/** + * firmware_data - firmware data structure + * @fw_summary: firmware information + * @firmware: firmware data structure + */ +struct firmware_data { + struct firmware_summary fw_summary; + const struct firmware *firmware; + struct firmware *fw_sysfs; +}; + +struct config_data { + u8 *data; + int size; +}; + +#pragma pack(1) +struct goodix_flash_cmd { + union { + struct { + u8 status; + u8 ack; + u8 len; + u8 cmd; + u8 fw_type; + u16 fw_len; + u32 fw_addr; + //u16 checksum; + }; + u8 buf[16]; + }; +}; +#pragma pack() + +enum update_status { + UPSTA_NOTWORK = 0, + UPSTA_PREPARING, + UPSTA_UPDATING, + UPSTA_SUCCESS, + UPSTA_FAILED +}; + +enum compare_status { + COMPARE_EQUAL = 0, + COMPARE_NOCODE, + COMPARE_PIDMISMATCH, + COMPARE_FW_NOTEQUAL, + COMPARE_CFG_NOTEQUAL, +}; + +/** + * fw_update_ctrl - structure used to control the + * firmware update process + * @initialized: struct init state + * @mode: indicate weather reflash config or not, fw data source, + * and run on block mode or not. + * @status: update status + * @progress: indicate the progress of update + * @fw_data: firmware data + * @fw_name: firmware name + * @attr_fwimage: sysfs bin attrs, for storing fw image + * @fw_data_src: firmware data source form sysfs, request or head file + * @kobj: pointer to the sysfs kobject + */ +struct fw_update_ctrl { + struct mutex mutex; + int initialized; + char fw_name[GOODIX_MAX_STR_LABLE_LEN]; + int mode; + enum update_status status; + int spend_time; + + struct firmware_data fw_data; + struct goodix_ic_config *ic_config; + struct goodix_ts_core *core_data; + struct update_info_t *update_info; + + struct bin_attribute attr_fwimage; + struct kobject *kobj; +}; +static struct fw_update_ctrl goodix_fw_update_ctrl; + +static int goodix_fw_update_reset(int delay) +{ + struct goodix_ts_hw_ops *hw_ops; + + hw_ops = goodix_fw_update_ctrl.core_data->hw_ops; + return hw_ops->reset(goodix_fw_update_ctrl.core_data, delay); +} + +static int get_fw_version_info(struct goodix_fw_version *fw_version) +{ + struct goodix_ts_hw_ops *hw_ops = + goodix_fw_update_ctrl.core_data->hw_ops; + + return hw_ops->read_version(goodix_fw_update_ctrl.core_data, + fw_version); +} + +static int goodix_reg_write(unsigned int addr, + unsigned char *data, unsigned int len) +{ + struct goodix_ts_hw_ops *hw_ops = + goodix_fw_update_ctrl.core_data->hw_ops; + + return hw_ops->write(goodix_fw_update_ctrl.core_data, + addr, data, len); +} + +static int goodix_reg_read(unsigned int addr, + unsigned char *data, unsigned int len) +{ + struct goodix_ts_hw_ops *hw_ops = + goodix_fw_update_ctrl.core_data->hw_ops; + + return hw_ops->read(goodix_fw_update_ctrl.core_data, + addr, data, len); +} + +/** + * goodix_parse_firmware - parse firmware header information + * and subsystem information from firmware data buffer + * + * @fw_data: firmware struct, contains firmware header info + * and firmware data. + * return: 0 - OK, < 0 - error + */ +/* sizeof(length) + sizeof(checksum) */ + +static int goodix_parse_firmware(struct firmware_data *fw_data) +{ + const struct firmware *firmware; + struct firmware_summary *fw_summary; + unsigned int i, fw_offset, info_offset; + u32 checksum; + int ic_type = + goodix_fw_update_ctrl.core_data->bus->ic_type; + int subsys_info_offset = + goodix_fw_update_ctrl.update_info->subsys_info_offset; + int header_size = + goodix_fw_update_ctrl.update_info->header_size; + int r = 0; + + fw_summary = &fw_data->fw_summary; + + /* copy firmware head info */ + if (goodix_fw_update_ctrl.mode & UPDATE_MODE_SRC_SYSFS) + firmware = fw_data->fw_sysfs; + else + firmware = fw_data->firmware; + + if (firmware->size < subsys_info_offset) { + ts_err("Invalid firmware size:%zu", firmware->size); + r = -EINVAL; + goto err_size; + } + memcpy(fw_summary, firmware->data, sizeof(*fw_summary)); + + /* check firmware size */ + fw_summary->size = le32_to_cpu(fw_summary->size); + if (firmware->size != fw_summary->size + FW_FILE_CHECKSUM_OFFSET) { + ts_err("Bad firmware, size not match, %zu != %d", + firmware->size, + fw_summary->size + FW_FILE_CHECKSUM_OFFSET); + r = -EINVAL; + goto err_size; + } + + for (i = FW_FILE_CHECKSUM_OFFSET, checksum = 0; + i < firmware->size; i += 2) + checksum += firmware->data[i] + (firmware->data[i+1] << 8); + + /* byte order change, and check */ + fw_summary->checksum = le32_to_cpu(fw_summary->checksum); + if (checksum != fw_summary->checksum) { + ts_err("Bad firmware, cheksum error"); + r = -EINVAL; + goto err_size; + } + + if (fw_summary->subsys_num > FW_SUBSYS_MAX_NUM) { + ts_err("Bad firmware, invalid subsys num: %d", + fw_summary->subsys_num); + r = -EINVAL; + goto err_size; + } + + /* parse subsystem info */ + fw_offset = header_size; + for (i = 0; i < fw_summary->subsys_num; i++) { + info_offset = subsys_info_offset + + i * FW_SUBSYS_INFO_SIZE; + + fw_summary->subsys[i].type = firmware->data[info_offset]; + fw_summary->subsys[i].size = + le32_to_cpup((__le32 *)&firmware->data[info_offset + 1]); + + fw_summary->subsys[i].flash_addr = + le32_to_cpup((__le32 *)&firmware->data[info_offset + 5]); + if (fw_offset > firmware->size) { + ts_err("Sybsys offset exceed Firmware size"); + goto err_size; + } + + fw_summary->subsys[i].data = firmware->data + fw_offset; + fw_offset += fw_summary->subsys[i].size; + } + + ts_info("Firmware package protocol: V%u", fw_summary->protocol_ver); + ts_info("Firmware PID:GT%s", fw_summary->fw_pid); + ts_info("Firmware VID:%*ph", 4, fw_summary->fw_vid); + ts_info("Firmware chip type:0x%02X", fw_summary->chip_type); + ts_info("Firmware bus type:%s", + (fw_summary->bus_type & BUS_TYPE_SPI) ? "SPI" : "I2C"); + ts_info("Firmware size:%u", fw_summary->size); + ts_info("Firmware subsystem num:%u", fw_summary->subsys_num); + + for (i = 0; i < fw_summary->subsys_num; i++) { + ts_debug("------------------------------------------"); + ts_debug("Index:%d", i); + ts_debug("Subsystem type:%02X", fw_summary->subsys[i].type); + ts_debug("Subsystem size:%u", fw_summary->subsys[i].size); + ts_debug("Subsystem flash_addr:%08X", + fw_summary->subsys[i].flash_addr); + ts_debug("Subsystem Ptr:%p", fw_summary->subsys[i].data); + } + + if (fw_summary->chip_type == CHIP_TYPE_BRA && + ic_type != IC_TYPE_BERLIN_A) { + ts_err("ic type mismatch!"); + r = -EINVAL; + } else if (fw_summary->chip_type == CHIP_TYPE_BRB && + ic_type != IC_TYPE_BERLIN_B) { + ts_err("ic type mismatch!"); + r = -EINVAL; + } else if (fw_summary->chip_type == CHIP_TYPE_BRD && + ic_type != IC_TYPE_BERLIN_D) { + ts_err("ic type mismatch!"); + r = -EINVAL; + } + +err_size: + return r; +} + +/** + * goodix_fw_version_compare - compare the active version with + * firmware file version. + * @fwu_ctrl: firmware information to be compared + * return: 0 equal, < 0 unequal + */ +#define GOODIX_NOCODE "NOCODE" +static int goodix_fw_version_compare(struct fw_update_ctrl *fwu_ctrl) +{ + int ret = 0; + struct goodix_fw_version fw_version; + struct firmware_summary *fw_summary = &fwu_ctrl->fw_data.fw_summary; + u32 config_id_reg = goodix_fw_update_ctrl.update_info->config_id_reg; + u32 file_cfg_id; + u32 ic_cfg_id; + + /* compare fw_version */ + ret = get_fw_version_info(&fw_version); + if (ret) + return -EINVAL; + + if (!memcmp(fw_version.rom_pid, GOODIX_NOCODE, 6) || + !memcmp(fw_version.patch_pid, GOODIX_NOCODE, 6)) { + ts_info("there is no code in the chip"); + return COMPARE_NOCODE; + } + + if (memcmp(fw_version.patch_pid, fw_summary->fw_pid, FW_PID_LEN)) { + ts_err("Product ID mismatch:%s != %s", + fw_version.patch_pid, fw_summary->fw_pid); + return COMPARE_PIDMISMATCH; + } + + ret = memcmp(fw_version.patch_vid, fw_summary->fw_vid, FW_VID_LEN); + if (ret) { + ts_info("active firmware version:%*ph", FW_VID_LEN, + fw_version.patch_vid); + ts_info("firmware file version: %*ph", FW_VID_LEN, + fw_summary->fw_vid); + return COMPARE_FW_NOTEQUAL; + } + ts_info("fw_version equal"); + + /* compare config id */ + if (fwu_ctrl->ic_config && fwu_ctrl->ic_config->len > 0) { + file_cfg_id = + goodix_get_file_config_id(fwu_ctrl->ic_config->data); + goodix_reg_read(config_id_reg, + (u8 *)&ic_cfg_id, sizeof(ic_cfg_id)); + if (ic_cfg_id != file_cfg_id) { + ts_info("ic_cfg_id:0x%x != file_cfg_id:0x%x", + ic_cfg_id, file_cfg_id); + return COMPARE_CFG_NOTEQUAL; + } + ts_info("config_id equal"); + } + + return COMPARE_EQUAL; +} + +/** + * goodix_reg_write_confirm - write register and confirm the value + * in the register. + * @dev: pointer to touch device + * @addr: register address + * @data: pointer to data buffer + * @len: data length + * return: 0 write success and confirm ok + * < 0 failed + */ +static int goodix_reg_write_confirm(unsigned int addr, + unsigned char *data, unsigned int len) +{ + u8 *cfm = NULL; + u8 cfm_buf[32]; + int r, i; + + if (len > sizeof(cfm_buf)) { + cfm = kzalloc(len, GFP_KERNEL); + if (!cfm) + return -ENOMEM; + } else { + cfm = &cfm_buf[0]; + } + + for (i = 0; i < GOODIX_BUS_RETRY_TIMES; i++) { + r = goodix_reg_write(addr, data, len); + if (r < 0) + goto exit; + + r = goodix_reg_read(addr, cfm, len); + if (r < 0) + goto exit; + + if (memcmp(data, cfm, len)) { + r = -EINVAL; + continue; + } else { + r = 0; + break; + } + } + +exit: + if (cfm != &cfm_buf[0]) + kfree(cfm); + return r; +} + + +/** + * goodix_load_isp - load ISP program to device ram + * @dev: pointer to touch device + * @fw_data: firmware data + * return 0 ok, <0 error + */ +static int goodix_load_isp(struct firmware_data *fw_data) +{ + struct goodix_fw_version isp_fw_version; + struct fw_subsys_info *fw_isp; + u32 isp_ram_reg = goodix_fw_update_ctrl.update_info->isp_ram_reg; + u8 reg_val[8] = {0x00}; + int r; + + memset(&isp_fw_version, 0, sizeof(isp_fw_version)); + fw_isp = &fw_data->fw_summary.subsys[0]; + + ts_info("Loading ISP start"); + r = goodix_reg_write_confirm(isp_ram_reg, + (u8 *)fw_isp->data, fw_isp->size); + if (r < 0) { + ts_err("Loading ISP error"); + return r; + } + + ts_info("Success send ISP data"); + + /* SET BOOT OPTION TO 0X55 */ + memset(reg_val, 0x55, 8); + r = goodix_reg_write_confirm(HW_REG_CPU_RUN_FROM, reg_val, 8); + if (r < 0) { + ts_err("Failed set REG_CPU_RUN_FROM flag"); + return r; + } + ts_info("Success write [8]0x55 to 0x%x", HW_REG_CPU_RUN_FROM); + + if (goodix_fw_update_reset(100)) + ts_err("reset abnormal"); + /*check isp state */ + if (get_fw_version_info(&isp_fw_version)) { + ts_err("failed read isp version"); + return -2; + } + if (memcmp(&isp_fw_version.patch_pid[3], "ISP", 3)) { + ts_err("patch id error %c%c%c != %s", + isp_fw_version.patch_pid[3], isp_fw_version.patch_pid[4], + isp_fw_version.patch_pid[5], "ISP"); + return -3; + } + ts_info("ISP running successfully"); + return 0; +} + +/** + * goodix_update_prepare - update prepare, loading ISP program + * and make sure the ISP is running. + * @fwu_ctrl: pointer to fimrware control structure + * return: 0 ok, <0 error + */ +static int goodix_update_prepare(struct fw_update_ctrl *fwu_ctrl) +{ + u32 misctl_reg = fwu_ctrl->update_info->misctl_reg; + u32 watch_dog_reg = fwu_ctrl->update_info->watch_dog_reg; + u32 enable_misctl_val = fwu_ctrl->update_info->enable_misctl_val; + u8 reg_val[4] = {0}; + u8 temp_buf[64] = {0}; + int retry = 20; + int r; + + /*reset IC*/ + ts_info("firmware update, reset"); + if (goodix_fw_update_reset(5)) + ts_err("reset abnormal"); + + retry = 100; + /* Hold cpu*/ + do { + reg_val[0] = 0x01; + reg_val[1] = 0x00; + r = goodix_reg_write(HOLD_CPU_REG_W, reg_val, 2); + r |= goodix_reg_read(HOLD_CPU_REG_R, &temp_buf[0], 4); + r |= goodix_reg_read(HOLD_CPU_REG_R, &temp_buf[4], 4); + r |= goodix_reg_read(HOLD_CPU_REG_R, &temp_buf[8], 4); + if (!r && !memcmp(&temp_buf[0], &temp_buf[4], 4) && + !memcmp(&temp_buf[4], &temp_buf[8], 4) && + !memcmp(&temp_buf[0], &temp_buf[8], 4)) { + break; + } + usleep_range(1000, 1100); + ts_info("retry hold cpu %d", retry); + ts_debug("data:%*ph", 12, temp_buf); + } while (--retry); + if (!retry) { + ts_err("Failed to hold CPU, return =%d", r); + return -1; + } + ts_info("Success hold CPU"); + + /* enable misctl clock */ + if (fwu_ctrl->core_data->bus->ic_type == IC_TYPE_BERLIN_D) + goodix_reg_write(misctl_reg, (u8 *)&enable_misctl_val, 4); + else + goodix_reg_write(misctl_reg, (u8 *)&enable_misctl_val, 1); + ts_info("enbale misctl clock"); + + if (fwu_ctrl->core_data->bus->ic_type == IC_TYPE_BERLIN_A) { + /* open ESD_KEY */ + retry = 20; + do { + reg_val[0] = 0x95; + r = goodix_reg_write(ESD_KEY_REG, reg_val, 1); + r |= goodix_reg_read(ESD_KEY_REG, temp_buf, 1); + if (!r && temp_buf[0] == 0x01) + break; + usleep_range(1000, 1100); + ts_info("retry %d enable esd key, 0x%x", + retry, temp_buf[0]); + } while (--retry); + if (!retry) { + ts_err("Failed to enable esd key, return =%d", r); + return -2; + } + ts_info("success enable esd key"); + } + + /* disable watch dog */ + reg_val[0] = 0x00; + r = goodix_reg_write(watch_dog_reg, reg_val, 1); + ts_info("disable watch dog"); + + /* load ISP code and run form isp */ + r = goodix_load_isp(&fwu_ctrl->fw_data); + if (r < 0) + ts_err("Failed load and run isp"); + + return r; +} + +/* goodix_send_flash_cmd: send command to read or write flash data + * @flash_cmd: command need to send. + */ +static int goodix_send_flash_cmd(struct goodix_flash_cmd *flash_cmd) +{ + int i, ret, retry; + struct goodix_flash_cmd tmp_cmd; + u32 flash_cmd_reg = goodix_fw_update_ctrl.update_info->flash_cmd_reg; + + ts_info("try send flash cmd:%*ph", (int)sizeof(flash_cmd->buf), + flash_cmd->buf); + memset(tmp_cmd.buf, 0, sizeof(tmp_cmd)); + ret = goodix_reg_write(flash_cmd_reg, + flash_cmd->buf, sizeof(flash_cmd->buf)); + if (ret) { + ts_err("failed send flash cmd %d", ret); + return ret; + } + + retry = 5; + for (i = 0; i < retry; i++) { + ret = goodix_reg_read(flash_cmd_reg, + tmp_cmd.buf, sizeof(tmp_cmd.buf)); + if (!ret && tmp_cmd.ack == FLASH_CMD_ACK_CHK_PASS) + break; + usleep_range(5000, 5100); + ts_info("flash cmd ack error retry %d, ack 0x%x, ret %d", + i, tmp_cmd.ack, ret); + } + if (tmp_cmd.ack != FLASH_CMD_ACK_CHK_PASS) { + ts_err("flash cmd ack error, ack 0x%x, ret %d", + tmp_cmd.ack, ret); + ts_err("data:%*ph", (int)sizeof(tmp_cmd.buf), tmp_cmd.buf); + return -EINVAL; + } + ts_info("flash cmd ack check pass"); + + msleep(50); + retry = 20; + for (i = 0; i < retry; i++) { + ret = goodix_reg_read(flash_cmd_reg, + tmp_cmd.buf, sizeof(tmp_cmd.buf)); + if (!ret && tmp_cmd.ack == FLASH_CMD_ACK_CHK_PASS && + tmp_cmd.status == FLASH_CMD_W_STATUS_WRITE_OK) { + ts_info("flash status check pass"); + return 0; + } + + ts_info("flash cmd status not ready, retry %d, ack 0x%x, status 0x%x, ret %d", + i, tmp_cmd.ack, tmp_cmd.status, ret); + usleep_range(10000, 11000); + } + + ts_err("flash cmd status error %d, ack 0x%x, status 0x%x, ret %d", + i, tmp_cmd.ack, tmp_cmd.status, ret); + if (ret) { + ts_info("reason: bus or paltform error"); + return -EINVAL; + } + + switch (tmp_cmd.status) { + case FLASH_CMD_W_STATUS_CHK_PASS: + ts_err("data check pass, but failed get follow-up results"); + return -EFAULT; + case FLASH_CMD_W_STATUS_CHK_FAIL: + ts_err("data check failed, please retry"); + return -EAGAIN; + case FLASH_CMD_W_STATUS_ADDR_ERR: + ts_err("flash target addr error, please check"); + return -EFAULT; + case FLASH_CMD_W_STATUS_WRITE_ERR: + ts_err("flash data write err, please retry"); + return -EAGAIN; + default: + ts_err("unknown status"); + return -EFAULT; + } +} + +static int goodix_flash_package(u8 subsys_type, u8 *pkg, + u32 flash_addr, u16 pkg_len) +{ + int ret, retry; + struct goodix_flash_cmd flash_cmd; + u32 isp_buffer_reg = goodix_fw_update_ctrl.update_info->isp_buffer_reg; + + retry = 2; + do { + ret = goodix_reg_write(isp_buffer_reg, pkg, pkg_len); + if (ret < 0) { + ts_err("Failed to write firmware packet"); + return ret; + } + + flash_cmd.status = 0; + flash_cmd.ack = 0; + flash_cmd.len = FLASH_CMD_LEN; + flash_cmd.cmd = FLASH_CMD_TYPE_WRITE; + flash_cmd.fw_type = subsys_type; + flash_cmd.fw_len = cpu_to_le16(pkg_len); + flash_cmd.fw_addr = cpu_to_le32(flash_addr); + + goodix_append_checksum(&(flash_cmd.buf[2]), + 9, CHECKSUM_MODE_U8_LE); + + ret = goodix_send_flash_cmd(&flash_cmd); + if (!ret) { + ts_info("success write package to 0x%x, len %d", + flash_addr, pkg_len - 4); + return 0; + } + } while (ret == -EAGAIN && --retry); + + return ret; +} + +/** + * goodix_flash_subsystem - flash subsystem firmware, + * Main flow of flashing firmware. + * Each firmware subsystem is divided into several + * packets, the max size of packet is limited to + * @{ISP_MAX_BUFFERSIZE} + * @dev: pointer to touch device + * @subsys: subsystem information + * return: 0 ok, < 0 error + */ +static int goodix_flash_subsystem(struct fw_subsys_info *subsys) +{ + u32 data_size, offset; + u32 total_size; + //TODO: confirm flash addr ,<< 8?? + u32 subsys_base_addr = subsys->flash_addr; + u8 *fw_packet = NULL; + int r = 0; + + /* + * if bus(i2c/spi) error occued, then exit, we will do + * hardware reset and re-prepare ISP and then retry + * flashing + */ + total_size = subsys->size; + fw_packet = kzalloc(ISP_MAX_BUFFERSIZE + 4, GFP_KERNEL); + if (!fw_packet) { + ts_err("Failed alloc memory"); + return -EINVAL; + } + + offset = 0; + while (total_size > 0) { + data_size = total_size > ISP_MAX_BUFFERSIZE ? + ISP_MAX_BUFFERSIZE : total_size; + ts_info("Flash firmware to %08x,size:%u bytes", + subsys_base_addr + offset, data_size); + + memcpy(fw_packet, &subsys->data[offset], data_size); + /* set checksum for package data */ + goodix_append_checksum(fw_packet, + data_size, CHECKSUM_MODE_U16_LE); + + r = goodix_flash_package(subsys->type, fw_packet, + subsys_base_addr + offset, data_size + 4); + if (r) { + ts_err("failed flash to %08x,size:%u bytes", + subsys_base_addr + offset, data_size); + break; + } + offset += data_size; + total_size -= data_size; + } /* end while */ + + kfree(fw_packet); + return r; +} + +/** + * goodix_flash_firmware - flash firmware + * @dev: pointer to touch device + * @fw_data: firmware data + * return: 0 ok, < 0 error + */ +static int goodix_flash_firmware(struct fw_update_ctrl *fw_ctrl) +{ + struct firmware_data *fw_data = &fw_ctrl->fw_data; + struct firmware_summary *fw_summary; + struct fw_subsys_info *fw_x; + struct fw_subsys_info subsys_cfg = {0}; + u32 config_data_reg = fw_ctrl->update_info->config_data_reg; + int retry = GOODIX_BUS_RETRY_TIMES; + int i, r = 0, fw_num; + + /* start from subsystem 1, + * subsystem 0 is the ISP program + */ + + fw_summary = &fw_data->fw_summary; + fw_num = fw_summary->subsys_num; + + /* flash config data first if we have */ + if (fw_ctrl->ic_config && fw_ctrl->ic_config->len) { + subsys_cfg.data = fw_ctrl->ic_config->data; + subsys_cfg.size = fw_ctrl->ic_config->len; + subsys_cfg.flash_addr = config_data_reg; + subsys_cfg.type = CONFIG_DATA_TYPE; + r = goodix_flash_subsystem(&subsys_cfg); + if (r) { + ts_err("failed flash config with ISP, %d", r); + return r; + } + ts_info("success flash config with ISP"); + } + + for (i = 1; i < fw_num && retry;) { + ts_info("--- Start to flash subsystem[%d] ---", i); + fw_x = &fw_summary->subsys[i]; + r = goodix_flash_subsystem(fw_x); + if (r == 0) { + ts_info("--- End flash subsystem[%d]: OK ---", i); + i++; + } else if (r == -EAGAIN) { + retry--; + ts_err("--- End flash subsystem%d: Fail, errno:%d, retry:%d ---", + i, r, GOODIX_BUS_RETRY_TIMES - retry); + } else if (r < 0) { /* bus error */ + ts_err("--- End flash subsystem%d: Fatal error:%d exit ---", + i, r); + goto exit_flash; + } + } + +exit_flash: + return r; +} + +/** + * goodix_update_finish - update finished, FREE resource + * and reset flags--- + * @fwu_ctrl: pointer to fw_update_ctrl structrue + * return: 0 ok, < 0 error + */ +static int goodix_update_finish(struct fw_update_ctrl *fwu_ctrl) +{ + int ret; + + if (goodix_fw_update_reset(100)) + ts_err("reset abnormal"); + ret = goodix_fw_version_compare(fwu_ctrl); + if (ret == COMPARE_EQUAL || ret == COMPARE_CFG_NOTEQUAL) + return 0; + + return -EINVAL; +} + +/** + * goodix_fw_update_proc - firmware update process, the entry of + * firmware update flow + * @fwu_ctrl: firmware control + * return: = 0 update ok, < 0 error or NO_NEED_UPDATE + */ +int goodix_fw_update_proc(struct fw_update_ctrl *fwu_ctrl) +{ +#define FW_UPDATE_RETRY 2 + int retry0 = FW_UPDATE_RETRY; + int retry1 = FW_UPDATE_RETRY; + int ret = 0; + + ret = goodix_parse_firmware(&fwu_ctrl->fw_data); + if (ret < 0) + return ret; + + if (!(fwu_ctrl->mode & UPDATE_MODE_FORCE)) { + ret = goodix_fw_version_compare(fwu_ctrl); + ts_info("need to upgrade"); + } + +start_update: + fwu_ctrl->status = UPSTA_PREPARING; + do { + ret = goodix_update_prepare(fwu_ctrl); + if (ret) { + ts_err("failed prepare ISP, retry %d", + FW_UPDATE_RETRY - retry0); + } + } while (ret && --retry0 > 0); + if (ret) { + ts_err("Failed to prepare ISP, exit update:%d", ret); + goto err_fw_prepare; + } + + /* progress: 20%~100% */ + fwu_ctrl->status = UPSTA_UPDATING; + ret = goodix_flash_firmware(fwu_ctrl); + if (ret < 0 && --retry1 > 0) { + ts_err("Bus error, retry firmware update:%d", + FW_UPDATE_RETRY - retry1); + goto start_update; + } + if (ret) + ts_err("flash fw data enter error, ret:%d", ret); + else + ts_info("flash fw data success, need check version"); + +err_fw_prepare: + ret = goodix_update_finish(fwu_ctrl); + if (!ret) + ts_info("Firmware update successfully"); + else + ts_err("Firmware update failed, ret:%d", ret); + + return ret; +} + +/* + * goodix_sysfs_update_en_store: start fw update manually + * @buf: '1'[001] update in blocking mode with fwdata from sysfs + * '2'[010] update in blocking mode with fwdata from request + * '5'[101] update in unblocking mode with fwdata from sysfs + * '6'[110] update in unblocking mode with fwdata from request + */ +static ssize_t goodix_sysfs_update_en_store( + struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t count) +{ + int ret = 0; + int mode = 0; + struct fw_update_ctrl *fw_ctrl = &goodix_fw_update_ctrl; + + if (!buf || count <= 0) { + ts_err("invalid params"); + return -EINVAL; + } + if (!fw_ctrl || !fw_ctrl->initialized) { + ts_err("fw module uninit"); + return -EINVAL; + } + + ts_info("set update mode:0x%x", buf[0]); + if (buf[0] == '1') { + mode = UPDATE_MODE_FORCE | UPDATE_MODE_BLOCK | + UPDATE_MODE_SRC_SYSFS; + } else if (buf[0] == '2') { + mode = UPDATE_MODE_FORCE | UPDATE_MODE_BLOCK | + UPDATE_MODE_SRC_REQUEST; + } else if (buf[0] == '5') { + mode = UPDATE_MODE_FORCE | UPDATE_MODE_SRC_SYSFS; + } else if (buf[0] == '6') { + mode = UPDATE_MODE_FORCE | UPDATE_MODE_SRC_REQUEST; + } else { + ts_err("invalid update mode:0x%x", buf[0]); + return -EINVAL; + } + + ret = goodix_do_fw_update(NULL, mode); + if (!ret) { + ts_info("success do update work"); + return count; + } + ts_err("failed do fw update work"); + return -EINVAL; +} + +static ssize_t goodix_sysfs_fwimage_store(struct file *file, + struct kobject *kobj, struct bin_attribute *attr, + char *buf, loff_t pos, size_t count) +{ + struct firmware **fw = &goodix_fw_update_ctrl.fw_data.fw_sysfs; + + if (*fw == NULL) { + *fw = kzalloc(sizeof(**fw), GFP_KERNEL); + if (*fw == NULL) + return -ENOMEM; + (*fw)->data = vmalloc(GOODIX_FW_MAX_SIEZE); + if ((*fw)->data == NULL) { + kfree(*fw); + *fw = NULL; + return -ENOMEM; + } + } + + if (pos + count > GOODIX_FW_MAX_SIEZE) + return -EFAULT; + memcpy((u8 *)&(*fw)->data[pos], buf, count); + (*fw)->size = pos + count; + + return count; +} + +/* return fw_update result */ +static ssize_t goodix_sysfs_result_show( + struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + struct fw_update_ctrl *fw_ctrl = &goodix_fw_update_ctrl; + char str[GOODIX_MAX_STR_LABLE_LEN] = {0}; + int r = -EINVAL; + + if (!fw_ctrl) + return r; + + switch (fw_ctrl->status) { + case UPSTA_PREPARING: + scnprintf(str, ARRAY_SIZE(str), "preparing"); + break; + case UPSTA_UPDATING: + scnprintf(str, ARRAY_SIZE(str), "updating"); + break; + case UPSTA_SUCCESS: + scnprintf(str, ARRAY_SIZE(str), "success"); + break; + case UPSTA_FAILED: + scnprintf(str, ARRAY_SIZE(str), "failed"); + break; + case UPSTA_NOTWORK: + default: + scnprintf(str, ARRAY_SIZE(str), "notwork"); + break; + } + + r = snprintf(buf, PAGE_SIZE, "result:%s spend_time:%dms\n", + str, fw_ctrl->spend_time); + + return r; +} + +static struct kobj_attribute goodix_sysfs_update = + __ATTR(update_en, 0220, NULL, goodix_sysfs_update_en_store); +static struct kobj_attribute goodix_sysfs_result = + __ATTR(result, 0664, goodix_sysfs_result_show, NULL); + +static struct attribute *goodix_fwu_attrs[] = { + &goodix_sysfs_update.attr, + &goodix_sysfs_result.attr +}; + +static int goodix_fw_sysfs_init(struct goodix_ts_core *core_data, + struct fw_update_ctrl *fw_ctrl) +{ + int ret = 0, i; + + fw_ctrl->kobj = kobject_create_and_add("fwupdate", + &core_data->pdev->dev.kobj); + if (!fw_ctrl->kobj) { + ts_err("failed create sub dir for fwupdate"); + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(goodix_fwu_attrs) && !ret; i++) + ret = sysfs_create_file(fw_ctrl->kobj, goodix_fwu_attrs[i]); + + if (ret) { + ts_err("failed create fwu sysfs files"); + while (--i >= 0) + sysfs_remove_file(fw_ctrl->kobj, goodix_fwu_attrs[i]); + + kobject_put(fw_ctrl->kobj); + return -EINVAL; + } + + fw_ctrl->attr_fwimage.attr.name = "fwimage"; + fw_ctrl->attr_fwimage.attr.mode = 0664; + fw_ctrl->attr_fwimage.size = 0; + fw_ctrl->attr_fwimage.write = goodix_sysfs_fwimage_store; + ret = sysfs_create_bin_file(fw_ctrl->kobj, &fw_ctrl->attr_fwimage); + if (ret) { + ts_err("failed create fwimage bin node, %d", ret); + for (i = 0; i < ARRAY_SIZE(goodix_fwu_attrs); i++) + sysfs_remove_file(fw_ctrl->kobj, goodix_fwu_attrs[i]); + kobject_put(fw_ctrl->kobj); + } + + return ret; +} + +static void goodix_fw_sysfs_remove(void) +{ + struct fw_update_ctrl *fw_ctrl = &goodix_fw_update_ctrl; + int i; + + sysfs_remove_bin_file(fw_ctrl->kobj, &fw_ctrl->attr_fwimage); + + for (i = 0; i < ARRAY_SIZE(goodix_fwu_attrs); i++) + sysfs_remove_file(fw_ctrl->kobj, + goodix_fwu_attrs[i]); + + kobject_put(fw_ctrl->kobj); +} + + +/** + * goodix_request_firmware - request firmware data from user space + * + * @fw_data: firmware struct, contains firmware header info + * and firmware data pointer. + * return: 0 - OK, < 0 - error + */ +static int goodix_request_firmware(struct firmware_data *fw_data, + const char *name) +{ + struct fw_update_ctrl *fw_ctrl = + container_of(fw_data, struct fw_update_ctrl, fw_data); + struct device *dev = &(fw_ctrl->core_data->pdev->dev); + int r; + int retry = GOODIX_RETRY_3; + + ts_info("Request firmware image [%s]", name); + + while (retry--) { + r = request_firmware(&fw_data->firmware, name, dev); + if (!r) + break; + ts_info("get fw bin retry:[%d]", GOODIX_RETRY_3 - retry); + msleep(200); + } + if (retry < 0) { + ts_err("Firmware image [%s] not available,errno:%d", name, r); + return r; + } + + ts_info("Firmware image [%s] is ready", name); + return 0; +} + +/** + * relase firmware resources + * + */ +static inline void goodix_release_firmware(struct firmware_data *fw_data) +{ + if (fw_data->firmware) { + release_firmware(fw_data->firmware); + fw_data->firmware = NULL; + } +} + +static int goodix_fw_update_thread(void *data) +{ + struct fw_update_ctrl *fwu_ctrl = data; + ktime_t start, end; + int r = -EINVAL; + + start = ktime_get(); + fwu_ctrl->spend_time = 0; + fwu_ctrl->status = UPSTA_NOTWORK; + mutex_lock(&fwu_ctrl->mutex); + + ts_debug("notify update start"); + goodix_ts_blocking_notify(NOTIFY_FWUPDATE_START, NULL); + + if (fwu_ctrl->mode & UPDATE_MODE_SRC_REQUEST) { + ts_info("Firmware request update starts"); + r = goodix_request_firmware(&fwu_ctrl->fw_data, + fwu_ctrl->fw_name); + if (r < 0) + goto out; + } else if (fwu_ctrl->mode & UPDATE_MODE_SRC_SYSFS) { + if (!fwu_ctrl->fw_data.fw_sysfs) { + ts_err("Invalid firmware from sysfs"); + r = -EINVAL; + goto out; + } + if (fwu_ctrl->fw_data.fw_sysfs->size < 4096) { + ts_err("Invalid firmware size[%ld] from sysfs", + fwu_ctrl->fw_data.fw_sysfs->size); + vfree(fwu_ctrl->fw_data.fw_sysfs->data); + kfree(fwu_ctrl->fw_data.fw_sysfs); + fwu_ctrl->fw_data.fw_sysfs = NULL; + r = -EINVAL; + goto out; + } + } else { + ts_err("unknown update mode 0x%x", fwu_ctrl->mode); + r = -EINVAL; + goto out; + } + + /* ready to update */ + ts_debug("start update proc"); + r = goodix_fw_update_proc(fwu_ctrl); + + /* clean */ + if (fwu_ctrl->mode & UPDATE_MODE_SRC_SYSFS) { + vfree(fwu_ctrl->fw_data.fw_sysfs->data); + kfree(fwu_ctrl->fw_data.fw_sysfs); + fwu_ctrl->fw_data.fw_sysfs = NULL; + } else if (fwu_ctrl->mode & UPDATE_MODE_SRC_REQUEST) { + goodix_release_firmware(&fwu_ctrl->fw_data); + } +out: + fwu_ctrl->mode = UPDATE_MODE_DEFAULT; + mutex_unlock(&fwu_ctrl->mutex); + + if (r) { + ts_err("fw update failed, %d", r); + fwu_ctrl->status = UPSTA_FAILED; + goodix_ts_blocking_notify(NOTIFY_FWUPDATE_FAILED, NULL); + } else { + ts_info("fw update success"); + fwu_ctrl->status = UPSTA_SUCCESS; + goodix_ts_blocking_notify(NOTIFY_FWUPDATE_SUCCESS, NULL); + } + + end = ktime_get(); + fwu_ctrl->spend_time = ktime_to_ms(ktime_sub(end, start)); + + return r; +} + +int goodix_do_fw_update(struct goodix_ic_config *ic_config, int mode) +{ + struct task_struct *fwu_thrd; + struct fw_update_ctrl *fwu_ctrl = &goodix_fw_update_ctrl; + int ret; + + if (!fwu_ctrl->initialized) { + ts_err("fw mode uninit"); + return -EINVAL; + } + + fwu_ctrl->mode = mode; + fwu_ctrl->ic_config = ic_config; + ts_debug("fw update mode 0x%x", mode); + if (fwu_ctrl->mode & UPDATE_MODE_BLOCK) { + ret = goodix_fw_update_thread(fwu_ctrl); + ts_info("fw update return %d", ret); + return ret; + } + /* create and run update thread */ + fwu_thrd = kthread_run(goodix_fw_update_thread, + fwu_ctrl, "goodix-fwu"); + if (IS_ERR_OR_NULL(fwu_thrd)) { + ts_err("Failed to create update thread:%ld", + PTR_ERR(fwu_thrd)); + return -EFAULT; + } + ts_info("success create fw update thread"); + return 0; +} + +int goodix_fw_update_init(struct goodix_ts_core *core_data) +{ + int ret; + + if (!core_data || !core_data->hw_ops) { + ts_err("core_data && hw_ops cann't be null"); + return -ENODEV; + } + + mutex_init(&goodix_fw_update_ctrl.mutex); + goodix_fw_update_ctrl.core_data = core_data; + goodix_fw_update_ctrl.mode = 0; + + strlcpy(goodix_fw_update_ctrl.fw_name, core_data->board_data.fw_name, + sizeof(goodix_fw_update_ctrl.fw_name)); + + ret = goodix_fw_sysfs_init(core_data, &goodix_fw_update_ctrl); + if (ret) { + ts_err("failed create fwupate sysfs node"); + return ret; + } + if (core_data->bus->ic_type == IC_TYPE_BERLIN_A) + goodix_fw_update_ctrl.update_info = &update_bra; + else if (core_data->bus->ic_type == IC_TYPE_BERLIN_B) + goodix_fw_update_ctrl.update_info = &update_brb; + else + goodix_fw_update_ctrl.update_info = &update_brd; + + goodix_fw_update_ctrl.initialized = 1; + return 0; +} + +void goodix_fw_update_uninit(void) +{ + if (!goodix_fw_update_ctrl.initialized) + return; + + mutex_lock(&goodix_fw_update_ctrl.mutex); + goodix_fw_sysfs_remove(); + goodix_fw_update_ctrl.initialized = 0; + mutex_unlock(&goodix_fw_update_ctrl.mutex); + mutex_destroy(&goodix_fw_update_ctrl.mutex); +} diff --git a/qcom/opensource/touch-drivers/goodix_berlin_driver/goodix_brl_hw.c b/qcom/opensource/touch-drivers/goodix_berlin_driver/goodix_brl_hw.c new file mode 100644 index 0000000000..c02ba6178b --- /dev/null +++ b/qcom/opensource/touch-drivers/goodix_berlin_driver/goodix_brl_hw.c @@ -0,0 +1,1526 @@ + /* + * Goodix Touchscreen Driver + * Copyright (C) 2020 - 2021 Goodix, Inc. + * Copyright (c) 2022 - 2023 Qualcomm Innovation Center, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be a reference + * to you, when you are integrating the GOODiX's CTP IC into your system, + * 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. + * + */ +#include "goodix_ts_core.h" + +/* berlin_A SPI mode setting */ +#define GOODIX_SPI_MODE_REG 0xC900 +#define GOODIX_SPI_NORMAL_MODE_0 0x01 + +/* berlin_A D12 setting */ +#define GOODIX_REG_CLK_STA0 0xD807 +#define GOODIX_CLK_STA0_ENABLE 0xFF +#define GOODIX_REG_CLK_STA1 0xD806 +#define GOODIX_CLK_STA1_ENABLE 0x77 +#define GOODIX_REG_TRIM_D12 0xD006 +#define GOODIX_TRIM_D12_LEVEL 0x3C +#define GOODIX_REG_RESET 0xD808 +#define GOODIX_RESET_EN 0xFA +#define HOLD_CPU_REG_W 0x0002 +#define HOLD_CPU_REG_R 0x2000 + +#define DEV_CONFIRM_VAL 0xAA +#define BOOTOPTION_ADDR 0x10000 +#define FW_VERSION_INFO_ADDR_BRA 0x1000C +#define FW_VERSION_INFO_ADDR 0x10014 + +#define GOODIX_IC_INFO_MAX_LEN 1024 +#define GOODIX_IC_INFO_ADDR_BRA 0x10068 +#define GOODIX_IC_INFO_ADDR 0x10070 + + +enum brl_request_code { + BRL_REQUEST_CODE_CONFIG = 0x01, + BRL_REQUEST_CODE_REF_ERR = 0x02, + BRL_REQUEST_CODE_RESET = 0x03, + BRL_REQUEST_CODE_CLOCK = 0x04, +}; + +static int brl_select_spi_mode(struct goodix_ts_core *cd) +{ + int ret; + int i; + u8 w_value = GOODIX_SPI_NORMAL_MODE_0; + u8 r_value; + + if (cd->bus->bus_type == GOODIX_BUS_TYPE_I2C || + cd->bus->ic_type != IC_TYPE_BERLIN_A) + return 0; + + for (i = 0; i < GOODIX_RETRY_5; i++) { + cd->hw_ops->write(cd, GOODIX_SPI_MODE_REG, + &w_value, 1); + ret = cd->hw_ops->read(cd, GOODIX_SPI_MODE_REG, + &r_value, 1); + if (!ret && r_value == w_value) + return 0; + } + ts_err("failed switch SPI mode, ret:%d r_value:%02x", ret, r_value); + return -EINVAL; +} + +static int brl_dev_confirm(struct goodix_ts_core *cd) +{ + struct goodix_ts_hw_ops *hw_ops = cd->hw_ops; + int ret = 0; + int retry = GOODIX_RETRY_3; + u8 tx_buf[8] = {0}; + u8 rx_buf[8] = {0}; + + memset(tx_buf, DEV_CONFIRM_VAL, sizeof(tx_buf)); + while (retry--) { + ret = hw_ops->write(cd, BOOTOPTION_ADDR, + tx_buf, sizeof(tx_buf)); + if (ret < 0) + return ret; + ret = hw_ops->read(cd, BOOTOPTION_ADDR, + rx_buf, sizeof(rx_buf)); + if (ret < 0) + return ret; + if (!memcmp(tx_buf, rx_buf, sizeof(tx_buf))) + break; + usleep_range(5000, 5100); + } + + if (retry < 0) { + ret = -EINVAL; + ts_err("device confirm failed, rx_buf:%*ph", 8, rx_buf); + } + + ts_info("device connected"); + return ret; +} + +static int brl_reset_after(struct goodix_ts_core *cd) +{ + u8 reg_val[2] = {0}; + u8 temp_buf[12] = {0}; + int ret; + int retry; + + if (cd->bus->ic_type != IC_TYPE_BERLIN_A) + return 0; + + ts_info("IN"); + + /* select spi mode */ + ret = brl_select_spi_mode(cd); + if (ret < 0) + return ret; + + /* hold cpu */ + retry = GOODIX_RETRY_10; + while (retry--) { + reg_val[0] = 0x01; + reg_val[1] = 0x00; + ret = cd->hw_ops->write(cd, HOLD_CPU_REG_W, reg_val, 2); + ret |= cd->hw_ops->read(cd, HOLD_CPU_REG_R, &temp_buf[0], 4); + ret |= cd->hw_ops->read(cd, HOLD_CPU_REG_R, &temp_buf[4], 4); + ret |= cd->hw_ops->read(cd, HOLD_CPU_REG_R, &temp_buf[8], 4); + if (!ret && !memcmp(&temp_buf[0], &temp_buf[4], 4) && + !memcmp(&temp_buf[4], &temp_buf[8], 4) && + !memcmp(&temp_buf[0], &temp_buf[8], 4)) { + break; + } + } + if (retry < 0) { + ts_err("failed to hold cpu, status:%*ph", 12, temp_buf); + return -EINVAL; + } + + /* enable sta0 clk */ + retry = GOODIX_RETRY_5; + while (retry--) { + reg_val[0] = GOODIX_CLK_STA0_ENABLE; + ret = cd->hw_ops->write(cd, GOODIX_REG_CLK_STA0, reg_val, 1); + ret |= cd->hw_ops->read(cd, GOODIX_REG_CLK_STA0, temp_buf, 1); + if (!ret && temp_buf[0] == GOODIX_CLK_STA0_ENABLE) + break; + } + if (retry < 0) { + ts_err("failed to enable group0 clock, ret:%d status:%02x", + ret, temp_buf[0]); + return -EINVAL; + } + + /* enable sta1 clk */ + retry = GOODIX_RETRY_5; + while (retry--) { + reg_val[0] = GOODIX_CLK_STA1_ENABLE; + ret = cd->hw_ops->write(cd, GOODIX_REG_CLK_STA1, reg_val, 1); + ret |= cd->hw_ops->read(cd, GOODIX_REG_CLK_STA1, temp_buf, 1); + if (!ret && temp_buf[0] == GOODIX_CLK_STA1_ENABLE) + break; + } + if (retry < 0) { + ts_err("failed to enable group1 clock, ret:%d status:%02x", + ret, temp_buf[0]); + return -EINVAL; + } + + /* set D12 level */ + retry = GOODIX_RETRY_5; + while (retry--) { + reg_val[0] = GOODIX_TRIM_D12_LEVEL; + ret = cd->hw_ops->write(cd, GOODIX_REG_TRIM_D12, reg_val, 1); + ret |= cd->hw_ops->read(cd, GOODIX_REG_TRIM_D12, temp_buf, 1); + if (!ret && temp_buf[0] == GOODIX_TRIM_D12_LEVEL) + break; + } + if (retry < 0) { + ts_err("failed to set D12, ret:%d status:%02x", + ret, temp_buf[0]); + return -EINVAL; + } + + usleep_range(5000, 5100); + /* soft reset */ + reg_val[0] = GOODIX_RESET_EN; + ret = cd->hw_ops->write(cd, GOODIX_REG_RESET, reg_val, 1); + if (ret < 0) + return ret; + + /* select spi mode */ + ret = brl_select_spi_mode(cd); + if (ret < 0) + return ret; + + ts_info("OUT"); + + return 0; +} + +#define REG_SUSPEND_CURRENT 20 +#define REG_RESUME_CURRENT 30000 +#define REG_RESUME_MIN_VOLTAGE 3200000 +#define REG_RESUME_MAX_VOLTAGE 3200000 + +static int brl_power_on(struct goodix_ts_core *cd, bool on) +{ + int ret = 0; + int iovdd_gpio = cd->board_data.iovdd_gpio; + int avdd_gpio = cd->board_data.avdd_gpio; + int reset_gpio = cd->board_data.reset_gpio; + + if (on) { + if (iovdd_gpio > 0) { + gpio_direction_output(iovdd_gpio, 1); + } else if (cd->iovdd) { + if (regulator_count_voltages(cd->iovdd) > 0) { + ret = regulator_set_load(cd->iovdd, REG_RESUME_CURRENT); + if (ret) { + ts_err("Setting regulator load failed:%d", ret); + goto power_off; + } + } + ret = regulator_enable(cd->iovdd); + if (ret < 0) { + ts_err("Failed to enable iovdd:%d", ret); + goto power_off; + } + } + usleep_range(3000, 3100); + if (avdd_gpio > 0) { + gpio_direction_output(avdd_gpio, 1); + } else if (cd->avdd) { + if (regulator_count_voltages(cd->avdd) > 0) { + ret = regulator_set_load(cd->avdd, REG_RESUME_CURRENT); + if (ret) { + ts_err("vdd regulator set_load failed ret=%d", ret); + return ret; + } + ret = regulator_set_voltage(cd->avdd, REG_RESUME_MIN_VOLTAGE, + REG_RESUME_MAX_VOLTAGE); + if (ret) { + ts_err("vdd regulator set_vtg failed ret=%d", ret); + return ret; + } + } + ret = regulator_enable(cd->avdd); + if (ret < 0) { + ts_err("Failed to enable avdd:%d", ret); + goto power_off; + } + } + + gpio_direction_output(cd->board_data.reset_gpio, 0); + usleep_range(15000, 15100); + gpio_direction_output(cd->board_data.reset_gpio, 1); + msleep(GOODIX_NORMAL_RESET_DELAY_MS); + + ret = brl_dev_confirm(cd); + if (ret < 0) + goto power_off; + ret = brl_reset_after(cd); + if (ret < 0) + goto power_off; + + return 0; + } + +power_off: + gpio_direction_output(reset_gpio, 0); + if (iovdd_gpio > 0) + gpio_direction_output(iovdd_gpio, 0); + else if (cd->iovdd) { + regulator_disable(cd->iovdd); + if (regulator_count_voltages(cd->iovdd) > 0) + regulator_set_load(cd->iovdd, REG_SUSPEND_CURRENT); + } + if (avdd_gpio > 0) + gpio_direction_output(avdd_gpio, 0); + else if (cd->avdd) + regulator_disable(cd->avdd); + return ret; +} + +#define GOODIX_SLEEP_CMD 0x84 +int brl_suspend(struct goodix_ts_core *cd) +{ +#ifdef GOODIX_SUSPEND_GESTURE_ENABLE + struct goodix_ts_cmd sleep_cmd; + + sleep_cmd.cmd = GOODIX_SLEEP_CMD; + sleep_cmd.len = 4; + if (cd->hw_ops->send_cmd(cd, &sleep_cmd)) + ts_err("failed send sleep cmd"); +#else + if (cd->hw_ops->power_on(cd, 0)) + ts_err("failed power off"); +#endif + return 0; +} + +int brl_resume(struct goodix_ts_core *cd) +{ + int ret = 0; + +#ifdef GOODIX_SUSPEND_GESTURE_ENABLE + ret = cd->hw_ops->reset(cd, GOODIX_NORMAL_RESET_DELAY_MS); +#else + ret = cd->hw_ops->power_on(cd, 1); + if (ret) { + ts_err("failed power on"); + return ret; + } +#endif + + return ret; +} + +#define GOODIX_GESTURE_CMD_BA 0x12 +#define GOODIX_GESTURE_CMD 0xA6 +int brl_gesture(struct goodix_ts_core *cd, int gesture_type) +{ + struct goodix_ts_cmd cmd; + + if (cd->bus->ic_type == IC_TYPE_BERLIN_A) + cmd.cmd = GOODIX_GESTURE_CMD_BA; + else + cmd.cmd = GOODIX_GESTURE_CMD; + cmd.len = 5; + cmd.data[0] = gesture_type; + if (cd->hw_ops->send_cmd(cd, &cmd)) + ts_err("failed send gesture cmd"); + + return 0; +} + +static int brl_reset(struct goodix_ts_core *cd, int delay) +{ + ts_info("chip_reset"); + + gpio_direction_output(cd->board_data.reset_gpio, 0); + usleep_range(2000, 2100); + gpio_direction_output(cd->board_data.reset_gpio, 1); + if (delay < 20) + usleep_range(delay * 1000, delay * 1000 + 100); + else + msleep(delay); + + return brl_select_spi_mode(cd); +} + +static int brl_irq_enbale(struct goodix_ts_core *cd, bool enable) +{ + if (enable && !atomic_cmpxchg(&cd->irq_enabled, 0, 1)) { + enable_irq(cd->irq); + ts_debug("Irq enabled"); + return 0; + } + + if (!enable && atomic_cmpxchg(&cd->irq_enabled, 1, 0)) { + disable_irq_nosync(cd->irq); + ts_debug("Irq disabled"); + return 0; + } + ts_info("warning: irq depth imbalance!"); + return 0; +} + +static int brl_read(struct goodix_ts_core *cd, unsigned int addr, + unsigned char *data, unsigned int len) +{ + struct goodix_bus_interface *bus = cd->bus; + + return bus->read(bus->dev, addr, data, len); +} + +static int brl_write(struct goodix_ts_core *cd, unsigned int addr, + unsigned char *data, unsigned int len) +{ + struct goodix_bus_interface *bus = cd->bus; + + return bus->write(bus->dev, addr, data, len); +} + +/* command ack info */ +#define CMD_ACK_IDLE 0x01 +#define CMD_ACK_BUSY 0x02 +#define CMD_ACK_BUFFER_OVERFLOW 0x03 +#define CMD_ACK_CHECKSUM_ERROR 0x04 +#define CMD_ACK_OK 0x80 + +#define GOODIX_CMD_RETRY 6 +static int brl_send_cmd(struct goodix_ts_core *cd, + struct goodix_ts_cmd *cmd) +{ + int ret, retry, i; + struct goodix_ts_cmd cmd_ack; + struct goodix_ic_info_misc *misc = &cd->ic_info.misc; + struct goodix_ts_hw_ops *hw_ops = cd->hw_ops; + + cmd->state = 0; + cmd->ack = 0; + goodix_append_checksum(&(cmd->buf[2]), cmd->len - 2, + CHECKSUM_MODE_U8_LE); + ts_debug("cmd data %*ph", cmd->len, &(cmd->buf[2])); + + retry = 0; + while (retry++ < GOODIX_CMD_RETRY) { + ret = hw_ops->write(cd, misc->cmd_addr, + cmd->buf, sizeof(*cmd)); + if (ret < 0) { + ts_err("failed write command"); + return ret; + } + for (i = 0; i < GOODIX_CMD_RETRY; i++) { + /* check command result */ + ret = hw_ops->read(cd, misc->cmd_addr, + cmd_ack.buf, sizeof(cmd_ack)); + if (ret < 0) { + ts_err("failed read command ack, %d", ret); + return ret; + } + ts_debug("cmd ack data %*ph", + (int)sizeof(cmd_ack), cmd_ack.buf); + if (cmd_ack.ack == CMD_ACK_OK) { + msleep(40); // wait for cmd response + return 0; + } + if (cmd_ack.ack == CMD_ACK_BUSY || + cmd_ack.ack == 0x00) { + usleep_range(1000, 1100); + continue; + } + if (cmd_ack.ack == CMD_ACK_BUFFER_OVERFLOW) + usleep_range(10000, 11000); + usleep_range(1000, 1100); + break; + } + } + ts_err("failed get valid cmd ack"); + return -EINVAL; +} + +#pragma pack(1) +struct goodix_config_head { + union { + struct { + u8 panel_name[8]; + u8 fw_pid[8]; + u8 fw_vid[4]; + u8 project_name[8]; + u8 file_ver[2]; + u32 cfg_id; + u8 cfg_ver; + u8 cfg_time[8]; + u8 reserved[15]; + u8 flag; + u16 cfg_len; + u8 cfg_num; + u16 checksum; + }; + u8 buf[64]; + }; +}; +#pragma pack() + +#define CONFIG_CND_LEN 4 +#define CONFIG_CMD_START 0x04 +#define CONFIG_CMD_WRITE 0x05 +#define CONFIG_CMD_EXIT 0x06 +#define CONFIG_CMD_READ_START 0x07 +#define CONFIG_CMD_READ_EXIT 0x08 + +#define CONFIG_CMD_STATUS_PASS 0x80 +#define CONFIG_CMD_WAIT_RETRY 20 + +static int wait_cmd_status(struct goodix_ts_core *cd, + u8 target_status, int retry) +{ + struct goodix_ts_cmd cmd_ack; + struct goodix_ic_info_misc *misc = &cd->ic_info.misc; + struct goodix_ts_hw_ops *hw_ops = cd->hw_ops; + int i, ret; + + for (i = 0; i < retry; i++) { + ret = hw_ops->read(cd, misc->cmd_addr, cmd_ack.buf, + sizeof(cmd_ack)); + if (!ret && cmd_ack.state == target_status) { + ts_debug("status check pass"); + return 0; + } + ts_debug("cmd buf %*ph", (int)sizeof(cmd_ack), cmd_ack.buf); + msleep(20); + } + + ts_err("cmd status not ready, retry %d, ack 0x%x, status 0x%x, ret %d", + i, cmd_ack.ack, cmd_ack.state, ret); + return -EINVAL; +} + +static int send_cfg_cmd(struct goodix_ts_core *cd, + struct goodix_ts_cmd *cfg_cmd) +{ + int ret; + + ret = cd->hw_ops->send_cmd(cd, cfg_cmd); + if (ret) { + ts_err("failed write cfg prepare cmd %d", ret); + return ret; + } + ret = wait_cmd_status(cd, CONFIG_CMD_STATUS_PASS, + CONFIG_CMD_WAIT_RETRY); + if (ret) { + ts_err("failed wait for fw ready for config, %d", ret); + return ret; + } + return 0; +} + +static int brl_send_config(struct goodix_ts_core *cd, u8 *cfg, int len) +{ + int ret; + u8 *tmp_buf; + u16 cfg_head_len = sizeof(struct goodix_config_head) / sizeof(u8); + struct goodix_ts_cmd cfg_cmd; + struct goodix_ic_info_misc *misc = &cd->ic_info.misc; + struct goodix_ts_hw_ops *hw_ops = cd->hw_ops; + struct goodix_config_head *cfg_head = (struct goodix_config_head *)cfg; + + if (!cd || !cfg) { + ts_err("input parameter is NULL"); + return -EINVAL; + } else if (len > misc->fw_buffer_max_len) { + ts_err("config len exceed limit %d > %d", + len, misc->fw_buffer_max_len); + return -EINVAL; + } else if (len < cfg_head_len) { + ts_err("config buffer size %d smaller than header size %d", + len, cfg_head_len); + return -EINVAL; + } else if (len != cfg_head_len + cfg_head->cfg_len) { + ts_err("config buffer size %d not equal to head %d + cfg_len %d", + len, cfg_head_len, cfg_head->cfg_len); + return -EINVAL; + } else if (checksum_cmp(cfg, cfg_head_len, CHECKSUM_MODE_U8_LE)) { + ts_err("config head checksum error"); + return -EINVAL; + } else if (checksum_cmp(cfg + cfg_head_len, cfg_head->cfg_len, CHECKSUM_MODE_U16_LE)) { + ts_err("config body checksum error"); + return -EINVAL; + } + + tmp_buf = kzalloc(len, GFP_KERNEL); + if (!tmp_buf) + return -ENOMEM; + + cfg_cmd.len = CONFIG_CND_LEN; + cfg_cmd.cmd = CONFIG_CMD_START; + ret = send_cfg_cmd(cd, &cfg_cmd); + if (ret) { + ts_err("failed write cfg prepare cmd %d", ret); + goto exit; + } + + ts_debug("try send config to 0x%x, len %d", misc->fw_buffer_addr, len); + ret = hw_ops->write(cd, misc->fw_buffer_addr, cfg, len); + if (ret) { + ts_err("failed write config data, %d", ret); + goto exit; + } + ret = hw_ops->read(cd, misc->fw_buffer_addr, tmp_buf, len); + if (ret) { + ts_err("failed read back config data"); + goto exit; + } + + if (memcmp(cfg, tmp_buf, len)) { + ts_err("config data read back compare file"); + ret = -EINVAL; + goto exit; + } + /* notify fw for recive config */ + memset(cfg_cmd.buf, 0, sizeof(cfg_cmd)); + cfg_cmd.len = CONFIG_CND_LEN; + cfg_cmd.cmd = CONFIG_CMD_WRITE; + ret = send_cfg_cmd(cd, &cfg_cmd); + if (ret) + ts_err("failed send config data ready cmd %d", ret); + +exit: + memset(cfg_cmd.buf, 0, sizeof(cfg_cmd)); + cfg_cmd.len = CONFIG_CND_LEN; + cfg_cmd.cmd = CONFIG_CMD_EXIT; + if (send_cfg_cmd(cd, &cfg_cmd)) { + ts_err("failed send config write end command"); + ret = -EINVAL; + } + + if (!ret) { + ts_info("success send config"); + msleep(100); + } + + kfree(tmp_buf); + return ret; +} + +/* + * return: return config length on succes, other wise return < 0 + **/ +static int brl_read_config(struct goodix_ts_core *cd, u8 *cfg, int size) +{ + int ret; + struct goodix_ts_cmd cfg_cmd; + struct goodix_ic_info_misc *misc = &cd->ic_info.misc; + struct goodix_ts_hw_ops *hw_ops = cd->hw_ops; + struct goodix_config_head cfg_head; + + if (!cfg || sizeof(cfg_head) > size) + return -EINVAL; + + cfg_cmd.len = CONFIG_CND_LEN; + cfg_cmd.cmd = CONFIG_CMD_READ_START; + ret = send_cfg_cmd(cd, &cfg_cmd); + if (ret) { + ts_err("failed send config read prepare command"); + return ret; + } + + ret = hw_ops->read(cd, misc->fw_buffer_addr, + cfg_head.buf, sizeof(cfg_head)); + if (ret) { + ts_err("failed read config head %d", ret); + goto exit; + } + + if (checksum_cmp(cfg_head.buf, sizeof(cfg_head), CHECKSUM_MODE_U8_LE)) { + ts_err("config head checksum error"); + ret = -EINVAL; + goto exit; + } + + cfg_head.cfg_len = le16_to_cpu(cfg_head.cfg_len); + if (cfg_head.cfg_len > misc->fw_buffer_max_len || + cfg_head.cfg_len > size) { + ts_err("cfg len exceed buffer size %d > %d", cfg_head.cfg_len, + misc->fw_buffer_max_len); + ret = -EINVAL; + goto exit; + } + + memcpy(cfg, cfg_head.buf, sizeof(cfg_head)); + ret = hw_ops->read(cd, misc->fw_buffer_addr + sizeof(cfg_head), + cfg + sizeof(cfg_head), cfg_head.cfg_len); + if (ret) { + ts_err("failed read cfg pack, %d", ret); + goto exit; + } + + ts_info("config len %d", cfg_head.cfg_len); + if (checksum_cmp(cfg + sizeof(cfg_head), + cfg_head.cfg_len, CHECKSUM_MODE_U16_LE)) { + ts_err("config body checksum error"); + ret = -EINVAL; + goto exit; + } + ts_info("success read config data: len %zu", + cfg_head.cfg_len + sizeof(cfg_head)); +exit: + memset(cfg_cmd.buf, 0, sizeof(cfg_cmd)); + cfg_cmd.len = CONFIG_CND_LEN; + cfg_cmd.cmd = CONFIG_CMD_READ_EXIT; + if (send_cfg_cmd(cd, &cfg_cmd)) { + ts_err("failed send config read finish command"); + ret = -EINVAL; + } + if (ret) + return -EINVAL; + return cfg_head.cfg_len + sizeof(cfg_head); +} + +/* + * return: 0 for no error. + * GOODIX_EBUS when encounter a bus error + * GOODIX_ECHECKSUM version checksum error + * GOODIX_EVERSION patch ID compare failed, + * in this case the sensorID is valid. + */ +static int brl_read_version(struct goodix_ts_core *cd, + struct goodix_fw_version *version) +{ + int ret, i; + u32 fw_addr; + struct goodix_ts_hw_ops *hw_ops = cd->hw_ops; + u8 buf[sizeof(struct goodix_fw_version)] = {0}; + u8 temp_pid[8] = {0}; + + if (cd->bus->ic_type == IC_TYPE_BERLIN_A) + fw_addr = FW_VERSION_INFO_ADDR_BRA; + else + fw_addr = FW_VERSION_INFO_ADDR; + + for (i = 0; i < 2; i++) { + ret = hw_ops->read(cd, fw_addr, buf, sizeof(buf)); + if (ret) { + ts_info("read fw version: %d, retry %d", ret, i); + ret = -GOODIX_EBUS; + usleep_range(5000, 5100); + continue; + } + + if (!checksum_cmp(buf, sizeof(buf), CHECKSUM_MODE_U8_LE)) + break; + + ts_info("invalid fw version: checksum error!"); + ts_info("fw version:%*ph", (int)sizeof(buf), buf); + ret = -GOODIX_ECHECKSUM; + usleep_range(10000, 11000); + } + if (ret) { + ts_err("failed get valied fw version"); + return ret; + } + memcpy(version, buf, sizeof(*version)); + memcpy(temp_pid, version->rom_pid, sizeof(version->rom_pid)); + ts_info("rom_pid:%s", temp_pid); + ts_info("rom_vid:%*ph", (int)sizeof(version->rom_vid), + version->rom_vid); + ts_info("pid:%s", version->patch_pid); + ts_info("vid:%*ph", (int)sizeof(version->patch_vid), + version->patch_vid); + ts_info("sensor_id:%d", version->sensor_id); + + return 0; +} + +#define LE16_TO_CPU(x) (x = le16_to_cpu(x)) +#define LE32_TO_CPU(x) (x = le32_to_cpu(x)) +static int convert_ic_info(struct goodix_ic_info *info, const u8 *data) +{ + int i; + struct goodix_ic_info_version *version = &info->version; + struct goodix_ic_info_feature *feature = &info->feature; + struct goodix_ic_info_param *parm = &info->parm; + struct goodix_ic_info_misc *misc = &info->misc; + + info->length = le16_to_cpup((__le16 *)data); + + data += 2; + memcpy(version, data, sizeof(*version)); + version->config_id = le32_to_cpu(version->config_id); + + data += sizeof(struct goodix_ic_info_version); + memcpy(feature, data, sizeof(*feature)); + feature->freqhop_feature = + le16_to_cpu(feature->freqhop_feature); + feature->calibration_feature = + le16_to_cpu(feature->calibration_feature); + feature->gesture_feature = + le16_to_cpu(feature->gesture_feature); + feature->side_touch_feature = + le16_to_cpu(feature->side_touch_feature); + feature->stylus_feature = + le16_to_cpu(feature->stylus_feature); + + data += sizeof(struct goodix_ic_info_feature); + parm->drv_num = *(data++); + parm->sen_num = *(data++); + parm->button_num = *(data++); + parm->force_num = *(data++); + parm->active_scan_rate_num = *(data++); + if (parm->active_scan_rate_num > MAX_SCAN_RATE_NUM) { + ts_err("invalid scan rate num %d > %d", + parm->active_scan_rate_num, MAX_SCAN_RATE_NUM); + return -EINVAL; + } + for (i = 0; i < parm->active_scan_rate_num; i++) + parm->active_scan_rate[i] = + le16_to_cpup((__le16 *)(data + i * 2)); + + data += parm->active_scan_rate_num * 2; + parm->mutual_freq_num = *(data++); + if (parm->mutual_freq_num > MAX_SCAN_FREQ_NUM) { + ts_err("invalid mntual freq num %d > %d", + parm->mutual_freq_num, MAX_SCAN_FREQ_NUM); + return -EINVAL; + } + for (i = 0; i < parm->mutual_freq_num; i++) + parm->mutual_freq[i] = + le16_to_cpup((__le16 *)(data + i * 2)); + + data += parm->mutual_freq_num * 2; + parm->self_tx_freq_num = *(data++); + if (parm->self_tx_freq_num > MAX_SCAN_FREQ_NUM) { + ts_err("invalid tx freq num %d > %d", + parm->self_tx_freq_num, MAX_SCAN_FREQ_NUM); + return -EINVAL; + } + for (i = 0; i < parm->self_tx_freq_num; i++) + parm->self_tx_freq[i] = + le16_to_cpup((__le16 *)(data + i * 2)); + + data += parm->self_tx_freq_num * 2; + parm->self_rx_freq_num = *(data++); + if (parm->self_rx_freq_num > MAX_SCAN_FREQ_NUM) { + ts_err("invalid rx freq num %d > %d", + parm->self_rx_freq_num, MAX_SCAN_FREQ_NUM); + return -EINVAL; + } + for (i = 0; i < parm->self_rx_freq_num; i++) + parm->self_rx_freq[i] = + le16_to_cpup((__le16 *)(data + i * 2)); + + data += parm->self_rx_freq_num * 2; + parm->stylus_freq_num = *(data++); + if (parm->stylus_freq_num > MAX_FREQ_NUM_STYLUS) { + ts_err("invalid stylus freq num %d > %d", + parm->stylus_freq_num, MAX_FREQ_NUM_STYLUS); + return -EINVAL; + } + for (i = 0; i < parm->stylus_freq_num; i++) + parm->stylus_freq[i] = + le16_to_cpup((__le16 *)(data + i * 2)); + + data += parm->stylus_freq_num * 2; + memcpy(misc, data, sizeof(*misc)); + misc->cmd_addr = le32_to_cpu(misc->cmd_addr); + misc->cmd_max_len = le16_to_cpu(misc->cmd_max_len); + misc->cmd_reply_addr = le32_to_cpu(misc->cmd_reply_addr); + misc->cmd_reply_len = le16_to_cpu(misc->cmd_reply_len); + misc->fw_state_addr = le32_to_cpu(misc->fw_state_addr); + misc->fw_state_len = le16_to_cpu(misc->fw_state_len); + misc->fw_buffer_addr = le32_to_cpu(misc->fw_buffer_addr); + misc->fw_buffer_max_len = le16_to_cpu(misc->fw_buffer_max_len); + misc->frame_data_addr = le32_to_cpu(misc->frame_data_addr); + misc->frame_data_head_len = le16_to_cpu(misc->frame_data_head_len); + + misc->fw_attr_len = le16_to_cpu(misc->fw_attr_len); + misc->fw_log_len = le16_to_cpu(misc->fw_log_len); + misc->stylus_struct_len = le16_to_cpu(misc->stylus_struct_len); + misc->mutual_struct_len = le16_to_cpu(misc->mutual_struct_len); + misc->self_struct_len = le16_to_cpu(misc->self_struct_len); + misc->noise_struct_len = le16_to_cpu(misc->noise_struct_len); + misc->touch_data_addr = le32_to_cpu(misc->touch_data_addr); + misc->touch_data_head_len = le16_to_cpu(misc->touch_data_head_len); + misc->point_struct_len = le16_to_cpu(misc->point_struct_len); + LE32_TO_CPU(misc->mutual_rawdata_addr); + LE32_TO_CPU(misc->mutual_diffdata_addr); + LE32_TO_CPU(misc->mutual_refdata_addr); + LE32_TO_CPU(misc->self_rawdata_addr); + LE32_TO_CPU(misc->self_diffdata_addr); + LE32_TO_CPU(misc->self_refdata_addr); + LE32_TO_CPU(misc->iq_rawdata_addr); + LE32_TO_CPU(misc->iq_refdata_addr); + LE32_TO_CPU(misc->im_rawdata_addr); + LE16_TO_CPU(misc->im_readata_len); + LE32_TO_CPU(misc->noise_rawdata_addr); + LE16_TO_CPU(misc->noise_rawdata_len); + LE32_TO_CPU(misc->stylus_rawdata_addr); + LE16_TO_CPU(misc->stylus_rawdata_len); + LE32_TO_CPU(misc->noise_data_addr); + LE32_TO_CPU(misc->esd_addr); + + return 0; +} + +static void print_ic_info(struct goodix_ic_info *ic_info) +{ + struct goodix_ic_info_version *version = &ic_info->version; + struct goodix_ic_info_feature *feature = &ic_info->feature; + struct goodix_ic_info_param *parm = &ic_info->parm; + struct goodix_ic_info_misc *misc = &ic_info->misc; + + ts_info("ic_info_length: %d", + ic_info->length); + ts_info("info_customer_id: 0x%01X", + version->info_customer_id); + ts_info("info_version_id: 0x%01X", + version->info_version_id); + ts_info("ic_die_id: 0x%01X", + version->ic_die_id); + ts_info("ic_version_id: 0x%01X", + version->ic_version_id); + ts_info("config_id: 0x%4X", + version->config_id); + ts_info("config_version: 0x%01X", + version->config_version); + ts_info("frame_data_customer_id: 0x%01X", + version->frame_data_customer_id); + ts_info("frame_data_version_id: 0x%01X", + version->frame_data_version_id); + ts_info("touch_data_customer_id: 0x%01X", + version->touch_data_customer_id); + ts_info("touch_data_version_id: 0x%01X", + version->touch_data_version_id); + + ts_info("freqhop_feature: 0x%04X", + feature->freqhop_feature); + ts_info("calibration_feature: 0x%04X", + feature->calibration_feature); + ts_info("gesture_feature: 0x%04X", + feature->gesture_feature); + ts_info("side_touch_feature: 0x%04X", + feature->side_touch_feature); + ts_info("stylus_feature: 0x%04X", + feature->stylus_feature); + + ts_info("Drv*Sen,Button,Force num: %d x %d, %d, %d", + parm->drv_num, parm->sen_num, + parm->button_num, parm->force_num); + + ts_info("Cmd: 0x%04X, %d", + misc->cmd_addr, misc->cmd_max_len); + ts_info("Cmd-Reply: 0x%04X, %d", + misc->cmd_reply_addr, misc->cmd_reply_len); + ts_info("FW-State: 0x%04X, %d", + misc->fw_state_addr, misc->fw_state_len); + ts_info("FW-Buffer: 0x%04X, %d", + misc->fw_buffer_addr, misc->fw_buffer_max_len); + ts_info("Touch-Data: 0x%04X, %d", + misc->touch_data_addr, misc->touch_data_head_len); + ts_info("point_struct_len: %d", + misc->point_struct_len); + ts_info("mutual_rawdata_addr: 0x%04X", + misc->mutual_rawdata_addr); + ts_info("mutual_diffdata_addr: 0x%04X", + misc->mutual_diffdata_addr); + ts_info("self_rawdata_addr: 0x%04X", + misc->self_rawdata_addr); + ts_info("self_diffdata_addr: 0x%04X", + misc->self_diffdata_addr); + ts_info("stylus_rawdata_addr: 0x%04X, %d", + misc->stylus_rawdata_addr, misc->stylus_rawdata_len); + ts_info("esd_addr: 0x%04X", + misc->esd_addr); +} + +static int brl_get_ic_info(struct goodix_ts_core *cd, + struct goodix_ic_info *ic_info) +{ + int ret, i; + u16 length = 0; + u32 ic_addr; + u8 afe_data[GOODIX_IC_INFO_MAX_LEN] = {0}; + struct goodix_ts_hw_ops *hw_ops = cd->hw_ops; + + if (cd->bus->ic_type == IC_TYPE_BERLIN_A) + ic_addr = GOODIX_IC_INFO_ADDR_BRA; + else + ic_addr = GOODIX_IC_INFO_ADDR; + + for (i = 0; i < GOODIX_RETRY_3; i++) { + ret = hw_ops->read(cd, ic_addr, + (u8 *)&length, sizeof(length)); + if (ret) { + ts_info("failed get ic info length, %d", ret); + usleep_range(5000, 5100); + continue; + } + length = le16_to_cpu(length); + if (length >= GOODIX_IC_INFO_MAX_LEN) { + ts_info("invalid ic info length %d, retry %d", + length, i); + continue; + } + + ret = hw_ops->read(cd, ic_addr, afe_data, length); + if (ret) { + ts_info("failed get ic info data, %d", ret); + usleep_range(5000, 5100); + continue; + } + /* judge whether the data is valid */ + if (is_risk_data((const uint8_t *)afe_data, length)) { + ts_info("fw info data invalid"); + usleep_range(5000, 5100); + continue; + } + if (checksum_cmp((const uint8_t *)afe_data, + length, CHECKSUM_MODE_U8_LE)) { + ts_info("fw info checksum error!"); + usleep_range(5000, 5100); + continue; + } + break; + } + if (i == GOODIX_RETRY_3) { + ts_err("failed get ic info"); + return -EINVAL; + } + + ret = convert_ic_info(ic_info, afe_data); + if (ret) { + ts_err("convert ic info encounter error"); + return ret; + } + + print_ic_info(ic_info); + + /* check some key info */ + if (!ic_info->misc.cmd_addr || !ic_info->misc.fw_buffer_addr || + !ic_info->misc.touch_data_addr) { + ts_err("cmd_addr fw_buf_addr and touch_data_addr is null"); + return -EINVAL; + } + + return 0; +} + +#define GOODIX_ESD_TICK_WRITE_DATA 0xAA +static int brl_esd_check(struct goodix_ts_core *cd) +{ + int ret; + u32 esd_addr; + u8 esd_value; + + if (!cd->ic_info.misc.esd_addr) + return 0; + + esd_addr = cd->ic_info.misc.esd_addr; + ret = cd->hw_ops->read(cd, esd_addr, &esd_value, 1); + if (ret) { + ts_err("failed get esd value, %d", ret); + return ret; + } + + if (esd_value == GOODIX_ESD_TICK_WRITE_DATA) { + ts_err("esd check failed, 0x%x", esd_value); + return -EINVAL; + } + esd_value = GOODIX_ESD_TICK_WRITE_DATA; + ret = cd->hw_ops->write(cd, esd_addr, &esd_value, 1); + if (ret) { + ts_err("failed refrash esd value"); + return ret; + } + return 0; +} + +#define IRQ_EVENT_HEAD_LEN 8 +#define BYTES_PER_POINT 8 +#define COOR_DATA_CHECKSUM_SIZE 2 + +#define GOODIX_TOUCH_EVENT 0x80 +#define GOODIX_REQUEST_EVENT 0x40 +#define GOODIX_GESTURE_EVENT 0x20 +#define POINT_TYPE_STYLUS_HOVER 0x01 +#define POINT_TYPE_STYLUS 0x03 + +static void goodix_parse_finger(struct goodix_touch_data *touch_data, + u8 *buf, int touch_num) +{ + unsigned int id = 0, x = 0, y = 0, w = 0; + u8 *coor_data; + int i; + + coor_data = &buf[IRQ_EVENT_HEAD_LEN]; + for (i = 0; i < touch_num; i++) { + id = (coor_data[0] >> 4) & 0x0F; + if (id >= GOODIX_MAX_TOUCH) { + ts_info("invalid finger id =%d", id); + touch_data->touch_num = 0; + return; + } + x = le16_to_cpup((__le16 *)(coor_data + 2)); + y = le16_to_cpup((__le16 *)(coor_data + 4)); + w = le16_to_cpup((__le16 *)(coor_data + 6)); + touch_data->coords[id].status = TS_TOUCH; + touch_data->coords[id].x = x; + touch_data->coords[id].y = y; + touch_data->coords[id].w = w; + coor_data += BYTES_PER_POINT; + } + touch_data->touch_num = touch_num; +} + +static unsigned int goodix_pen_btn_code[] = {BTN_STYLUS, BTN_STYLUS2}; +static void goodix_parse_pen(struct goodix_pen_data *pen_data, + u8 *buf, int touch_num) +{ + unsigned int id = 0; + u8 cur_key_map = 0; + u8 *coor_data; + int16_t x_angle, y_angle; + int i; + + pen_data->coords.tool_type = BTN_TOOL_PEN; + + if (touch_num) { + pen_data->coords.status = TS_TOUCH; + coor_data = &buf[IRQ_EVENT_HEAD_LEN]; + + id = (coor_data[0] >> 4) & 0x0F; + pen_data->coords.x = le16_to_cpup((__le16 *)(coor_data + 2)); + pen_data->coords.y = le16_to_cpup((__le16 *)(coor_data + 4)); + pen_data->coords.p = le16_to_cpup((__le16 *)(coor_data + 6)); + x_angle = le16_to_cpup((__le16 *)(coor_data + 8)); + y_angle = le16_to_cpup((__le16 *)(coor_data + 10)); + pen_data->coords.tilt_x = x_angle / 100; + pen_data->coords.tilt_y = y_angle / 100; + } else { + pen_data->coords.status = TS_RELEASE; + } + + cur_key_map = (buf[3] & 0x0F) >> 1; + for (i = 0; i < GOODIX_MAX_PEN_KEY; i++) { + pen_data->keys[i].code = goodix_pen_btn_code[i]; + if (!(cur_key_map & (1 << i))) + continue; + pen_data->keys[i].status = TS_TOUCH; + } +} + +static int goodix_touch_handler(struct goodix_ts_core *cd, + struct goodix_ts_event *ts_event, + u8 *pre_buf, u32 pre_buf_len) +{ + struct goodix_ts_hw_ops *hw_ops = cd->hw_ops; + struct goodix_ic_info_misc *misc = &cd->ic_info.misc; + struct goodix_touch_data *touch_data = &ts_event->touch_data; + struct goodix_pen_data *pen_data = &ts_event->pen_data; + static u8 buffer[IRQ_EVENT_HEAD_LEN + + BYTES_PER_POINT * GOODIX_MAX_TOUCH + 2]; + u8 touch_num = 0; + int ret = 0; + u8 point_type = 0; + static u8 pre_finger_num; + static u8 pre_pen_num; + + /* clean event buffer */ + memset(ts_event, 0, sizeof(*ts_event)); + /* copy pre-data to buffer */ + memcpy(buffer, pre_buf, pre_buf_len); + + touch_num = buffer[2] & 0x0F; + + if (touch_num > GOODIX_MAX_TOUCH) { + ts_debug("invalid touch num %d", touch_num); + return -EINVAL; + } + + if (unlikely(touch_num > 2)) { + ret = hw_ops->read(cd, + misc->touch_data_addr + pre_buf_len, + &buffer[pre_buf_len], + (touch_num - 2) * BYTES_PER_POINT); + if (ret) { + ts_debug("failed get touch data"); + return ret; + } + } + + /* read done */ + hw_ops->after_event_handler(cd); + + if (touch_num > 0) { + point_type = buffer[IRQ_EVENT_HEAD_LEN] & 0x0F; + if (point_type == POINT_TYPE_STYLUS || + point_type == POINT_TYPE_STYLUS_HOVER) { + ret = checksum_cmp(&buffer[IRQ_EVENT_HEAD_LEN], + BYTES_PER_POINT * 2 + 2, + CHECKSUM_MODE_U8_LE); + if (ret) { + ts_debug("touch data checksum error"); + ts_debug("data:%*ph", BYTES_PER_POINT * 2 + 2, + &buffer[IRQ_EVENT_HEAD_LEN]); + return -EINVAL; + } + } else { + ret = checksum_cmp(&buffer[IRQ_EVENT_HEAD_LEN], + touch_num * BYTES_PER_POINT + 2, + CHECKSUM_MODE_U8_LE); + if (ret) { + ts_debug("touch data checksum error"); + ts_debug("data:%*ph", + touch_num * BYTES_PER_POINT + 2, + &buffer[IRQ_EVENT_HEAD_LEN]); + return -EINVAL; + } + } + } + + if (touch_num > 0 && (point_type == POINT_TYPE_STYLUS + || point_type == POINT_TYPE_STYLUS_HOVER)) { + /* stylus info */ + if (pre_finger_num) { + ts_event->event_type = EVENT_TOUCH; + goodix_parse_finger(touch_data, buffer, 0); + pre_finger_num = 0; + } else { + pre_pen_num = 1; + ts_event->event_type = EVENT_PEN; + goodix_parse_pen(pen_data, buffer, touch_num); + } + } else { + /* finger info */ + if (pre_pen_num) { + ts_event->event_type = EVENT_PEN; + goodix_parse_pen(pen_data, buffer, 0); + pre_pen_num = 0; + } else { + ts_event->event_type = EVENT_TOUCH; + goodix_parse_finger(touch_data, buffer, touch_num); + pre_finger_num = touch_num; + } + } + + /* process custom info */ + if (buffer[3] & 0x01) + ts_debug("TODO add custom info process function"); + + return 0; +} + +static int brl_event_handler(struct goodix_ts_core *cd, + struct goodix_ts_event *ts_event) +{ + struct goodix_ts_hw_ops *hw_ops = cd->hw_ops; + struct goodix_ic_info_misc *misc = &cd->ic_info.misc; + int pre_read_len; + u8 pre_buf[32]; + u8 event_status; + int ret; + + pre_read_len = IRQ_EVENT_HEAD_LEN + + BYTES_PER_POINT * 2 + COOR_DATA_CHECKSUM_SIZE; + ret = hw_ops->read(cd, misc->touch_data_addr, + pre_buf, pre_read_len); + if (ret) { + ts_debug("failed get event head data"); + return ret; + } + + if (pre_buf[0] == 0x00) { + ts_debug("invalid touch head"); + return -EINVAL; + } + + if (checksum_cmp(pre_buf, IRQ_EVENT_HEAD_LEN, CHECKSUM_MODE_U8_LE)) { + ts_debug("touch head checksum err[%*ph]", + IRQ_EVENT_HEAD_LEN, pre_buf); + return -EINVAL; + } + + event_status = pre_buf[0]; + if (event_status & GOODIX_TOUCH_EVENT) + return goodix_touch_handler(cd, ts_event, + pre_buf, pre_read_len); + + if (event_status & GOODIX_REQUEST_EVENT) { + ts_event->event_type = EVENT_REQUEST; + if (pre_buf[2] == BRL_REQUEST_CODE_CONFIG) + ts_event->request_code = REQUEST_TYPE_CONFIG; + else if (pre_buf[2] == BRL_REQUEST_CODE_RESET) + ts_event->request_code = REQUEST_TYPE_RESET; + else + ts_debug("unsupported request code 0x%x", pre_buf[2]); + } + + if (event_status & GOODIX_GESTURE_EVENT) { + ts_event->event_type = EVENT_GESTURE; + ts_event->gesture_type = pre_buf[4]; + memcpy(ts_event->gesture_data, &pre_buf[8], + GOODIX_GESTURE_DATA_LEN); + } + /* read done */ + hw_ops->after_event_handler(cd); + + return 0; +} + +static int brl_after_event_handler(struct goodix_ts_core *cd) +{ + struct goodix_ts_hw_ops *hw_ops = cd->hw_ops; + struct goodix_ic_info_misc *misc = &cd->ic_info.misc; + u8 sync_clean = 0; + + if (cd->tools_ctrl_sync) + return 0; + return hw_ops->write(cd, misc->touch_data_addr, + &sync_clean, 1); +} + +static int brld_get_framedata(struct goodix_ts_core *cd, + struct ts_rawdata_info *info) +{ + int ret; + unsigned char val; + int retry = 20; + struct frame_head *frame_head; + unsigned char frame_buf[GOODIX_MAX_FRAMEDATA_LEN]; + unsigned char *cur_ptr; + unsigned int flag_addr = cd->ic_info.misc.frame_data_addr; + + /* clean touch event flag */ + val = 0; + ret = brl_write(cd, flag_addr, &val, 1); + if (ret < 0) { + ts_err("clean touch event failed, exit!"); + return ret; + } + + while (retry--) { + usleep_range(2000, 2100); + ret = brl_read(cd, flag_addr, &val, 1); + if (!ret && (val & GOODIX_TOUCH_EVENT)) + break; + } + if (retry < 0) { + ts_err("framedata is not ready val:0x%02x, exit!", val); + return -EINVAL; + } + + ret = brl_read(cd, flag_addr, frame_buf, GOODIX_MAX_FRAMEDATA_LEN); + if (ret < 0) { + ts_err("read frame data failed"); + return ret; + } + + if (checksum_cmp(frame_buf, cd->ic_info.misc.frame_data_head_len, + CHECKSUM_MODE_U8_LE)) { + ts_err("frame head checksum error"); + return -EINVAL; + } + + frame_head = (struct frame_head *)frame_buf; + if (checksum_cmp(frame_buf, frame_head->cur_frame_len, + CHECKSUM_MODE_U16_LE)) { + ts_err("frame body checksum error"); + return -EINVAL; + } + cur_ptr = frame_buf; + cur_ptr += cd->ic_info.misc.frame_data_head_len; + cur_ptr += cd->ic_info.misc.fw_attr_len; + cur_ptr += cd->ic_info.misc.fw_log_len; + memcpy((u8 *)(info->buff + info->used_size), cur_ptr + 8, + cd->ic_info.misc.mutual_struct_len - 8); + + return 0; +} + +static int brld_get_cap_data(struct goodix_ts_core *cd, + struct ts_rawdata_info *info) +{ + struct goodix_ts_cmd temp_cmd; + int tx = cd->ic_info.parm.drv_num; + int rx = cd->ic_info.parm.sen_num; + int size = tx * rx; + int ret; + + /* disable irq & close esd */ + brl_irq_enbale(cd, false); + goodix_ts_blocking_notify(NOTIFY_ESD_OFF, NULL); + + info->buff[0] = rx; + info->buff[1] = tx; + info->used_size = 2; + + temp_cmd.cmd = 0x90; + temp_cmd.data[0] = 0x81; + temp_cmd.len = 5; + ret = brl_send_cmd(cd, &temp_cmd); + if (ret < 0) { + ts_err("report rawdata failed, exit!"); + goto exit; + } + + ret = brld_get_framedata(cd, info); + if (ret < 0) { + ts_err("brld get rawdata failed"); + goto exit; + } + goodix_rotate_abcd2cbad(tx, rx, &info->buff[info->used_size]); + info->used_size += size; + + temp_cmd.cmd = 0x90; + temp_cmd.data[0] = 0x82; + temp_cmd.len = 5; + ret = brl_send_cmd(cd, &temp_cmd); + if (ret < 0) { + ts_err("report diffdata failed, exit!"); + goto exit; + } + + ret = brld_get_framedata(cd, info); + if (ret < 0) { + ts_err("brld get diffdata failed"); + goto exit; + } + goodix_rotate_abcd2cbad(tx, rx, &info->buff[info->used_size]); + info->used_size += size; + +exit: + temp_cmd.cmd = 0x90; + temp_cmd.data[0] = 0; + temp_cmd.len = 5; + brl_send_cmd(cd, &temp_cmd); + /* enable irq & esd */ + brl_irq_enbale(cd, true); + goodix_ts_blocking_notify(NOTIFY_ESD_ON, NULL); + return ret; +} + +#define GOODIX_CMD_RAWDATA 2 +#define GOODIX_CMD_COORD 0 +static int brl_get_capacitance_data(struct goodix_ts_core *cd, + struct ts_rawdata_info *info) +{ + int ret; + int retry = 20; + struct goodix_ts_cmd temp_cmd; + u32 flag_addr = cd->ic_info.misc.touch_data_addr; + u32 raw_addr = cd->ic_info.misc.mutual_rawdata_addr; + u32 diff_addr = cd->ic_info.misc.mutual_diffdata_addr; + int tx = cd->ic_info.parm.drv_num; + int rx = cd->ic_info.parm.sen_num; + int size = tx * rx; + u8 val; + + if (!info) { + ts_err("input null ptr"); + return -EIO; + } + + if (cd->bus->ic_type == IC_TYPE_BERLIN_D) + return brld_get_cap_data(cd, info); + + /* disable irq & close esd */ + brl_irq_enbale(cd, false); + goodix_ts_blocking_notify(NOTIFY_ESD_OFF, NULL); + + /* switch rawdata mode */ + temp_cmd.cmd = GOODIX_CMD_RAWDATA; + temp_cmd.len = 4; + ret = brl_send_cmd(cd, &temp_cmd); + if (ret < 0) { + ts_err("switch rawdata mode failed, exit!"); + goto exit; + } + + /* clean touch event flag */ + val = 0; + ret = brl_write(cd, flag_addr, &val, 1); + if (ret < 0) { + ts_err("clean touch event failed, exit!"); + goto exit; + } + + while (retry--) { + usleep_range(5000, 5100); + ret = brl_read(cd, flag_addr, &val, 1); + if (!ret && (val & GOODIX_TOUCH_EVENT)) + break; + } + if (retry < 0) { + ts_err("rawdata is not ready val:0x%02x, exit!", val); + goto exit; + } + + /* obtain rawdata & diff_rawdata */ + info->buff[0] = rx; + info->buff[1] = tx; + info->used_size = 2; + + ret = brl_read(cd, raw_addr, (u8 *)&info->buff[info->used_size], + size * sizeof(s16)); + if (ret < 0) { + ts_err("obtian raw_data failed, exit!"); + goto exit; + } + goodix_rotate_abcd2cbad(tx, rx, &info->buff[info->used_size]); + info->used_size += size; + + ret = brl_read(cd, diff_addr, (u8 *)&info->buff[info->used_size], + size * sizeof(s16)); + if (ret < 0) { + ts_err("obtian diff_data failed, exit!"); + goto exit; + } + goodix_rotate_abcd2cbad(tx, rx, &info->buff[info->used_size]); + info->used_size += size; + +exit: + /* switch coor mode */ + temp_cmd.cmd = GOODIX_CMD_COORD; + temp_cmd.len = 4; + brl_send_cmd(cd, &temp_cmd); + /* clean touch event flag */ + val = 0; + brl_write(cd, flag_addr, &val, 1); + /* enable irq & esd */ + brl_irq_enbale(cd, true); + goodix_ts_blocking_notify(NOTIFY_ESD_ON, NULL); + return ret; +} + +static struct goodix_ts_hw_ops brl_hw_ops = { + .power_on = brl_power_on, + .resume = brl_resume, + .suspend = brl_suspend, + .gesture = brl_gesture, + .reset = brl_reset, + .irq_enable = brl_irq_enbale, + .read = brl_read, + .write = brl_write, + .send_cmd = brl_send_cmd, + .send_config = brl_send_config, + .read_config = brl_read_config, + .read_version = brl_read_version, + .get_ic_info = brl_get_ic_info, + .esd_check = brl_esd_check, + .event_handler = brl_event_handler, + .after_event_handler = brl_after_event_handler, + .get_capacitance_data = brl_get_capacitance_data, +}; + +struct goodix_ts_hw_ops *goodix_get_hw_ops(void) +{ + return &brl_hw_ops; +} diff --git a/qcom/opensource/touch-drivers/goodix_berlin_driver/goodix_brl_i2c.c b/qcom/opensource/touch-drivers/goodix_berlin_driver/goodix_brl_i2c.c new file mode 100644 index 0000000000..d4aa1549c6 --- /dev/null +++ b/qcom/opensource/touch-drivers/goodix_berlin_driver/goodix_brl_i2c.c @@ -0,0 +1,272 @@ + /* + * Goodix Touchscreen Driver + * Copyright (C) 2020 - 2021 Goodix, Inc. + * + * 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 a reference + * to you, when you are integrating the GOODiX's CTP IC into your system, + * 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. + * + */ +#include +#include +#include +#include + +#include "goodix_ts_core.h" + +#define TS_DRIVER_NAME "gtx8_i2c" +#define I2C_MAX_TRANSFER_SIZE 256 +#define GOODIX_BUS_RETRY_TIMES 2 +#define GOODIX_REG_ADDR_SIZE 4 + +static struct platform_device *goodix_pdev; +struct goodix_bus_interface goodix_i2c_bus; + +static int goodix_i2c_read(struct device *dev, unsigned int reg, + unsigned char *data, unsigned int len) +{ + struct i2c_client *client = to_i2c_client(dev); + unsigned int transfer_length = 0; + unsigned int pos = 0, address = reg; + unsigned char get_buf[128], addr_buf[GOODIX_REG_ADDR_SIZE]; + int retry, r = 0; + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = !I2C_M_RD, + .buf = &addr_buf[0], + .len = GOODIX_REG_ADDR_SIZE, + }, { + .addr = client->addr, + .flags = I2C_M_RD, + } + }; + + if (likely(len < sizeof(get_buf))) { + /* code optimize, use stack memory */ + msgs[1].buf = &get_buf[0]; + } else { + msgs[1].buf = kzalloc(len, GFP_KERNEL); + if (msgs[1].buf == NULL) + return -ENOMEM; + } + + while (pos != len) { + if (unlikely(len - pos > I2C_MAX_TRANSFER_SIZE)) + transfer_length = I2C_MAX_TRANSFER_SIZE; + else + transfer_length = len - pos; + + msgs[0].buf[0] = (address >> 24) & 0xFF; + msgs[0].buf[1] = (address >> 16) & 0xFF; + msgs[0].buf[2] = (address >> 8) & 0xFF; + msgs[0].buf[3] = address & 0xFF; + msgs[1].len = transfer_length; + + for (retry = 0; retry < GOODIX_BUS_RETRY_TIMES; retry++) { + if (likely(i2c_transfer(client->adapter, msgs, 2) == 2)) { + memcpy(&data[pos], msgs[1].buf, transfer_length); + pos += transfer_length; + address += transfer_length; + break; + } + ts_info("I2c read retry[%d]:0x%x", retry + 1, reg); + usleep_range(2000, 2100); + } + if (unlikely(retry == GOODIX_BUS_RETRY_TIMES)) { + ts_err("I2c read failed,dev:%02x,reg:%04x,size:%u", + client->addr, reg, len); + r = -EAGAIN; + goto read_exit; + } + } + +read_exit: + if (unlikely(len >= sizeof(get_buf))) + kfree(msgs[1].buf); + return r; +} + +static int goodix_i2c_write(struct device *dev, unsigned int reg, + unsigned char *data, unsigned int len) +{ + struct i2c_client *client = to_i2c_client(dev); + unsigned int pos = 0, transfer_length = 0; + unsigned int address = reg; + unsigned char put_buf[128]; + int retry, r = 0; + struct i2c_msg msg = { + .addr = client->addr, + .flags = !I2C_M_RD, + }; + + if (likely(len + GOODIX_REG_ADDR_SIZE < sizeof(put_buf))) { + /* code optimize,use stack memory*/ + msg.buf = &put_buf[0]; + } else { + msg.buf = kmalloc(len + GOODIX_REG_ADDR_SIZE, GFP_KERNEL); + if (msg.buf == NULL) + return -ENOMEM; + } + + while (pos != len) { + if (unlikely(len - pos > I2C_MAX_TRANSFER_SIZE - + GOODIX_REG_ADDR_SIZE)) + transfer_length = I2C_MAX_TRANSFER_SIZE - + GOODIX_REG_ADDR_SIZE; + else + transfer_length = len - pos; + msg.buf[0] = (address >> 24) & 0xFF; + msg.buf[1] = (address >> 16) & 0xFF; + msg.buf[2] = (address >> 8) & 0xFF; + msg.buf[3] = address & 0xFF; + + msg.len = transfer_length + GOODIX_REG_ADDR_SIZE; + memcpy(&msg.buf[GOODIX_REG_ADDR_SIZE], + &data[pos], transfer_length); + + for (retry = 0; retry < GOODIX_BUS_RETRY_TIMES; retry++) { + if (likely(i2c_transfer(client->adapter, + &msg, 1) == 1)) { + pos += transfer_length; + address += transfer_length; + break; + } + ts_debug("I2c write retry[%d]", retry + 1); + msleep(20); + } + if (unlikely(retry == GOODIX_BUS_RETRY_TIMES)) { + ts_err("I2c write failed,dev:%02x,reg:%04x,size:%u", + client->addr, reg, len); + r = -EAGAIN; + goto write_exit; + } + } + +write_exit: + if (likely(len + GOODIX_REG_ADDR_SIZE >= sizeof(put_buf))) + kfree(msg.buf); + return r; +} + +static void goodix_pdev_release(struct device *dev) +{ + ts_info("goodix pdev released"); + kfree(goodix_pdev); +} + +static int goodix_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *dev_id) +{ + int ret = 0; + + ts_info("goodix i2c probe in"); + +#ifndef CONFIG_ARCH_QTI_VM + ret = i2c_check_functionality(client->adapter, I2C_FUNC_I2C); + if (!ret) + return -EIO; +#endif + + /* get ic type */ + ret = goodix_get_ic_type(client->dev.of_node); + if (ret < 0) + return ret; + + goodix_i2c_bus.ic_type = ret; + goodix_i2c_bus.bus_type = GOODIX_BUS_TYPE_I2C; + goodix_i2c_bus.dev = &client->dev; + goodix_i2c_bus.read = goodix_i2c_read; + goodix_i2c_bus.write = goodix_i2c_write; + /* ts core device */ + goodix_pdev = kzalloc(sizeof(struct platform_device), GFP_KERNEL); + if (!goodix_pdev) + return -ENOMEM; + + goodix_pdev->name = GOODIX_CORE_DRIVER_NAME; + goodix_pdev->id = 0; + goodix_pdev->num_resources = 0; + /* + * you can find this platform dev in + * /sys/devices/platform/goodix_ts.0 + * goodix_pdev->dev.parent = &client->dev; + */ + goodix_pdev->dev.platform_data = &goodix_i2c_bus; + goodix_pdev->dev.release = goodix_pdev_release; + + /* register platform device, then the goodix_ts_core + * module will probe the touch device. + */ + ret = platform_device_register(goodix_pdev); + if (ret) { + ts_err("failed register goodix platform device, %d", ret); + goto err_pdev; + } + ts_info("i2c probe out"); + return ret; + +err_pdev: + kfree(goodix_pdev); + goodix_pdev = NULL; + ts_info("i2c probe out, %d", ret); + return ret; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0)) +static void goodix_i2c_remove(struct i2c_client *client) +{ + platform_device_unregister(goodix_pdev); +} +#else +static int goodix_i2c_remove(struct i2c_client *client) +{ + platform_device_unregister(goodix_pdev); + return 0; +} +#endif + +#ifdef CONFIG_OF +static const struct of_device_id i2c_matchs[] = { + {.compatible = "goodix,gt9897",}, + {.compatible = "goodix,gt9966",}, + {.compatible = "goodix,gt9916",}, + {}, +}; +MODULE_DEVICE_TABLE(of, i2c_matchs); +#endif + +static const struct i2c_device_id i2c_id_table[] = { + {TS_DRIVER_NAME, 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, i2c_id_table); + +static struct i2c_driver goodix_i2c_driver = { + .driver = { + .name = TS_DRIVER_NAME, + //.owner = THIS_MODULE, + .of_match_table = of_match_ptr(i2c_matchs), + }, + .probe = goodix_i2c_probe, + .remove = goodix_i2c_remove, + .id_table = i2c_id_table, +}; + +int goodix_i2c_bus_init(void) +{ + ts_info("Goodix i2c driver init"); + return i2c_add_driver(&goodix_i2c_driver); +} + +void goodix_i2c_bus_exit(void) +{ + ts_info("Goodix i2c driver exit"); + i2c_del_driver(&goodix_i2c_driver); +} diff --git a/qcom/opensource/touch-drivers/goodix_berlin_driver/goodix_brl_spi.c b/qcom/opensource/touch-drivers/goodix_berlin_driver/goodix_brl_spi.c new file mode 100644 index 0000000000..d246524a09 --- /dev/null +++ b/qcom/opensource/touch-drivers/goodix_berlin_driver/goodix_brl_spi.c @@ -0,0 +1,317 @@ + /* + * Goodix Touchscreen Driver + * Copyright (C) 2020 - 2021 Goodix, Inc. + * + * 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 a reference + * to you, when you are integrating the GOODiX's CTP IC into your system, + * 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. + * + */ +#include +#include +#include +#include + +#include "goodix_ts_core.h" +#define TS_DRIVER_NAME "gtx8_spi" + +#define SPI_TRANS_PREFIX_LEN 1 +#define REGISTER_WIDTH 4 +#define SPI_READ_DUMMY_LEN 4 +#define SPI_READ_PREFIX_LEN \ + (SPI_TRANS_PREFIX_LEN + REGISTER_WIDTH + SPI_READ_DUMMY_LEN) +#define SPI_WRITE_PREFIX_LEN (SPI_TRANS_PREFIX_LEN + REGISTER_WIDTH) + +#define SPI_WRITE_FLAG 0xF0 +#define SPI_READ_FLAG 0xF1 + +static struct platform_device *goodix_pdev; +struct goodix_bus_interface goodix_spi_bus[MAX_SUPPORTED_TOUCH_PANELS]; + +/** + * goodix_spi_read_bra- read device register through spi bus + * @dev: pointer to device data + * @addr: register address + * @data: read buffer + * @len: bytes to read + * return: 0 - read ok, < 0 - spi transter error + */ +static int goodix_spi_read_bra(struct device *dev, unsigned int addr, + unsigned char *data, unsigned int len) +{ + struct spi_device *spi = to_spi_device(dev); + u8 *rx_buf = NULL; + u8 *tx_buf = NULL; + struct spi_transfer xfers; + struct spi_message spi_msg; + int ret = 0; + + rx_buf = kzalloc(SPI_READ_PREFIX_LEN + len, GFP_KERNEL); + tx_buf = kzalloc(SPI_READ_PREFIX_LEN + len, GFP_KERNEL); + if (!rx_buf || !tx_buf) { + ts_err("alloc tx/rx_buf failed, size:%d", + SPI_READ_PREFIX_LEN + len); + return -ENOMEM; + } + + spi_message_init(&spi_msg); + memset(&xfers, 0, sizeof(xfers)); + + /*spi_read tx_buf format: 0xF1 + addr(4bytes) + data*/ + tx_buf[0] = SPI_READ_FLAG; + tx_buf[1] = (addr >> 24) & 0xFF; + tx_buf[2] = (addr >> 16) & 0xFF; + tx_buf[3] = (addr >> 8) & 0xFF; + tx_buf[4] = addr & 0xFF; + tx_buf[5] = 0xFF; + tx_buf[6] = 0xFF; + tx_buf[7] = 0xFF; + tx_buf[8] = 0xFF; + + xfers.tx_buf = tx_buf; + xfers.rx_buf = rx_buf; + xfers.len = SPI_READ_PREFIX_LEN + len; + xfers.cs_change = 0; + spi_message_add_tail(&xfers, &spi_msg); + ret = spi_sync(spi, &spi_msg); + if (ret < 0) { + ts_err("spi transfer error:%d", ret); + goto exit; + } + memcpy(data, &rx_buf[SPI_READ_PREFIX_LEN], len); + +exit: + kfree(rx_buf); + kfree(tx_buf); + return ret; +} + +static int goodix_spi_read(struct device *dev, unsigned int addr, + unsigned char *data, unsigned int len) +{ + struct spi_device *spi = to_spi_device(dev); + u8 *rx_buf = NULL; + u8 *tx_buf = NULL; + struct spi_transfer xfers; + struct spi_message spi_msg; + int ret = 0; + + rx_buf = kzalloc(SPI_READ_PREFIX_LEN - 1 + len, GFP_KERNEL); + tx_buf = kzalloc(SPI_READ_PREFIX_LEN - 1 + len, GFP_KERNEL); + if (!rx_buf || !tx_buf) { + ts_err("alloc tx/rx_buf failed, size:%d", + SPI_READ_PREFIX_LEN - 1 + len); + return -ENOMEM; + } + + spi_message_init(&spi_msg); + memset(&xfers, 0, sizeof(xfers)); + + /*spi_read tx_buf format: 0xF1 + addr(4bytes) + data*/ + tx_buf[0] = SPI_READ_FLAG; + tx_buf[1] = (addr >> 24) & 0xFF; + tx_buf[2] = (addr >> 16) & 0xFF; + tx_buf[3] = (addr >> 8) & 0xFF; + tx_buf[4] = addr & 0xFF; + tx_buf[5] = 0xFF; + tx_buf[6] = 0xFF; + tx_buf[7] = 0xFF; + + xfers.tx_buf = tx_buf; + xfers.rx_buf = rx_buf; + xfers.len = SPI_READ_PREFIX_LEN - 1 + len; + xfers.cs_change = 0; + spi_message_add_tail(&xfers, &spi_msg); + ret = spi_sync(spi, &spi_msg); + if (ret < 0) { + ts_err("spi transfer error:%d", ret); + goto exit; + } + memcpy(data, &rx_buf[SPI_READ_PREFIX_LEN - 1], len); + +exit: + kfree(rx_buf); + kfree(tx_buf); + return ret; +} + +/** + * goodix_spi_write- write device register through spi bus + * @dev: pointer to device data + * @addr: register address + * @data: write buffer + * @len: bytes to write + * return: 0 - write ok; < 0 - spi transter error. + */ +static int goodix_spi_write(struct device *dev, unsigned int addr, + unsigned char *data, unsigned int len) +{ + struct spi_device *spi = to_spi_device(dev); + u8 *tx_buf = NULL; + struct spi_transfer xfers; + struct spi_message spi_msg; + int ret = 0; + + tx_buf = kzalloc(SPI_WRITE_PREFIX_LEN + len, GFP_KERNEL); + if (!tx_buf) + return -ENOMEM; + + spi_message_init(&spi_msg); + memset(&xfers, 0, sizeof(xfers)); + + tx_buf[0] = SPI_WRITE_FLAG; + tx_buf[1] = (addr >> 24) & 0xFF; + tx_buf[2] = (addr >> 16) & 0xFF; + tx_buf[3] = (addr >> 8) & 0xFF; + tx_buf[4] = addr & 0xFF; + memcpy(&tx_buf[SPI_WRITE_PREFIX_LEN], data, len); + xfers.tx_buf = tx_buf; + xfers.len = SPI_WRITE_PREFIX_LEN + len; + xfers.cs_change = 0; + spi_message_add_tail(&xfers, &spi_msg); + ret = spi_sync(spi, &spi_msg); + if (ret < 0) + ts_err("spi transfer error:%d", ret); + + kfree(tx_buf); + return ret; +} + +static void goodix_pdev_release(struct device *dev) +{ + ts_info("goodix pdev released"); + kfree(goodix_pdev); +} + +static int goodix_spi_probe(struct spi_device *spi) +{ + int ret = 0, idx; + + ts_info("goodix spi probe in"); + + /* init spi_device */ + spi->mode = SPI_MODE_0; + spi->bits_per_word = 8; + + ret = spi_setup(spi); + if (ret) { + ts_err("failed set spi mode, %d", ret); + return ret; + } + + /* get ic type */ + ret = goodix_get_ic_type(spi->dev.of_node); + if (ret < 0) + return ret; + + idx = goodix_get_touch_type(spi->dev.of_node); + if (idx < 0 || idx >= MAX_SUPPORTED_TOUCH_PANELS) { + ts_err("unsupported touch type idx:%d", idx); + return -ENODEV; + } + + goodix_spi_bus[idx].ic_type = ret; + goodix_spi_bus[idx].bus_type = GOODIX_BUS_TYPE_SPI; + goodix_spi_bus[idx].dev = &spi->dev; + if (goodix_spi_bus[idx].ic_type == IC_TYPE_BERLIN_A) + goodix_spi_bus[idx].read = goodix_spi_read_bra; + else + goodix_spi_bus[idx].read = goodix_spi_read; + goodix_spi_bus[idx].write = goodix_spi_write; + /* ts core device */ + goodix_pdev = kzalloc(sizeof(struct platform_device), GFP_KERNEL); + if (!goodix_pdev) + return -ENOMEM; + + if (idx) + goodix_pdev->name = GOODIX_CORE_DEVICE_2_NAME; + else + goodix_pdev->name = GOODIX_CORE_DEVICE_NAME; + + goodix_pdev->id = 0; + goodix_pdev->num_resources = 0; + /* + * you can find this platform dev in + * /sys/devices/platfrom/goodix_ts.0 + * goodix_pdev->dev.parent = &client->dev; + */ + goodix_pdev->dev.platform_data = &goodix_spi_bus[idx]; + goodix_pdev->dev.release = goodix_pdev_release; + + /* + * register platform device, then the goodix_ts_core + * module will probe the touch deivce. + */ + ret = platform_device_register(goodix_pdev); + if (ret) { + ts_err("failed register goodix platform device, %d", ret); + goto err_pdev; + } + ts_info("spi probe out"); + return 0; + +err_pdev: + kfree(goodix_pdev); + goodix_pdev = NULL; + ts_info("spi probe out, %d", ret); + return ret; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)) + static void goodix_spi_remove(struct spi_device *spi) + { + platform_device_unregister(goodix_pdev); + } +#else + static int goodix_spi_remove(struct spi_device *spi) + { + platform_device_unregister(goodix_pdev); + return 0; + } +#endif + +#ifdef CONFIG_OF +static const struct of_device_id spi_matchs[] = { + {.compatible = "goodix,gt9897S",}, + {.compatible = "goodix,gt9897T",}, + {.compatible = "goodix,gt9966S",}, + {.compatible = "goodix,gt9916S",}, + {.compatible = "goodix,gt9916S2",}, + {}, +}; +#endif + +static const struct spi_device_id spi_id_table[] = { + {TS_DRIVER_NAME, 0}, + {}, +}; + +static struct spi_driver goodix_spi_driver = { + .driver = { + .name = TS_DRIVER_NAME, + //.owner = THIS_MODULE, + .of_match_table = spi_matchs, + }, + .id_table = spi_id_table, + .probe = goodix_spi_probe, + .remove = goodix_spi_remove, +}; + +int goodix_spi_bus_init(void) +{ + ts_info("Goodix spi driver init"); + return spi_register_driver(&goodix_spi_driver); +} + +void goodix_spi_bus_exit(void) +{ + ts_info("Goodix spi driver exit"); + spi_unregister_driver(&goodix_spi_driver); +} diff --git a/qcom/opensource/touch-drivers/goodix_berlin_driver/goodix_cfg_bin.c b/qcom/opensource/touch-drivers/goodix_berlin_driver/goodix_cfg_bin.c new file mode 100644 index 0000000000..99670f6e58 --- /dev/null +++ b/qcom/opensource/touch-drivers/goodix_berlin_driver/goodix_cfg_bin.c @@ -0,0 +1,343 @@ + /* + * Goodix Touchscreen Driver + * Copyright (C) 2020 - 2021 Goodix, Inc. + * + * 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 a reference + * to you, when you are integrating the GOODiX's CTP IC into your system, + * 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. + * + */ +#include "goodix_ts_core.h" + +#define TS_BIN_VERSION_START_INDEX 5 +#define TS_BIN_VERSION_LEN 4 +#define TS_CFG_BIN_HEAD_RESERVED_LEN 6 +#define TS_CFG_OFFSET_LEN 2 +#define TS_IC_TYPE_NAME_MAX_LEN 15 +#define TS_CFG_BIN_HEAD_LEN \ + (sizeof(struct goodix_cfg_bin_head) + \ + TS_CFG_BIN_HEAD_RESERVED_LEN) +#define TS_PKG_CONST_INFO_LEN \ + (sizeof(struct goodix_cfg_pkg_const_info)) +#define TS_PKG_REG_INFO_LEN \ + (sizeof(struct goodix_cfg_pkg_reg_info)) +#define TS_PKG_HEAD_LEN \ + (TS_PKG_CONST_INFO_LEN + TS_PKG_REG_INFO_LEN) + +/*cfg block definitin*/ +#define TS_CFG_BLOCK_PID_LEN 8 +#define TS_CFG_BLOCK_VID_LEN 8 +#define TS_CFG_BLOCK_FW_MASK_LEN 9 +#define TS_CFG_BLOCK_FW_PATCH_LEN 4 +#define TS_CFG_BLOCK_RESERVED_LEN 9 + +#define TS_NORMAL_CFG 0x01 +#define TS_HIGH_SENSE_CFG 0x03 +#define TS_RQST_FW_RETRY_TIMES 2 + +#pragma pack(1) +struct goodix_cfg_pkg_reg { + u16 addr; + u8 reserved1; + u8 reserved2; +}; + +struct goodix_cfg_pkg_const_info { + u32 pkg_len; + u8 ic_type[TS_IC_TYPE_NAME_MAX_LEN]; + u8 cfg_type; + u8 sensor_id; + u8 hw_pid[TS_CFG_BLOCK_PID_LEN]; + u8 hw_vid[TS_CFG_BLOCK_VID_LEN]; + u8 fw_mask[TS_CFG_BLOCK_FW_MASK_LEN]; + u8 fw_patch[TS_CFG_BLOCK_FW_PATCH_LEN]; + u16 x_res_offset; + u16 y_res_offset; + u16 trigger_offset; +}; + +struct goodix_cfg_pkg_reg_info { + struct goodix_cfg_pkg_reg cfg_send_flag; + struct goodix_cfg_pkg_reg version_base; + struct goodix_cfg_pkg_reg pid; + struct goodix_cfg_pkg_reg vid; + struct goodix_cfg_pkg_reg sensor_id; + struct goodix_cfg_pkg_reg fw_mask; + struct goodix_cfg_pkg_reg fw_status; + struct goodix_cfg_pkg_reg cfg_addr; + struct goodix_cfg_pkg_reg esd; + struct goodix_cfg_pkg_reg command; + struct goodix_cfg_pkg_reg coor; + struct goodix_cfg_pkg_reg gesture; + struct goodix_cfg_pkg_reg fw_request; + struct goodix_cfg_pkg_reg proximity; + u8 reserved[TS_CFG_BLOCK_RESERVED_LEN]; +}; + +struct goodix_cfg_bin_head { + u32 bin_len; + u8 checksum; + u8 bin_version[TS_BIN_VERSION_LEN]; + u8 pkg_num; +}; + +#pragma pack() + +struct goodix_cfg_package { + struct goodix_cfg_pkg_const_info cnst_info; + struct goodix_cfg_pkg_reg_info reg_info; + const u8 *cfg; + u32 pkg_len; +}; + +struct goodix_cfg_bin { + unsigned char *bin_data; + unsigned int bin_data_len; + struct goodix_cfg_bin_head head; + struct goodix_cfg_package *cfg_pkgs; +}; + +static int goodix_read_cfg_bin(struct device *dev, const char *cfg_name, + struct goodix_cfg_bin *cfg_bin) +{ + const struct firmware *firmware = NULL; + int ret; + int retry = GOODIX_RETRY_3; + + ts_info("cfg_bin_name:%s", cfg_name); + + while (retry--) { + ret = request_firmware(&firmware, cfg_name, dev); + if (!ret) + break; + ts_info("get cfg bin retry:[%d]", GOODIX_RETRY_3 - retry); + msleep(200); + } + if (retry < 0) { + ts_err("failed get cfg bin[%s] error:%d", cfg_name, ret); + return ret; + } + + if (firmware->size <= 0) { + ts_err("request_firmware, cfg_bin length ERROR,len:%zu", + firmware->size); + ret = -EINVAL; + goto exit; + } + + cfg_bin->bin_data_len = firmware->size; + /* allocate memory for cfg_bin->bin_data */ + cfg_bin->bin_data = kzalloc(cfg_bin->bin_data_len, GFP_KERNEL); + if (!cfg_bin->bin_data) { + ret = -ENOMEM; + goto exit; + } + memcpy(cfg_bin->bin_data, firmware->data, cfg_bin->bin_data_len); + +exit: + release_firmware(firmware); + return ret; +} + +static int goodix_parse_cfg_bin(struct goodix_cfg_bin *cfg_bin) +{ + u16 offset1, offset2; + u8 checksum; + int i; + + /* copy cfg_bin head info */ + if (cfg_bin->bin_data_len < sizeof(struct goodix_cfg_bin_head)) { + ts_err("Invalid cfg_bin size:%d", cfg_bin->bin_data_len); + return -EINVAL; + } + + memcpy(&cfg_bin->head, cfg_bin->bin_data, + sizeof(struct goodix_cfg_bin_head)); + cfg_bin->head.bin_len = le32_to_cpu(cfg_bin->head.bin_len); + + /*check length*/ + if (cfg_bin->bin_data_len != cfg_bin->head.bin_len) { + ts_err("cfg_bin len check failed,%d != %d", + cfg_bin->head.bin_len, cfg_bin->bin_data_len); + return -EINVAL; + } + + /*check cfg_bin valid*/ + checksum = 0; + for (i = TS_BIN_VERSION_START_INDEX; i < cfg_bin->bin_data_len; i++) + checksum += cfg_bin->bin_data[i]; + + if (checksum != cfg_bin->head.checksum) { + ts_err("cfg_bin checksum check filed 0x%02x != 0x%02x", + cfg_bin->head.checksum, checksum); + return -EINVAL; + } + + /*allocate memory for cfg packages*/ + cfg_bin->cfg_pkgs = kzalloc(sizeof(struct goodix_cfg_package) * + cfg_bin->head.pkg_num, GFP_KERNEL); + if (!cfg_bin->cfg_pkgs) + return -ENOMEM; + + /*get cfg_pkg's info*/ + for (i = 0; i < cfg_bin->head.pkg_num; i++) { + /*get cfg pkg length*/ + if (i == cfg_bin->head.pkg_num - 1) { + offset1 = cfg_bin->bin_data[TS_CFG_BIN_HEAD_LEN + + i * TS_CFG_OFFSET_LEN] + + (cfg_bin->bin_data[TS_CFG_BIN_HEAD_LEN + + i * TS_CFG_OFFSET_LEN + 1] << 8); + + cfg_bin->cfg_pkgs[i].pkg_len = + cfg_bin->bin_data_len - offset1; + } else { + offset1 = cfg_bin->bin_data[TS_CFG_BIN_HEAD_LEN + + i * TS_CFG_OFFSET_LEN] + + (cfg_bin->bin_data[TS_CFG_BIN_HEAD_LEN + + i * TS_CFG_OFFSET_LEN + 1] << 8); + + offset2 = cfg_bin->bin_data[TS_CFG_BIN_HEAD_LEN + + i * TS_CFG_OFFSET_LEN + 2] + + (cfg_bin->bin_data[TS_CFG_BIN_HEAD_LEN + + i * TS_CFG_OFFSET_LEN + 3] << 8); + + if (offset2 <= offset1) { + ts_err("offset error,pkg:%d, offset1:%d, offset2:%d", + i, offset1, offset2); + goto exit; + } + + cfg_bin->cfg_pkgs[i].pkg_len = offset2 - offset1; + } + /*get cfg pkg head*/ + memcpy(&cfg_bin->cfg_pkgs[i].cnst_info, + &cfg_bin->bin_data[offset1], TS_PKG_CONST_INFO_LEN); + memcpy(&cfg_bin->cfg_pkgs[i].reg_info, + &cfg_bin->bin_data[offset1 + TS_PKG_CONST_INFO_LEN], + TS_PKG_REG_INFO_LEN); + + /*get configuration data*/ + cfg_bin->cfg_pkgs[i].cfg = + &cfg_bin->bin_data[offset1 + TS_PKG_HEAD_LEN]; + } + + /*debug, print pkg information*/ + ts_info("Driver bin info: ver %s, len %d, pkgs %d", + cfg_bin->head.bin_version, + cfg_bin->head.bin_len, + cfg_bin->head.pkg_num); + + return 0; +exit: + kfree(cfg_bin->cfg_pkgs); + return -EINVAL; +} + +static int goodix_get_reg_and_cfg(struct goodix_ts_core *cd, u8 sensor_id, + struct goodix_cfg_bin *cfg_bin) +{ + int i; + u8 cfg_type; + u32 cfg_len; + struct goodix_cfg_package *cfg_pkg; + + if (!cfg_bin->head.pkg_num || !cfg_bin->cfg_pkgs) { + ts_err("there is none cfg package, pkg_num:%d", + cfg_bin->head.pkg_num); + return -EINVAL; + } + + /* find cfg packages with same sensor_id */ + for (i = 0; i < cfg_bin->head.pkg_num; i++) { + cfg_pkg = &cfg_bin->cfg_pkgs[i]; + if (sensor_id != cfg_pkg->cnst_info.sensor_id) { + ts_info("pkg:%d, sensor id contrast FAILED, bin %d != %d", + i, cfg_pkg->cnst_info.sensor_id, sensor_id); + continue; + } + cfg_type = cfg_pkg->cnst_info.cfg_type; + if (cfg_type >= GOODIX_MAX_CONFIG_GROUP) { + ts_err("usupported config type %d", + cfg_pkg->cnst_info.cfg_type); + goto err_out; + } + + cfg_len = cfg_pkg->pkg_len - TS_PKG_CONST_INFO_LEN - + TS_PKG_REG_INFO_LEN; + if (cfg_len > GOODIX_CFG_MAX_SIZE) { + ts_err("config len exceed limit %d > %d", + cfg_len, GOODIX_CFG_MAX_SIZE); + goto err_out; + } + if (cd->ic_configs[cfg_type]) { + ts_err("found same type config twice for sensor id %d, skiped", + sensor_id); + continue; + } + cd->ic_configs[cfg_type] = + kzalloc(sizeof(struct goodix_ic_config), + GFP_KERNEL); + if (!cd->ic_configs[cfg_type]) + goto err_out; + cd->ic_configs[cfg_type]->len = cfg_len; + memcpy(cd->ic_configs[cfg_type]->data, cfg_pkg->cfg, cfg_len); + ts_info("get config type %d, len %d, for sensor id %d", + cfg_type, cfg_len, sensor_id); + } + return 0; + +err_out: + /* parse config enter error, release memory alloced */ + for (i = 0; i < GOODIX_MAX_CONFIG_GROUP; i++) { + kfree(cd->ic_configs[i]); + cd->ic_configs[i] = NULL; + } + return -EINVAL; +} + +static int goodix_get_config_data(struct goodix_ts_core *cd, u8 sensor_id) +{ + struct goodix_cfg_bin cfg_bin = {0}; + char *cfg_name = cd->board_data.cfg_bin_name; + int ret; + + /*get cfg_bin from file system*/ + ret = goodix_read_cfg_bin(&cd->pdev->dev, cfg_name, &cfg_bin); + if (ret) { + ts_err("failed get valid config bin data"); + return ret; + } + + /*parse cfg bin*/ + ret = goodix_parse_cfg_bin(&cfg_bin); + if (ret) { + ts_err("failed parse cfg bin"); + goto err_out; + } + + /*get register address and configuration from cfg bin*/ + ret = goodix_get_reg_and_cfg(cd, sensor_id, &cfg_bin); + if (!ret) + ts_info("success get reg and cfg info from cfg bin"); + else + ts_err("failed get cfg and reg info, update fw then retry"); + + kfree(cfg_bin.cfg_pkgs); +err_out: + kfree(cfg_bin.bin_data); + return ret; +} + +int goodix_get_config_proc(struct goodix_ts_core *cd) +{ + return goodix_get_config_data(cd, cd->fw_version.sensor_id); +} + + diff --git a/qcom/opensource/touch-drivers/goodix_berlin_driver/goodix_ts_core.c b/qcom/opensource/touch-drivers/goodix_berlin_driver/goodix_ts_core.c new file mode 100644 index 0000000000..5783f0b4f9 --- /dev/null +++ b/qcom/opensource/touch-drivers/goodix_berlin_driver/goodix_ts_core.c @@ -0,0 +1,2710 @@ + /* + * Goodix Touchscreen Driver + * Copyright (C) 2020 - 2021 Goodix, Inc. + * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be a reference + * to you, when you are integrating the GOODiX's CTP IC into your system, + * 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. + * + */ +#include +#include +#include +#include +#include +#include + +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 38) +#include +#define INPUT_TYPE_B_PROTOCOL +#endif + +#include "goodix_ts_core.h" + +#define GOODIX_DEFAULT_CFG_NAME "goodix_cfg_group.cfg" +#define GOOIDX_INPUT_PHYS "goodix_ts/input0" + +#if defined(CONFIG_DRM) +static struct drm_panel *active_panel; +static void goodix_panel_notifier_callback(enum panel_event_notifier_tag tag, + struct panel_event_notification *event, void *client_data); +static irqreturn_t goodix_irq_handler(int irq, void *data); + +static void goodix_register_for_panel_events(struct device_node *dp, + struct goodix_ts_core *cd) +{ + void *cookie; + + cookie = panel_event_notifier_register(PANEL_EVENT_NOTIFICATION_PRIMARY, + PANEL_EVENT_NOTIFIER_CLIENT_PRIMARY_TOUCH, active_panel, + &goodix_panel_notifier_callback, cd); + if (!cookie) { + pr_err("Failed to register for panel events\n"); + return; + } + + ts_debug("registered for panel notifications panel: 0x%x\n", + active_panel); + + cd->notifier_cookie = cookie; +} + +#endif + +struct goodix_module goodix_modules; +int core_module_prob_sate = CORE_MODULE_UNPROBED; +struct mutex goodix_later_init_tmutex; + +int goodix_ts_esd_init(struct goodix_ts_core *cd); + +static int goodix_send_ic_config(struct goodix_ts_core *cd, int type); +/** + * __do_register_ext_module - register external module + * to register into touch core modules structure + * return 0 on success, otherwise return < 0 + */ +static int __do_register_ext_module(struct goodix_ext_module *module) +{ + struct goodix_ext_module *ext_module, *next; + struct list_head *insert_point = &goodix_modules.head; + + /* prority level *must* be set */ + if (module->priority == EXTMOD_PRIO_RESERVED) { + ts_err("Priority of module [%s] needs to be set", + module->name); + return -EINVAL; + } + mutex_lock(&goodix_modules.mutex); + /* find insert point for the specified priority */ + if (!list_empty(&goodix_modules.head)) { + list_for_each_entry_safe(ext_module, next, + &goodix_modules.head, list) { + if (ext_module == module) { + ts_info("Module [%s] already exists", + module->name); + mutex_unlock(&goodix_modules.mutex); + return 0; + } + } + + /* smaller priority value with higher priority level */ + list_for_each_entry_safe(ext_module, next, + &goodix_modules.head, list) { + if (ext_module->priority >= module->priority) { + insert_point = &ext_module->list; + break; + } + } + } + + if (module->funcs && module->funcs->init) { + if (module->funcs->init(goodix_modules.core_data, + module) < 0) { + ts_err("Module [%s] init error", + module->name ? module->name : " "); + mutex_unlock(&goodix_modules.mutex); + return -EFAULT; + } + } + + list_add(&module->list, insert_point->prev); + mutex_unlock(&goodix_modules.mutex); + + ts_info("Module [%s] registered,priority:%u", module->name, + module->priority); + return 0; +} + +static void goodix_register_ext_module_work(struct work_struct *work) +{ + struct goodix_ext_module *module = + container_of(work, struct goodix_ext_module, work); + + ts_info("module register work IN"); + + /* driver probe failed */ + if (core_module_prob_sate != CORE_MODULE_PROB_SUCCESS) { + ts_err("Can't register ext_module core error"); + return; + } + + if (__do_register_ext_module(module)) + ts_err("failed register module: %s", module->name); + else + ts_info("success register module: %s", module->name); +} + +static void goodix_core_module_init(void) +{ + if (goodix_modules.initilized) + return; + goodix_modules.initilized = true; + INIT_LIST_HEAD(&goodix_modules.head); + mutex_init(&goodix_modules.mutex); +} + +/** + * goodix_register_ext_module - interface for register external module + * to the core. This will create a workqueue to finish the real register + * work and return immediately. The user need to check the final result + * to make sure registe is success or fail. + * + * @module: pointer to external module to be register + * return: 0 ok, <0 failed + */ +int goodix_register_ext_module(struct goodix_ext_module *module) +{ + if (!module) + return -EINVAL; + + ts_info("IN"); + + goodix_core_module_init(); + INIT_WORK(&module->work, goodix_register_ext_module_work); + schedule_work(&module->work); + + ts_info("OUT"); + return 0; +} + +/** + * goodix_register_ext_module_no_wait + * return: 0 ok, <0 failed + */ +int goodix_register_ext_module_no_wait(struct goodix_ext_module *module) +{ + if (!module) + return -EINVAL; + + ts_info("IN"); + goodix_core_module_init(); + /* driver probe failed */ + if (core_module_prob_sate != CORE_MODULE_PROB_SUCCESS) { + ts_err("Can't register ext_module core error"); + return -EINVAL; + } + return __do_register_ext_module(module); +} + +/** + * goodix_unregister_ext_module - interface for external module + * to unregister external modules + * + * @module: pointer to external module + * return: 0 ok, <0 failed + */ +int goodix_unregister_ext_module(struct goodix_ext_module *module) +{ + struct goodix_ext_module *ext_module, *next; + bool found = false; + + if (!module) + return -EINVAL; + + if (!goodix_modules.initilized) + return -EINVAL; + + if (!goodix_modules.core_data) + return -ENODEV; + + mutex_lock(&goodix_modules.mutex); + if (!list_empty(&goodix_modules.head)) { + list_for_each_entry_safe(ext_module, next, + &goodix_modules.head, list) { + if (ext_module == module) { + found = true; + break; + } + } + } else { + mutex_unlock(&goodix_modules.mutex); + return 0; + } + + if (!found) { + ts_debug("Module [%s] never registed", + module->name); + mutex_unlock(&goodix_modules.mutex); + return 0; + } + + list_del(&module->list); + mutex_unlock(&goodix_modules.mutex); + + if (module->funcs && module->funcs->exit) + module->funcs->exit(goodix_modules.core_data, module); + + ts_info("Moudle [%s] unregistered", + module->name ? module->name : " "); + return 0; +} + +static void goodix_ext_sysfs_release(struct kobject *kobj) +{ + ts_info("Kobject released!"); +} + +#define to_ext_module(kobj) container_of(kobj,\ + struct goodix_ext_module, kobj) +#define to_ext_attr(attr) container_of(attr,\ + struct goodix_ext_attribute, attr) + +static ssize_t goodix_ext_sysfs_show(struct kobject *kobj, + struct attribute *attr, char *buf) +{ + struct goodix_ext_module *module = to_ext_module(kobj); + struct goodix_ext_attribute *ext_attr = to_ext_attr(attr); + + if (ext_attr->show) + return ext_attr->show(module, buf); + + return -EIO; +} + +static ssize_t goodix_ext_sysfs_store(struct kobject *kobj, + struct attribute *attr, const char *buf, size_t count) +{ + struct goodix_ext_module *module = to_ext_module(kobj); + struct goodix_ext_attribute *ext_attr = to_ext_attr(attr); + + if (ext_attr->store) + return ext_attr->store(module, buf, count); + + return -EIO; +} + +static const struct sysfs_ops goodix_ext_ops = { + .show = goodix_ext_sysfs_show, + .store = goodix_ext_sysfs_store +}; + +static struct kobj_type goodix_ext_ktype = { + .release = goodix_ext_sysfs_release, + .sysfs_ops = &goodix_ext_ops, +}; + +struct kobj_type *goodix_get_default_ktype(void) +{ + return &goodix_ext_ktype; +} + +struct kobject *goodix_get_default_kobj(void) +{ + struct kobject *kobj = NULL; + + if (goodix_modules.core_data && + goodix_modules.core_data->pdev) + kobj = &goodix_modules.core_data->pdev->dev.kobj; + return kobj; +} + +/* show driver infomation */ +static ssize_t driver_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "DriverVersion:%s\n", + GOODIX_DRIVER_VERSION); +} + +/* show chip infoamtion */ +static ssize_t chip_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct goodix_ts_core *cd = dev_get_drvdata(dev); + struct goodix_ts_hw_ops *hw_ops = cd->hw_ops; + struct goodix_fw_version chip_ver; + struct goodix_ic_info ic_info; + u8 temp_pid[8] = {0}; + int ret; + int cnt = -EINVAL; + + if (hw_ops->read_version) { + ret = hw_ops->read_version(cd, &chip_ver); + if (!ret) { + memcpy(temp_pid, chip_ver.rom_pid, + sizeof(chip_ver.rom_pid)); + cnt = snprintf(&buf[0], PAGE_SIZE, + "rom_pid:%s\nrom_vid:%02x%02x%02x\n", + temp_pid, chip_ver.rom_vid[0], + chip_ver.rom_vid[1], chip_ver.rom_vid[2]); + cnt += snprintf(&buf[cnt], PAGE_SIZE, + "patch_pid:%s\npatch_vid:%02x%02x%02x%02x\n", + chip_ver.patch_pid, chip_ver.patch_vid[0], + chip_ver.patch_vid[1], chip_ver.patch_vid[2], + chip_ver.patch_vid[3]); + cnt += snprintf(&buf[cnt], PAGE_SIZE, + "sensorid:%d\n", chip_ver.sensor_id); + } + } + + if (hw_ops->get_ic_info) { + ret = hw_ops->get_ic_info(cd, &ic_info); + if (!ret) { + cnt += snprintf(&buf[cnt], PAGE_SIZE, + "config_id:%x\n", + ic_info.version.config_id); + cnt += snprintf(&buf[cnt], PAGE_SIZE, + "config_version:%x\n", + ic_info.version.config_version); + } + } + + return cnt; +} + +/* reset chip */ +static ssize_t goodix_ts_reset_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct goodix_ts_core *core_data = dev_get_drvdata(dev); + struct goodix_ts_hw_ops *hw_ops = core_data->hw_ops; + + if (!buf || count <= 0) + return -EINVAL; + if (buf[0] != '0') + hw_ops->reset(core_data, GOODIX_NORMAL_RESET_DELAY_MS); + return count; +} + +/* read config */ +static ssize_t read_cfg_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct goodix_ts_core *core_data = dev_get_drvdata(dev); + struct goodix_ts_hw_ops *hw_ops = core_data->hw_ops; + int ret; + int i; + int offset; + char *cfg_buf = NULL; + + cfg_buf = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!cfg_buf) + return -ENOMEM; + + if (hw_ops->read_config) + ret = hw_ops->read_config(core_data, cfg_buf, PAGE_SIZE); + else + ret = -EINVAL; + + if (ret > 0) { + offset = 0; + for (i = 0; i < 200; i++) { // only print 200 bytes + offset += snprintf(&buf[offset], PAGE_SIZE - offset, + "%02x,", cfg_buf[i]); + if ((i + 1) % 20 == 0) + buf[offset++] = '\n'; + } + } + + kfree(cfg_buf); + if (ret <= 0) + return ret; + + return offset; +} + +static u8 ascii2hex(u8 a) +{ + s8 value = 0; + + if (a >= '0' && a <= '9') + value = a - '0'; + else if (a >= 'A' && a <= 'F') + value = a - 'A' + 0x0A; + else if (a >= 'a' && a <= 'f') + value = a - 'a' + 0x0A; + else + value = 0xff; + + return value; +} + +static int goodix_ts_convert_0x_data(const u8 *buf, int buf_size, + u8 *out_buf, int *out_buf_len) +{ + int i, m_size = 0; + int temp_index = 0; + u8 high, low; + + for (i = 0; i < buf_size; i++) { + if (buf[i] == 'x' || buf[i] == 'X') + m_size++; + } + + if (m_size <= 1) { + ts_err("cfg file ERROR, valid data count:%d", m_size); + return -EINVAL; + } + *out_buf_len = m_size; + + for (i = 0; i < buf_size; i++) { + if (buf[i] != 'x' && buf[i] != 'X') + continue; + + if (temp_index >= m_size) { + ts_err("exchange cfg data error, overflow, temp_index:%d,m_size:%d", + temp_index, m_size); + return -EINVAL; + } + high = ascii2hex(buf[i + 1]); + low = ascii2hex(buf[i + 2]); + if (high == 0xff || low == 0xff) { + ts_err("failed convert: 0x%x, 0x%x", + buf[i + 1], buf[i + 2]); + return -EINVAL; + } + out_buf[temp_index++] = (high << 4) + low; + } + return 0; +} + +/* send config */ +static ssize_t goodix_ts_send_cfg_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct goodix_ts_core *core_data = dev_get_drvdata(dev); + struct goodix_ts_hw_ops *hw_ops = core_data->hw_ops; + struct goodix_ic_config *config = NULL; + const struct firmware *cfg_img = NULL; + int ret; + + if (buf[0] != '1') + return -EINVAL; + + hw_ops->irq_enable(core_data, false); + + ret = request_firmware(&cfg_img, GOODIX_DEFAULT_CFG_NAME, dev); + if (ret < 0) { + ts_err("cfg file [%s] not available,errno:%d", + GOODIX_DEFAULT_CFG_NAME, ret); + goto exit; + } else + ts_info("cfg file [%s] is ready", GOODIX_DEFAULT_CFG_NAME); + + config = kzalloc(sizeof(*config), GFP_KERNEL); + if (!config) + goto exit; + + if (goodix_ts_convert_0x_data(cfg_img->data, cfg_img->size, + config->data, &config->len)) { + ts_err("convert config data FAILED"); + goto exit; + } + + if (hw_ops->send_config) { + ret = hw_ops->send_config(core_data, config->data, config->len); + if (ret < 0) + ts_err("send config failed"); + } + +exit: + hw_ops->irq_enable(core_data, true); + kfree(config); + if (cfg_img) + release_firmware(cfg_img); + + return count; +} + +/* reg read/write */ +static u32 rw_addr; +static u32 rw_len; +static u8 rw_flag; +static u8 store_buf[32]; +static u8 show_buf[PAGE_SIZE]; +static ssize_t goodix_ts_reg_rw_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct goodix_ts_core *core_data = dev_get_drvdata(dev); + struct goodix_ts_hw_ops *hw_ops = core_data->hw_ops; + int ret; + + if (!rw_addr || !rw_len) { + ts_err("address(0x%x) and length(%d) can't be null", + rw_addr, rw_len); + return -EINVAL; + } + + if (rw_flag != 1) { + ts_err("invalid rw flag %d, only support [1/2]", rw_flag); + return -EINVAL; + } + + ret = hw_ops->read(core_data, rw_addr, show_buf, rw_len); + if (ret < 0) { + ts_err("failed read addr(%x) length(%d)", rw_addr, rw_len); + return snprintf(buf, PAGE_SIZE, + "failed read addr(%x), len(%d)\n", + rw_addr, rw_len); + } + + return snprintf(buf, PAGE_SIZE, "0x%x,%d {%*ph}\n", + rw_addr, rw_len, rw_len, show_buf); +} + +static ssize_t goodix_ts_reg_rw_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct goodix_ts_core *core_data = dev_get_drvdata(dev); + struct goodix_ts_hw_ops *hw_ops = core_data->hw_ops; + char *pos = NULL; + char *token = NULL; + long result = 0; + int ret; + int i; + + if (!buf || !count) { + ts_err("invalid parame"); + goto err_out; + } + + if (buf[0] == 'r') + rw_flag = 1; + else if (buf[0] == 'w') + rw_flag = 2; + else { + ts_err("string must start with 'r/w'"); + goto err_out; + } + + /* get addr */ + pos = (char *)buf; + pos += 2; + token = strsep(&pos, ":"); + if (!token) { + ts_err("invalid address info"); + goto err_out; + } else { + if (kstrtol(token, 16, &result)) { + ts_err("failed get addr info"); + goto err_out; + } + rw_addr = (u32)result; + ts_info("rw addr is 0x%x", rw_addr); + } + + /* get length */ + token = strsep(&pos, ":"); + if (!token) { + ts_err("invalid length info"); + goto err_out; + } else { + if (kstrtol(token, 0, &result)) { + ts_err("failed get length info"); + goto err_out; + } + rw_len = (u32)result; + ts_info("rw length info is %d", rw_len); + if (rw_len > sizeof(store_buf)) { + ts_err("data len > %lu", sizeof(store_buf)); + goto err_out; + } + } + + if (rw_flag == 1) + return count; + + for (i = 0; i < rw_len; i++) { + token = strsep(&pos, ":"); + if (!token) { + ts_err("invalid data info"); + goto err_out; + } else { + if (kstrtol(token, 16, &result)) { + ts_err("failed get data[%d] info", i); + goto err_out; + } + store_buf[i] = (u8)result; + ts_info("get data[%d]=0x%x", i, store_buf[i]); + } + } + ret = hw_ops->write(core_data, rw_addr, store_buf, rw_len); + if (ret < 0) { + ts_err("failed write addr(%x) data %*ph", rw_addr, + rw_len, store_buf); + goto err_out; + } + + ts_info("%s write to addr (%x) with data %*ph", + "success", rw_addr, rw_len, store_buf); + + return count; +err_out: + snprintf(show_buf, PAGE_SIZE, "%s\n", + "invalid params, format{r/w:4100:length:[41:21:31]}"); + return -EINVAL; + +} + +/* show irq infomation */ +static ssize_t goodix_ts_irq_info_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct goodix_ts_core *core_data = dev_get_drvdata(dev); + struct irq_desc *desc; + size_t offset = 0; + int r; + + r = snprintf(&buf[offset], PAGE_SIZE, "irq:%u\n", core_data->irq); + if (r < 0) + return -EINVAL; + + offset += r; + r = snprintf(&buf[offset], PAGE_SIZE - offset, "state:%s\n", + atomic_read(&core_data->irq_enabled) ? "enabled" : "disabled"); + if (r < 0) + return -EINVAL; + + desc = irq_to_desc(core_data->irq); + offset += r; + r = snprintf(&buf[offset], PAGE_SIZE - offset, "disable-depth:%d\n", desc->depth); + if (r < 0) + return -EINVAL; + + offset += r; + r = snprintf(&buf[offset], PAGE_SIZE - offset, "trigger-count:%zu\n", + core_data->irq_trig_cnt); + if (r < 0) + return -EINVAL; + + offset += r; + r = snprintf(&buf[offset], PAGE_SIZE - offset, + "echo 0/1 > irq_info to disable/enable irq\n"); + if (r < 0) + return -EINVAL; + + offset += r; + return offset; +} + +/* enable/disable irq */ +static ssize_t goodix_ts_irq_info_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct goodix_ts_core *core_data = dev_get_drvdata(dev); + struct goodix_ts_hw_ops *hw_ops = core_data->hw_ops; + + if (!buf || count <= 0) + return -EINVAL; + + if (buf[0] != '0') + hw_ops->irq_enable(core_data, true); + else + hw_ops->irq_enable(core_data, false); + return count; +} + +/* show esd status */ +static ssize_t goodix_ts_esd_info_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct goodix_ts_core *core_data = dev_get_drvdata(dev); + struct goodix_ts_esd *ts_esd = &core_data->ts_esd; + int r = 0; + + r = snprintf(buf, PAGE_SIZE, "state:%s\n", + atomic_read(&ts_esd->esd_on) ? "enabled" : "disabled"); + + return r; +} + +/* enable/disable esd */ +static ssize_t goodix_ts_esd_info_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct goodix_ts_core *core_data = dev_get_drvdata(dev); + + if (!buf || count <= 0) + return -EINVAL; + + if (buf[0] != '0') { + if (!core_data->esd_initialized) + goodix_ts_esd_init(core_data); + goodix_ts_blocking_notify(NOTIFY_ESD_ON, NULL); + } else if (core_data->esd_initialized) { + goodix_ts_blocking_notify(NOTIFY_ESD_OFF, NULL); + } + + return count; +} + +/* debug level show */ +static ssize_t goodix_ts_debug_log_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int r = 0; + + r = snprintf(buf, PAGE_SIZE, "state:%s\n", + debug_log_flag ? "enabled" : "disabled"); + + return r; +} + +/* debug level store */ +static ssize_t goodix_ts_debug_log_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + if (!buf || count <= 0) + return -EINVAL; + + if (buf[0] != '0') + debug_log_flag = true; + else + debug_log_flag = false; + return count; +} + +static DEVICE_ATTR(driver_info, 0440, + driver_info_show, NULL); +static DEVICE_ATTR(chip_info, 0440, + chip_info_show, NULL); +static DEVICE_ATTR(reset, 0220, + NULL, goodix_ts_reset_store); +static DEVICE_ATTR(send_cfg, 0220, + NULL, goodix_ts_send_cfg_store); +static DEVICE_ATTR(read_cfg, 0440, + read_cfg_show, NULL); +static DEVICE_ATTR(reg_rw, 0664, + goodix_ts_reg_rw_show, goodix_ts_reg_rw_store); +static DEVICE_ATTR(irq_info, 0664, + goodix_ts_irq_info_show, goodix_ts_irq_info_store); +static DEVICE_ATTR(esd_info, 0664, + goodix_ts_esd_info_show, goodix_ts_esd_info_store); +static DEVICE_ATTR(debug_log, 0664, + goodix_ts_debug_log_show, goodix_ts_debug_log_store); + +static struct attribute *sysfs_attrs[] = { + &dev_attr_driver_info.attr, + &dev_attr_chip_info.attr, + &dev_attr_reset.attr, + &dev_attr_send_cfg.attr, + &dev_attr_read_cfg.attr, + &dev_attr_reg_rw.attr, + &dev_attr_irq_info.attr, + &dev_attr_esd_info.attr, + &dev_attr_debug_log.attr, + NULL, +}; + +static const struct attribute_group sysfs_group = { + .attrs = sysfs_attrs, +}; + +static int goodix_ts_sysfs_init(struct goodix_ts_core *core_data) +{ + int ret; + + ret = sysfs_create_group(&core_data->pdev->dev.kobj, &sysfs_group); + if (ret) { + ts_err("failed create core sysfs group"); + return ret; + } + + return ret; +} + +static void goodix_ts_sysfs_exit(struct goodix_ts_core *core_data) +{ + sysfs_remove_group(&core_data->pdev->dev.kobj, &sysfs_group); +} + +/* prosfs create */ +static int rawdata_proc_show(struct seq_file *m, void *v) +{ + struct ts_rawdata_info *info; + struct goodix_ts_core *cd = m->private; + int tx; + int rx; + int ret; + int i; + int index; + + if (!m || !v || !cd) + return -EIO; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + ret = cd->hw_ops->get_capacitance_data(cd, info); + if (ret < 0) { + ts_err("failed to get_capacitance_data, exit!"); + goto exit; + } + + rx = info->buff[0]; + tx = info->buff[1]; + seq_printf(m, "TX:%d RX:%d\n", tx, rx); + seq_puts(m, "mutual_rawdata:\n"); + index = 2; + for (i = 0; i < tx * rx; i++) { + seq_printf(m, "%5d,", info->buff[index + i]); + if ((i + 1) % tx == 0) + seq_puts(m, "\n"); + } + seq_puts(m, "mutual_diffdata:\n"); + index += tx * rx; + for (i = 0; i < tx * rx; i++) { + seq_printf(m, "%3d,", info->buff[index + i]); + if ((i + 1) % tx == 0) + seq_puts(m, "\n"); + } + +exit: + kfree(info); + return ret; +} + +static int rawdata_proc_open(struct inode *inode, struct file *file) +{ + #if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)) + return single_open_size(file, rawdata_proc_show, + pde_data(inode), PAGE_SIZE * 10); + #else + return single_open_size(file, rawdata_proc_show, + PDE_DATA(inode), PAGE_SIZE * 10); + #endif +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)) +static const struct proc_ops rawdata_proc_fops = { + .proc_open = rawdata_proc_open, + .proc_read = seq_read, + .proc_lseek = seq_lseek, + .proc_release = single_release, +}; +#else +static const struct file_operations rawdata_proc_fops = { + .open = rawdata_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; +#endif + +static void goodix_ts_procfs_init(struct goodix_ts_core *core_data) +{ + struct proc_dir_entry *proc_entry; + + if (!proc_mkdir("goodix_ts", NULL)) + return; + proc_entry = proc_create_data("goodix_ts/tp_capacitance_data", + 0664, NULL, &rawdata_proc_fops, core_data); + if (!proc_entry) + ts_err("failed to create proc entry"); +} + +static void goodix_ts_procfs_exit(struct goodix_ts_core *core_data) +{ + remove_proc_entry("goodix_ts/tp_capacitance_data", NULL); + remove_proc_entry("goodix_ts", NULL); +} + +/* event notifier */ +static BLOCKING_NOTIFIER_HEAD(ts_notifier_list); +/** + * goodix_ts_register_client - register a client notifier + * @nb: notifier block to callback on events + * see enum ts_notify_event in goodix_ts_core.h + */ +int goodix_ts_register_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&ts_notifier_list, nb); +} + +/** + * goodix_ts_unregister_client - unregister a client notifier + * @nb: notifier block to callback on events + * see enum ts_notify_event in goodix_ts_core.h + */ +int goodix_ts_unregister_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&ts_notifier_list, nb); +} + +/** + * fb_notifier_call_chain - notify clients of fb_events + * see enum ts_notify_event in goodix_ts_core.h + */ +int goodix_ts_blocking_notify(enum ts_notify_event evt, void *v) +{ + int ret; + + ret = blocking_notifier_call_chain(&ts_notifier_list, + (unsigned long)evt, v); + return ret; +} + +#if IS_ENABLED(CONFIG_OF) +/** + * goodix_parse_dt_resolution - parse resolution from dt + * @node: devicetree node + * @board_data: pointer to board data structure + * return: 0 - no error, <0 error + */ +static int goodix_parse_dt_resolution(struct device_node *node, + struct goodix_ts_board_data *board_data) +{ + int ret; + + ret = of_property_read_u32(node, "goodix,panel-max-x", + &board_data->panel_max_x); + if (ret) { + ts_err("failed get panel-max-x"); + return ret; + } + + ret = of_property_read_u32(node, "goodix,panel-max-y", + &board_data->panel_max_y); + if (ret) { + ts_err("failed get panel-max-y"); + return ret; + } + + board_data->invert_xy = of_property_read_bool(node, + "invert_xy"); + if (board_data->invert_xy) { + swap(board_data->panel_max_x, board_data->panel_max_y); + ts_info("Panel max x and max y inverted\n"); + } + + ret = of_property_read_u32(node, "goodix,panel-max-w", + &board_data->panel_max_w); + if (ret) { + ts_err("failed get panel-max-w"); + return ret; + } + + ret = of_property_read_u32(node, "goodix,panel-max-p", + &board_data->panel_max_p); + if (ret) { + ts_err("failed get panel-max-p, use default"); + board_data->panel_max_p = GOODIX_PEN_MAX_PRESSURE; + } + + return 0; +} + +/** + * goodix_parse_dt - parse board data from dt + * @dev: pointer to device + * @board_data: pointer to board data structure + * return: 0 - no error, <0 error + */ +static int goodix_parse_dt(struct device_node *node, + struct goodix_ts_board_data *board_data) +{ + const char *name_tmp; + int r; + + if (!board_data) { + ts_err("invalid board data"); + return -EINVAL; + } + + r = of_get_named_gpio(node, "goodix,avdd-gpio", 0); + if (r < 0) { + ts_info("can't find avdd-gpio, use other power supply"); + board_data->avdd_gpio = 0; + } else { + ts_info("get avdd-gpio[%d] from dt", r); + board_data->avdd_gpio = r; + } + + r = of_get_named_gpio(node, "goodix,iovdd-gpio", 0); + if (r < 0) { + ts_info("can't find iovdd-gpio, use other power supply"); + board_data->iovdd_gpio = 0; + } else { + ts_info("get iovdd-gpio[%d] from dt", r); + board_data->iovdd_gpio = r; + } + +#ifndef CONFIG_ARCH_QTI_VM + r = of_get_named_gpio(node, "goodix,reset-gpio", 0); + if (r < 0) { + ts_err("invalid reset-gpio in dt: %d", r); + return -EINVAL; + } + ts_info("get reset-gpio[%d] from dt", r); + board_data->reset_gpio = r; + + r = of_get_named_gpio(node, "goodix,irq-gpio", 0); + if (r < 0) { + ts_err("invalid irq-gpio in dt: %d", r); + return -EINVAL; + } + ts_info("get irq-gpio[%d] from dt", r); + board_data->irq_gpio = r; + + r = of_property_read_u32(node, "goodix,irq-flags", + &board_data->irq_flags); + if (r) { + ts_err("invalid irq-flags"); + return -EINVAL; + } +#endif + + memset(board_data->avdd_name, 0, sizeof(board_data->avdd_name)); + r = of_property_read_string(node, "goodix,avdd-name", &name_tmp); + if (!r) { + ts_info("avdd name from dt: %s", name_tmp); + if (strlen(name_tmp) < sizeof(board_data->avdd_name)) + strlcpy(board_data->avdd_name, + name_tmp, sizeof(board_data->avdd_name)); + else + ts_info("invalied avdd name length: %ld > %ld", + strlen(name_tmp), + sizeof(board_data->avdd_name)); + } + + memset(board_data->iovdd_name, 0, sizeof(board_data->iovdd_name)); + r = of_property_read_string(node, "goodix,iovdd-name", &name_tmp); + if (!r) { + ts_info("iovdd name from dt: %s", name_tmp); + if (strlen(name_tmp) < sizeof(board_data->iovdd_name)) + strlcpy(board_data->iovdd_name, + name_tmp, sizeof(board_data->iovdd_name)); + else + ts_info("invalied iovdd name length: %ld > %ld", + strlen(name_tmp), + sizeof(board_data->iovdd_name)); + } + + /* get firmware file name */ + r = of_property_read_string(node, "goodix,firmware-name", &name_tmp); + if (!r) { + ts_info("firmware name from dt: %s", name_tmp); + strlcpy(board_data->fw_name, + name_tmp, sizeof(board_data->fw_name)); + } else { + ts_info("can't find firmware name, use default: %s", + TS_DEFAULT_FIRMWARE); + strlcpy(board_data->fw_name, + TS_DEFAULT_FIRMWARE, + sizeof(board_data->fw_name)); + } + + /* get config file name */ + r = of_property_read_string(node, "goodix,config-name", &name_tmp); + if (!r) { + ts_info("config name from dt: %s", name_tmp); + strlcpy(board_data->cfg_bin_name, name_tmp, + sizeof(board_data->cfg_bin_name)); + } else { + ts_info("can't find config name, use default: %s", + TS_DEFAULT_CFG_BIN); + strlcpy(board_data->cfg_bin_name, + TS_DEFAULT_CFG_BIN, + sizeof(board_data->cfg_bin_name)); + } + + /* get xyz resolutions */ + r = goodix_parse_dt_resolution(node, board_data); + if (r) { + ts_err("Failed to parse resolutions:%d", r); + return r; + } + + + /*get pen-enable switch and pen keys, must after "key map"*/ + board_data->pen_enable = of_property_read_bool(node, + "goodix,pen-enable"); + if (board_data->pen_enable) + ts_info("goodix pen enabled"); + + ts_debug("[DT]x:%d, y:%d, w:%d, p:%d", board_data->panel_max_x, + board_data->panel_max_y, board_data->panel_max_w, + board_data->panel_max_p); + return 0; +} +#endif + +static void goodix_ts_report_pen(struct input_dev *dev, + struct goodix_pen_data *pen_data) +{ + int i; + + mutex_lock(&dev->mutex); + + if (pen_data->coords.status == TS_TOUCH) { + input_report_key(dev, BTN_TOUCH, 1); + input_report_key(dev, pen_data->coords.tool_type, 1); + input_report_abs(dev, ABS_X, pen_data->coords.x); + input_report_abs(dev, ABS_Y, pen_data->coords.y); + input_report_abs(dev, ABS_PRESSURE, pen_data->coords.p); + input_report_abs(dev, ABS_TILT_X, pen_data->coords.tilt_x); + input_report_abs(dev, ABS_TILT_Y, pen_data->coords.tilt_y); + ts_debug("pen_data:x %d, y %d, p %d, tilt_x %d tilt_y %d key[%d %d]", + pen_data->coords.x, pen_data->coords.y, + pen_data->coords.p, pen_data->coords.tilt_x, + pen_data->coords.tilt_y, + pen_data->keys[0].status == TS_TOUCH ? 1 : 0, + pen_data->keys[1].status == TS_TOUCH ? 1 : 0); + } else { + input_report_key(dev, BTN_TOUCH, 0); + input_report_key(dev, pen_data->coords.tool_type, 0); + } + /* report pen button */ + for (i = 0; i < GOODIX_MAX_PEN_KEY; i++) { + if (pen_data->keys[i].status == TS_TOUCH) + input_report_key(dev, pen_data->keys[i].code, 1); + else + input_report_key(dev, pen_data->keys[i].code, 0); + } + + input_sync(dev); + mutex_unlock(&dev->mutex); +} + +static void goodix_ts_report_finger(struct input_dev *dev, + struct goodix_touch_data *touch_data, + bool invert_xy) +{ + unsigned int touch_num = touch_data->touch_num; + int i; + + mutex_lock(&dev->mutex); + + for (i = 0; i < GOODIX_MAX_TOUCH; i++) { + if (touch_data->coords[i].status == TS_TOUCH) { + ts_debug("report: id[%d], x %d, y %d, w %d", i, + touch_data->coords[i].x, + touch_data->coords[i].y, + touch_data->coords[i].w); + + if (invert_xy) + swap(touch_data->coords[i].x, + touch_data->coords[i].y); + + input_mt_slot(dev, i); + input_mt_report_slot_state(dev, MT_TOOL_FINGER, true); + input_report_abs(dev, ABS_MT_POSITION_X, + touch_data->coords[i].x); + input_report_abs(dev, ABS_MT_POSITION_Y, + touch_data->coords[i].y); + input_report_abs(dev, ABS_MT_TOUCH_MAJOR, + touch_data->coords[i].w); + } else { + input_mt_slot(dev, i); + input_mt_report_slot_state(dev, MT_TOOL_FINGER, false); + } + } + + input_report_key(dev, BTN_TOUCH, touch_num > 0 ? 1 : 0); + input_sync(dev); + + mutex_unlock(&dev->mutex); +} + +static int goodix_ts_request_handle(struct goodix_ts_core *cd, + struct goodix_ts_event *ts_event) +{ + struct goodix_ts_hw_ops *hw_ops = cd->hw_ops; + int ret = -1; + + if (ts_event->request_code == REQUEST_TYPE_CONFIG) + ret = goodix_send_ic_config(cd, CONFIG_TYPE_NORMAL); + else if (ts_event->request_code == REQUEST_TYPE_RESET) + ret = hw_ops->reset(cd, GOODIX_NORMAL_RESET_DELAY_MS); + else + ts_info("can not handle request type 0x%x", + ts_event->request_code); + if (ret) + ts_err("failed handle request 0x%x", + ts_event->request_code); + else + ts_info("success handle ic request 0x%x", + ts_event->request_code); + return ret; +} + +/** + * goodix_ts_threadirq_func - Bottom half of interrupt + * This functions is excuted in thread context, + * sleep in this function is permit. + * + * @data: pointer to touch core data + * return: 0 ok, <0 failed + */ +static irqreturn_t goodix_ts_threadirq_func(int irq, void *data) +{ + struct goodix_ts_core *core_data = data; + struct goodix_ts_hw_ops *hw_ops = core_data->hw_ops; + struct goodix_ext_module *ext_module, *next; + struct goodix_ts_event *ts_event = &core_data->ts_event; + struct goodix_ts_esd *ts_esd = &core_data->ts_esd; + int ret; + + ts_esd->irq_status = true; + core_data->irq_trig_cnt++; + /* inform external module */ + mutex_lock(&goodix_modules.mutex); + list_for_each_entry_safe(ext_module, next, + &goodix_modules.head, list) { + if (!ext_module->funcs->irq_event) + continue; + ret = ext_module->funcs->irq_event(core_data, ext_module); + if (ret == EVT_CANCEL_IRQEVT) { + mutex_unlock(&goodix_modules.mutex); + return IRQ_HANDLED; + } + } + mutex_unlock(&goodix_modules.mutex); + + /* read touch data from touch device */ + ret = hw_ops->event_handler(core_data, ts_event); + if (likely(!ret)) { + if (ts_event->event_type == EVENT_TOUCH) { + /* report touch */ + goodix_ts_report_finger(core_data->input_dev, + &ts_event->touch_data, + core_data->board_data.invert_xy); + } + if (core_data->board_data.pen_enable && + ts_event->event_type == EVENT_PEN) { + goodix_ts_report_pen(core_data->pen_dev, + &ts_event->pen_data); + } + if (ts_event->event_type == EVENT_REQUEST) + goodix_ts_request_handle(core_data, ts_event); + } + + return IRQ_HANDLED; +} + +/** + * goodix_ts_init_irq - Requset interrput line from system + * @core_data: pointer to touch core data + * return: 0 ok, <0 failed + */ +static int goodix_ts_irq_setup(struct goodix_ts_core *core_data) +{ + const struct goodix_ts_board_data *ts_bdata = board_data(core_data); + int ret; + + /* if ts_bdata-> irq is invalid */ + core_data->irq = gpio_to_irq(ts_bdata->irq_gpio); + if (core_data->irq < 0) { + ts_err("failed get irq num %d", core_data->irq); + return -EINVAL; + } + + ts_info("IRQ:%u,flags:%d", core_data->irq, (int)ts_bdata->irq_flags); + ret = devm_request_threaded_irq(&core_data->pdev->dev, + core_data->irq, NULL, + goodix_irq_handler, + ts_bdata->irq_flags | IRQF_ONESHOT, + GOODIX_CORE_DRIVER_NAME, + core_data); + if (ret < 0) + ts_err("Failed to requeset threaded irq:%d", ret); + else + atomic_set(&core_data->irq_enabled, 1); + + return ret; +} + +/** + * goodix_ts_power_init - Get regulator for touch device + * @core_data: pointer to touch core data + * return: 0 ok, <0 failed + */ +static int goodix_ts_power_init(struct goodix_ts_core *core_data) +{ + struct goodix_ts_board_data *ts_bdata = board_data(core_data); + struct device *dev = core_data->bus->dev; + int ret = 0; + + ts_info("Power init"); + if (strlen(ts_bdata->avdd_name)) { + core_data->avdd = devm_regulator_get(dev, ts_bdata->avdd_name); + if (IS_ERR_OR_NULL(core_data->avdd)) { + ret = PTR_ERR(core_data->avdd); + ts_err("Failed to get regulator avdd:%d", ret); + core_data->avdd = NULL; + return ret; + } + } else + ts_info("Avdd name is NULL"); + + if (strlen(ts_bdata->iovdd_name)) { + core_data->iovdd = devm_regulator_get(dev, + ts_bdata->iovdd_name); + if (IS_ERR_OR_NULL(core_data->iovdd)) { + ret = PTR_ERR(core_data->iovdd); + ts_err("Failed to get regulator iovdd:%d", ret); + core_data->iovdd = NULL; + } + } else + ts_info("iovdd name is NULL"); + + return ret; +} + +/** + * goodix_ts_power_on - Turn on power to the touch device + * @core_data: pointer to touch core data + * return: 0 ok, <0 failed + */ +int goodix_ts_power_on(struct goodix_ts_core *cd) +{ + int ret = 0; + + ts_info("Device power on"); + if (cd->power_on) + return 0; + + ret = cd->hw_ops->power_on(cd, true); + if (!ret) + cd->power_on = 1; + else + ts_err("failed power on, %d", ret); + return ret; +} + +/** + * goodix_ts_power_off - Turn off power to the touch device + * @core_data: pointer to touch core data + * return: 0 ok, <0 failed + */ +int goodix_ts_power_off(struct goodix_ts_core *cd) +{ + int ret; + + ts_info("Device power off"); + if (!cd->power_on) + return 0; + + ret = cd->hw_ops->power_on(cd, false); + if (!ret) + cd->power_on = 0; + else + ts_err("failed power off, %d", ret); + + return ret; +} + +/** + * goodix_ts_gpio_setup - Request gpio resources from GPIO subsysten + * @core_data: pointer to touch core data + * return: 0 ok, <0 failed + */ +static int goodix_ts_gpio_setup(struct goodix_ts_core *core_data) +{ + struct goodix_ts_board_data *ts_bdata = board_data(core_data); + int r = 0; + + ts_info("GPIO setup,reset-gpio:%d, irq-gpio:%d", + ts_bdata->reset_gpio, ts_bdata->irq_gpio); + /* + * after kenerl3.13, gpio_ api is deprecated, new + * driver should use gpiod_ api. + */ + r = devm_gpio_request_one(&core_data->pdev->dev, + ts_bdata->reset_gpio, + GPIOF_OUT_INIT_LOW, "ts_reset_gpio"); + if (r < 0) { + ts_err("Failed to request reset gpio, r:%d", r); + return r; + } + + r = devm_gpio_request_one(&core_data->pdev->dev, + ts_bdata->irq_gpio, + GPIOF_IN, "ts_irq_gpio"); + if (r < 0) { + ts_err("Failed to request irq gpio, r:%d", r); + return r; + } + + if (ts_bdata->avdd_gpio > 0) { + r = devm_gpio_request_one(&core_data->pdev->dev, + ts_bdata->avdd_gpio, + GPIOF_OUT_INIT_LOW, "ts_avdd_gpio"); + if (r < 0) { + ts_err("Failed to request avdd-gpio, r:%d", r); + return r; + } + } + + if (ts_bdata->iovdd_gpio > 0) { + r = devm_gpio_request_one(&core_data->pdev->dev, + ts_bdata->iovdd_gpio, + GPIOF_OUT_INIT_LOW, "ts_iovdd_gpio"); + if (r < 0) { + ts_err("Failed to request iovdd-gpio, r:%d", r); + return r; + } + } + + return 0; +} + +/** + * goodix_ts_input_dev_config - Requset and config a input device + * then register it to input sybsystem. + * @core_data: pointer to touch core data + * return: 0 ok, <0 failed + */ +static int goodix_ts_input_dev_config(struct goodix_ts_core *core_data) +{ + struct goodix_ts_board_data *ts_bdata = board_data(core_data); + struct input_dev *input_dev = NULL; + int r; + + input_dev = input_allocate_device(); + if (!input_dev) { + ts_err("Failed to allocated input device"); + return -ENOMEM; + } + + core_data->input_dev = input_dev; + input_set_drvdata(input_dev, core_data); + + input_dev->name = GOODIX_CORE_DRIVER_NAME; + input_dev->phys = GOOIDX_INPUT_PHYS; + input_dev->id.product = 0xDEAD; + input_dev->id.vendor = 0xBEEF; + input_dev->id.version = 10427; + input_dev->dev.parent = core_data->bus->dev; + + set_bit(EV_SYN, input_dev->evbit); + set_bit(EV_KEY, input_dev->evbit); + set_bit(EV_ABS, input_dev->evbit); + set_bit(BTN_TOUCH, input_dev->keybit); + set_bit(BTN_TOOL_FINGER, input_dev->keybit); + set_bit(INPUT_PROP_DIRECT, input_dev->propbit); + + /* set input parameters */ + input_set_abs_params(input_dev, ABS_MT_POSITION_X, + 0, ts_bdata->panel_max_x, 0, 0); + input_set_abs_params(input_dev, ABS_MT_POSITION_Y, + 0, ts_bdata->panel_max_y, 0, 0); + input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, + 0, ts_bdata->panel_max_w, 0, 0); +#ifdef INPUT_TYPE_B_PROTOCOL +#if LINUX_VERSION_CODE > KERNEL_VERSION(3, 7, 0) + input_mt_init_slots(input_dev, GOODIX_MAX_TOUCH, + INPUT_MT_DIRECT); +#else + input_mt_init_slots(input_dev, GOODIX_MAX_TOUCH); +#endif +#endif + + input_set_capability(input_dev, EV_KEY, KEY_POWER); + input_set_capability(input_dev, EV_KEY, KEY_WAKEUP); + input_set_capability(input_dev, EV_KEY, KEY_GOTO); + + r = input_register_device(input_dev); + if (r < 0) { + ts_err("Unable to register input device"); + input_free_device(input_dev); + return r; + } + + return 0; +} + +static int goodix_ts_pen_dev_config(struct goodix_ts_core *core_data) +{ + struct goodix_ts_board_data *ts_bdata = board_data(core_data); + struct input_dev *pen_dev = NULL; + int r; + + pen_dev = input_allocate_device(); + if (!pen_dev) { + ts_err("Failed to allocated pen device"); + return -ENOMEM; + } + + core_data->pen_dev = pen_dev; + input_set_drvdata(pen_dev, core_data); + + pen_dev->name = GOODIX_PEN_DRIVER_NAME; + pen_dev->id.product = 0xDEAD; + pen_dev->id.vendor = 0xBEEF; + pen_dev->id.version = 10427; + + pen_dev->evbit[0] |= BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + set_bit(ABS_X, pen_dev->absbit); + set_bit(ABS_Y, pen_dev->absbit); + set_bit(ABS_TILT_X, pen_dev->absbit); + set_bit(ABS_TILT_Y, pen_dev->absbit); + set_bit(BTN_STYLUS, pen_dev->keybit); + set_bit(BTN_STYLUS2, pen_dev->keybit); + set_bit(BTN_TOUCH, pen_dev->keybit); + set_bit(BTN_TOOL_PEN, pen_dev->keybit); + set_bit(INPUT_PROP_DIRECT, pen_dev->propbit); + input_set_abs_params(pen_dev, ABS_X, 0, ts_bdata->panel_max_x, 0, 0); + input_set_abs_params(pen_dev, ABS_Y, 0, ts_bdata->panel_max_y, 0, 0); + input_set_abs_params(pen_dev, ABS_PRESSURE, 0, + ts_bdata->panel_max_p, 0, 0); + input_set_abs_params(pen_dev, ABS_TILT_X, + -GOODIX_PEN_MAX_TILT, GOODIX_PEN_MAX_TILT, 0, 0); + input_set_abs_params(pen_dev, ABS_TILT_Y, + -GOODIX_PEN_MAX_TILT, GOODIX_PEN_MAX_TILT, 0, 0); + + r = input_register_device(pen_dev); + if (r < 0) { + ts_err("Unable to register pen device"); + input_free_device(pen_dev); + return r; + } + + return 0; +} + +void goodix_ts_input_dev_remove(struct goodix_ts_core *core_data) +{ + if (!core_data->input_dev) + return; + input_unregister_device(core_data->input_dev); + input_free_device(core_data->input_dev); + core_data->input_dev = NULL; +} + +void goodix_ts_pen_dev_remove(struct goodix_ts_core *core_data) +{ + if (!core_data->pen_dev) + return; + input_unregister_device(core_data->pen_dev); + input_free_device(core_data->pen_dev); + core_data->pen_dev = NULL; +} + +/** + * goodix_ts_esd_work - check hardware status and recovery + * the hardware if needed. + */ +static void goodix_ts_esd_work(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct goodix_ts_esd *ts_esd = container_of(dwork, + struct goodix_ts_esd, esd_work); + struct goodix_ts_core *cd = container_of(ts_esd, + struct goodix_ts_core, ts_esd); + const struct goodix_ts_hw_ops *hw_ops = cd->hw_ops; + int ret = 0; + + if (ts_esd->irq_status) + goto exit; + + if (!atomic_read(&ts_esd->esd_on)) + return; + + if (!hw_ops->esd_check) + return; + + ret = hw_ops->esd_check(cd); + if (ret) { + ts_err("esd check failed"); + goodix_ts_power_off(cd); + usleep_range(5000, 5100); + goodix_ts_power_on(cd); + } + +exit: + ts_esd->irq_status = false; + if (atomic_read(&ts_esd->esd_on)) + schedule_delayed_work(&ts_esd->esd_work, 2 * HZ); +} + +/** + * goodix_ts_esd_on - turn on esd protection + */ +static void goodix_ts_esd_on(struct goodix_ts_core *cd) +{ + struct goodix_ic_info_misc *misc = &cd->ic_info.misc; + struct goodix_ts_esd *ts_esd = &cd->ts_esd; + + if (!misc->esd_addr) + return; + + if (atomic_read(&ts_esd->esd_on)) + return; + + atomic_set(&ts_esd->esd_on, 1); + if (!schedule_delayed_work(&ts_esd->esd_work, 2 * HZ)) + ts_info("esd work already in workqueue"); + + ts_info("esd on"); +} + +/** + * goodix_ts_esd_off - turn off esd protection + */ +static void goodix_ts_esd_off(struct goodix_ts_core *cd) +{ + struct goodix_ts_esd *ts_esd = &cd->ts_esd; + int ret; + + if (!atomic_read(&ts_esd->esd_on)) + return; + + atomic_set(&ts_esd->esd_on, 0); + ret = cancel_delayed_work_sync(&ts_esd->esd_work); + ts_info("Esd off, esd work state %d", ret); +} + +/** + * goodix_esd_notifier_callback - notification callback + * under certain condition, we need to turn off/on the esd + * protector, we use kernel notify call chain to achieve this. + * + * for example: before firmware update we need to turn off the + * esd protector and after firmware update finished, we should + * turn on the esd protector. + */ +static int goodix_esd_notifier_callback(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct goodix_ts_esd *ts_esd = container_of(nb, + struct goodix_ts_esd, esd_notifier); + + switch (action) { + case NOTIFY_FWUPDATE_START: + case NOTIFY_SUSPEND: + case NOTIFY_ESD_OFF: + goodix_ts_esd_off(ts_esd->ts_core); + break; + case NOTIFY_FWUPDATE_FAILED: + case NOTIFY_FWUPDATE_SUCCESS: + case NOTIFY_RESUME: + case NOTIFY_ESD_ON: + goodix_ts_esd_on(ts_esd->ts_core); + break; + default: + break; + } + + return 0; +} + +/** + * goodix_ts_esd_init - initialize esd protection + */ +int goodix_ts_esd_init(struct goodix_ts_core *cd) +{ + struct goodix_ic_info_misc *misc = &cd->ic_info.misc; + struct goodix_ts_esd *ts_esd = &cd->ts_esd; + + if (!cd->hw_ops->esd_check || !misc->esd_addr) { + ts_info("missing key info for esd check"); + return 0; + } + + INIT_DELAYED_WORK(&ts_esd->esd_work, goodix_ts_esd_work); + ts_esd->ts_core = cd; + atomic_set(&ts_esd->esd_on, 0); + ts_esd->esd_notifier.notifier_call = goodix_esd_notifier_callback; + goodix_ts_register_notifier(&ts_esd->esd_notifier); + goodix_ts_esd_on(cd); + cd->esd_initialized = true; + + return 0; +} + +static void goodix_ts_release_connects(struct goodix_ts_core *core_data) +{ + struct input_dev *input_dev = core_data->input_dev; + int i; + + mutex_lock(&input_dev->mutex); + + for (i = 0; i < GOODIX_MAX_TOUCH; i++) { + input_mt_slot(input_dev, i); + input_mt_report_slot_state(input_dev, + MT_TOOL_FINGER, + false); + } + input_report_key(input_dev, BTN_TOUCH, 0); + input_mt_sync_frame(input_dev); + input_sync(input_dev); + + mutex_unlock(&input_dev->mutex); +} + +/** + * goodix_ts_suspend - Touchscreen suspend function + * Called by PM/FB/EARLYSUSPEN module to put the device to sleep + */ +static int goodix_ts_suspend(struct goodix_ts_core *core_data) +{ + struct goodix_ext_module *ext_module, *next; + struct goodix_ts_hw_ops *hw_ops = core_data->hw_ops; + int ret; + + if (core_data->init_stage < CORE_INIT_STAGE2 || + atomic_read(&core_data->suspended)) + return 0; + + ts_info("Suspend start"); + atomic_set(&core_data->suspended, 1); + /* disable irq */ + hw_ops->irq_enable(core_data, false); + + /* + * notify suspend event, inform the esd protector + * and charger detector to turn off the work + */ + goodix_ts_blocking_notify(NOTIFY_SUSPEND, NULL); + + /* inform external module */ + mutex_lock(&goodix_modules.mutex); + if (!list_empty(&goodix_modules.head)) { + list_for_each_entry_safe(ext_module, next, + &goodix_modules.head, list) { + if (!ext_module->funcs->before_suspend) + continue; + + ret = ext_module->funcs->before_suspend(core_data, ext_module); + if (ret == EVT_CANCEL_SUSPEND) { + mutex_unlock(&goodix_modules.mutex); + ts_info("Canceled by module:%s", + ext_module->name); + goto out; + } + } + } + mutex_unlock(&goodix_modules.mutex); + + /* enter sleep mode or power off */ + if (hw_ops->suspend) + hw_ops->suspend(core_data); + + /* inform exteranl modules */ + mutex_lock(&goodix_modules.mutex); + if (!list_empty(&goodix_modules.head)) { + list_for_each_entry_safe(ext_module, next, + &goodix_modules.head, list) { + if (!ext_module->funcs->after_suspend) + continue; + + ret = ext_module->funcs->after_suspend(core_data, ext_module); + if (ret == EVT_CANCEL_SUSPEND) { + mutex_unlock(&goodix_modules.mutex); + ts_info("Canceled by module:%s", + ext_module->name); + goto out; + } + } + } + mutex_unlock(&goodix_modules.mutex); + +out: + goodix_ts_release_connects(core_data); + ts_info("Suspend end"); + return 0; +} + +/** + * goodix_ts_resume - Touchscreen resume function + * Called by PM/FB/EARLYSUSPEN module to wakeup device + */ +static int goodix_ts_resume(struct goodix_ts_core *core_data) +{ + struct goodix_ext_module *ext_module, *next; + struct goodix_ts_hw_ops *hw_ops = core_data->hw_ops; + int ret; + + if (core_data->init_stage < CORE_INIT_STAGE2 || + !atomic_read(&core_data->suspended)) + return 0; + + ts_info("Resume start"); + atomic_set(&core_data->suspended, 0); + hw_ops->irq_enable(core_data, false); + + mutex_lock(&goodix_modules.mutex); + if (!list_empty(&goodix_modules.head)) { + list_for_each_entry_safe(ext_module, next, + &goodix_modules.head, list) { + if (!ext_module->funcs->before_resume) + continue; + + ret = ext_module->funcs->before_resume(core_data, ext_module); + if (ret == EVT_CANCEL_RESUME) { + mutex_unlock(&goodix_modules.mutex); + ts_info("Canceled by module:%s", ext_module->name); + goto out; + } + } + } + mutex_unlock(&goodix_modules.mutex); + + /* reset device or power on*/ + if (hw_ops->resume) + hw_ops->resume(core_data); + + mutex_lock(&goodix_modules.mutex); + if (!list_empty(&goodix_modules.head)) { + list_for_each_entry_safe(ext_module, next, + &goodix_modules.head, list) { + if (!ext_module->funcs->after_resume) + continue; + + ret = ext_module->funcs->after_resume(core_data, ext_module); + if (ret == EVT_CANCEL_RESUME) { + mutex_unlock(&goodix_modules.mutex); + ts_info("Canceled by module:%s", + ext_module->name); + goto out; + } + } + } + mutex_unlock(&goodix_modules.mutex); + +out: + /* enable irq */ + hw_ops->irq_enable(core_data, true); + /* open esd */ + goodix_ts_blocking_notify(NOTIFY_RESUME, NULL); + ts_info("Resume end"); + return 0; +} + +#if defined(CONFIG_DRM) + +static void goodix_panel_notifier_callback(enum panel_event_notifier_tag tag, + struct panel_event_notification *notification, void *client_data) +{ + struct goodix_ts_core *core_data = client_data; + + if (!notification) { + pr_err("Invalid notification\n"); + return; + } + + ts_debug("Notification type:%d, early_trigger:%d", + notification->notif_type, + notification->notif_data.early_trigger); + switch (notification->notif_type) { + case DRM_PANEL_EVENT_UNBLANK: + if (!notification->notif_data.early_trigger) + goodix_ts_resume(core_data); + break; + + case DRM_PANEL_EVENT_BLANK: + if (notification->notif_data.early_trigger) + goodix_ts_suspend(core_data); + break; + + case DRM_PANEL_EVENT_BLANK_LP: + ts_debug("received lp event\n"); + break; + + case DRM_PANEL_EVENT_FPS_CHANGE: + ts_debug("Received fps change old fps:%d new fps:%d\n", + notification->notif_data.old_fps, + notification->notif_data.new_fps); + break; + + default: + ts_debug("notification serviced :%d\n", + notification->notif_type); + break; + } +} + +#elif IS_ENABLED(CONFIG_FB) + +/** + * goodix_ts_fb_notifier_callback - Framebuffer notifier callback + * Called by kernel during framebuffer blanck/unblank phrase + */ +int goodix_ts_fb_notifier_callback(struct notifier_block *self, + unsigned long event, void *data) +{ + struct goodix_ts_core *core_data = + container_of(self, struct goodix_ts_core, fb_notifier); + struct fb_event *fb_event = data; + + if (fb_event && fb_event->data && core_data) { + if (event == FB_EARLY_EVENT_BLANK) { + /* before fb blank */ + } else if (event == FB_EVENT_BLANK) { + int *blank = fb_event->data; + + if (*blank == FB_BLANK_UNBLANK) + goodix_ts_resume(core_data); + else if (*blank == FB_BLANK_POWERDOWN) + goodix_ts_suspend(core_data); + } + } + + return 0; +} +#endif + + +#if defined(CONFIG_PM) && !defined(CONFIG_DRM) +#if !defined(CONFIG_FB) && !defined(CONFIG_HAS_EARLYSUSPEND) +/** + * goodix_ts_pm_suspend - PM suspend function + * Called by kernel during system suspend phrase + */ +static int goodix_ts_pm_suspend(struct device *dev) +{ + struct goodix_ts_core *core_data = + dev_get_drvdata(dev); + + return goodix_ts_suspend(core_data); +} +/** + * goodix_ts_pm_resume - PM resume function + * Called by kernel during system wakeup + */ +static int goodix_ts_pm_resume(struct device *dev) +{ + struct goodix_ts_core *core_data = + dev_get_drvdata(dev); + + return goodix_ts_resume(core_data); +} +#endif +#endif + +/** + * goodix_generic_noti_callback - generic notifier callback + * for goodix touch notification event. + */ +static int goodix_generic_noti_callback(struct notifier_block *self, + unsigned long action, void *data) +{ + struct goodix_ts_core *cd = container_of(self, + struct goodix_ts_core, ts_notifier); + const struct goodix_ts_hw_ops *hw_ops = cd->hw_ops; + + if (cd->init_stage < CORE_INIT_STAGE2) + return 0; + + ts_info("notify event type 0x%x", (unsigned int)action); + switch (action) { + case NOTIFY_FWUPDATE_START: + hw_ops->irq_enable(cd, 0); + break; + case NOTIFY_FWUPDATE_SUCCESS: + case NOTIFY_FWUPDATE_FAILED: + if (hw_ops->read_version(cd, &cd->fw_version)) + ts_info("failed read fw version info[ignore]"); + hw_ops->irq_enable(cd, 1); + break; + default: + break; + } + return 0; +} + +int goodix_ts_stage2_init(struct goodix_ts_core *cd) +{ + int ret; + struct goodix_bus_interface *bus_interface; + struct device_node *node; + bool is_primary; + + bus_interface = cd->bus; + node = bus_interface->dev->of_node; + is_primary = (goodix_get_touch_type(node) == PRIMARY_TOUCH_IDX) ? 1 : 0; + + + /* alloc/config/register input device */ + ret = goodix_ts_input_dev_config(cd); + if (ret < 0) { + ts_err("failed set input device"); + return ret; + } + + if (cd->board_data.pen_enable) { + ret = goodix_ts_pen_dev_config(cd); + if (ret < 0) { + ts_err("failed set pen device"); + goto err_finger; + } + } +#ifdef CONFIG_ARCH_QTI_VM + goto skip_goodix_ts_irq_setup; +#endif + /* request irq line */ + ret = goodix_ts_irq_setup(cd); + if (ret < 0) { + ts_info("failed set irq"); + goto exit; + } + ts_info("success register irq"); + +#if defined(CONFIG_DRM) + if (is_primary && cd->touch_environment && !strcmp(cd->touch_environment, "pvm")) + goodix_register_for_panel_events(cd->bus->dev->of_node, cd); + +#elif defined(CONFIG_FB) + cd->fb_notifier.notifier_call = goodix_ts_fb_notifier_callback; + if (fb_register_client(&cd->fb_notifier)) + ts_err("Failed to register fb notifier client:%d", ret); +#endif + +#ifdef CONFIG_ARCH_QTI_VM +skip_goodix_ts_irq_setup: +#endif + /* create sysfs files */ + goodix_ts_sysfs_init(cd); + + if (!is_primary) + return 0; + + /* create procfs files */ + goodix_ts_procfs_init(cd); +#ifdef GOODIX_SUSPEND_GESTURE_ENABLE + /* gesture init */ + gesture_module_init(); +#endif + /* inspect init */ + inspect_module_init(); + + return 0; +exit: + goodix_ts_pen_dev_remove(cd); +err_finger: + goodix_ts_input_dev_remove(cd); + return ret; +} + +/* try send the config specified with type */ +static int goodix_send_ic_config(struct goodix_ts_core *cd, int type) +{ + u32 config_id; + struct goodix_ic_config *cfg; + + if (type >= GOODIX_MAX_CONFIG_GROUP) { + ts_err("unsupproted config type %d", type); + return -EINVAL; + } + + cfg = cd->ic_configs[type]; + if (!cfg || cfg->len <= 0) { + ts_info("no valid normal config found"); + return -EINVAL; + } + + config_id = goodix_get_file_config_id(cfg->data); + if (cd->ic_info.version.config_id == config_id) { + ts_info("config id is equal 0x%x, skiped", config_id); + return 0; + } + + ts_info("try send config, id=0x%x", config_id); + return cd->hw_ops->send_config(cd, cfg->data, cfg->len); +} + +/** + * goodix_later_init_thread - init IC fw and config + * @data: point to goodix_ts_core + * + * This function respond for get fw version and try upgrade fw and config. + * Note: when init encounter error, need release all resource allocated here. + */ +static int goodix_later_init_thread(void *data) +{ + int ret, i; + int update_flag = UPDATE_MODE_BLOCK | UPDATE_MODE_SRC_REQUEST; + struct goodix_ts_core *cd = data; + struct goodix_ts_hw_ops *hw_ops = cd->hw_ops; + + mutex_lock(&goodix_later_init_tmutex); +#ifdef CONFIG_ARCH_QTI_VM + goto skip_to_stage2_init; +#endif + + /* step 1: read version */ + ret = cd->hw_ops->read_version(cd, &cd->fw_version); + if (ret < 0) { + ts_err("failed to get version info, try to upgrade"); + update_flag |= UPDATE_MODE_FORCE; + goto upgrade; + } + + /* step 2: get config data from config bin */ + ret = goodix_get_config_proc(cd); + if (ret) + ts_info("no valid ic config found"); + else + ts_info("success get valid ic config"); + +upgrade: + /* step 3: init fw struct add try do fw upgrade */ + ret = goodix_fw_update_init(cd); + if (ret) { + ts_err("failed init fw update module"); + goto err_out; + } + + ts_info("update flag: 0x%X", update_flag); + ret = goodix_do_fw_update(cd->ic_configs[CONFIG_TYPE_NORMAL], + update_flag); + if (ret) + ts_err("failed do fw update"); + + /* step 4: get fw version and ic_info + * at this step we believe that the ic is in normal mode, + * if the version info is invalid there must have some + * problem we cann't cover so exit init directly. + */ + ret = hw_ops->read_version(cd, &cd->fw_version); + if (ret) { + ts_err("invalid fw version, abort"); + goto uninit_fw; + } + ret = hw_ops->get_ic_info(cd, &cd->ic_info); + if (ret) { + ts_err("invalid ic info, abort"); + goto uninit_fw; + } + + /* the recomend way to update ic config is throuth ISP, + * if not we will send config with interactive mode + */ + goodix_send_ic_config(cd, CONFIG_TYPE_NORMAL); +#ifdef CONFIG_ARCH_QTI_VM +skip_to_stage2_init: +#endif + /* init other resources */ + ret = goodix_ts_stage2_init(cd); + if (ret) { + ts_err("stage2 init failed"); + goto uninit_fw; + } + cd->init_stage = CORE_INIT_STAGE2; + mutex_unlock(&goodix_later_init_tmutex); + return 0; + +uninit_fw: + goodix_fw_update_uninit(); +err_out: + ts_err("stage2 init failed"); + cd->init_stage = CORE_INIT_FAIL; + for (i = 0; i < GOODIX_MAX_CONFIG_GROUP; i++) { + kfree(cd->ic_configs[i]); + cd->ic_configs[i] = NULL; + } + mutex_unlock(&goodix_later_init_tmutex); + return ret; +} + +static int goodix_start_later_init(struct goodix_ts_core *ts_core) +{ + struct task_struct *init_thrd; + /* create and run update thread */ + init_thrd = kthread_run(goodix_later_init_thread, + ts_core, "goodix_init_thread"); + if (IS_ERR_OR_NULL(init_thrd)) { + ts_err("Failed to create update thread:%ld", + PTR_ERR(init_thrd)); + return -EFAULT; + } + return 0; +} + +#if defined(CONFIG_DRM) +static int goodix_check_dt(struct device_node *np) +{ + int i; + int count; + struct device_node *node; + struct drm_panel *panel; + + count = of_count_phandle_with_args(np, "panel", NULL); + if (count <= 0) + return 0; + + for (i = 0; i < count; i++) { + node = of_parse_phandle(np, "panel", i); + panel = of_drm_find_panel(node); + of_node_put(node); + if (!IS_ERR(panel)) { + active_panel = panel; + return 0; + } + } + + return PTR_ERR(panel); +} + +static int goodix_check_default_tp(struct device_node *dt, const char *prop) +{ + const char **active_tp = NULL; + int count, tmp, score = 0; + const char *active; + int ret, i; + + count = of_property_count_strings(dt->parent, prop); + if (count <= 0 || count > 3) + return -ENODEV; + + active_tp = kcalloc(count, sizeof(char *), GFP_KERNEL); + if (!active_tp) { + ts_err("FTS alloc failed\n"); + return -ENOMEM; + } + + ret = of_property_read_string_array(dt->parent, prop, + active_tp, count); + if (ret < 0) { + ts_err("fail to read %s %d\n", prop, ret); + ret = -ENODEV; + goto out; + } + + for (i = 0; i < count; i++) { + active = active_tp[i]; + if (active != NULL) { + tmp = of_device_is_compatible(dt, active); + if (tmp > 0) + score++; + } + } + + if (score <= 0) { + ts_err("not match this driver\n"); + ret = -ENODEV; + goto out; + } + ret = 0; +out: + kfree(active_tp); + return ret; +} + +#endif + +static int goodix_ts_suspend_helper(void *data) +{ + struct goodix_ts_core *core_data = data; + + if (!core_data || !core_data->ready) + return 0; + + return goodix_ts_suspend(core_data); +} + +static int goodix_ts_resume_helper(void *data) +{ + struct goodix_ts_core *core_data = data; + + if (!core_data || !core_data->ready) + return 0; + + return goodix_ts_resume(core_data); +} + +static int goodix_ts_enable_touch_irq(void *data, bool enable) +{ + struct goodix_ts_core *core_data = data; + struct goodix_ts_hw_ops *hw_ops = core_data->hw_ops; + + hw_ops->irq_enable(core_data, enable); + return 0; +} + +static int goodix_ts_pre_la_tui_enable(void *data) +{ + struct goodix_ts_core *core_data = data; + struct goodix_ts_esd *ts_esd = &core_data->ts_esd; + + mutex_lock(&core_data->tui_transition_lock); + atomic_set(&ts_esd->esd_on, 0); + + return 0; +} + +static int goodix_ts_post_la_tui_enable(void *data) +{ + struct goodix_ts_core *core_data = data; + + goodix_ts_release_connects(core_data); + mutex_unlock(&core_data->tui_transition_lock); + return 0; +} + +static int goodix_ts_post_la_tui_disable(void *data) +{ + struct goodix_ts_core *core_data = data; + struct goodix_ts_esd *ts_esd = &core_data->ts_esd; + + atomic_set(&ts_esd->esd_on, 1); + return 0; +} + +static int goodix_ts_post_le_tui_enable(void *data) +{ + int ret; + struct goodix_ts_core *cd = data; + struct goodix_ts_hw_ops *hw_ops = cd->hw_ops; + + ret = hw_ops->read_version(cd, &cd->fw_version); + if (ret) { + ts_err("invalid fw version, abort"); + return -EINVAL; + } + ret = hw_ops->get_ic_info(cd, &cd->ic_info); + if (ret) { + ts_err("invalid ic info, abort"); + return -EINVAL; + } + return 0; +} + +static int goodix_ts_post_le_tui_disable(void *data) +{ + struct goodix_ts_core *core_data = data; + + goodix_ts_release_connects(core_data); + return 0; +} + +static int goodix_ts_get_irq_num(void *data) +{ + struct goodix_ts_core *core_data = data; + + return core_data->irq; +} + +static irqreturn_t goodix_irq_handler(int irq, void *data) +{ + struct goodix_ts_core *core_data = data; + + if (!mutex_trylock(&core_data->tui_transition_lock)) + return IRQ_HANDLED; + + goodix_ts_threadirq_func(irq, data); + mutex_unlock(&core_data->tui_transition_lock); + + return IRQ_HANDLED; +} + +static void goodix_ts_fill_qts_vendor_data(struct qts_vendor_data *qts_vendor_data, + struct goodix_ts_core *core_data) +{ + struct goodix_bus_interface *bus_interface; + struct device_node *node; + const char *touch_type; + int rc = 0; + + bus_interface = core_data->bus; + node = bus_interface->dev->of_node; + + rc = of_property_read_string(node, "goodix,touch-type", &touch_type); + if (rc) { + ts_err("No touch type\n"); + return; + } + + if (!strcmp(touch_type, "primary")) + qts_vendor_data->client_type = QTS_CLIENT_PRIMARY_TOUCH; + else + qts_vendor_data->client_type = QTS_CLIENT_SECONDARY_TOUCH; + + switch (bus_interface->bus_type) { + case GOODIX_BUS_TYPE_I2C: + qts_vendor_data->client = to_i2c_client(bus_interface->dev); + qts_vendor_data->spi = NULL; + qts_vendor_data->bus_type = QTS_BUS_TYPE_I2C; + break; + + case GOODIX_BUS_TYPE_SPI: + qts_vendor_data->client = NULL; + qts_vendor_data->spi = to_spi_device(bus_interface->dev); + qts_vendor_data->bus_type = QTS_BUS_TYPE_SPI; + break; + + default: + ts_err("Invalid bus type :%d\n", + bus_interface->bus_type); + break; + } + + qts_vendor_data->vendor_data = core_data; + qts_vendor_data->schedule_suspend = false; + qts_vendor_data->schedule_resume = false; + qts_vendor_data->qts_vendor_ops.suspend = goodix_ts_suspend_helper; + qts_vendor_data->qts_vendor_ops.resume = goodix_ts_resume_helper; + qts_vendor_data->qts_vendor_ops.enable_touch_irq = goodix_ts_enable_touch_irq; + qts_vendor_data->qts_vendor_ops.get_irq_num = goodix_ts_get_irq_num; + qts_vendor_data->qts_vendor_ops.pre_la_tui_enable = goodix_ts_pre_la_tui_enable; + qts_vendor_data->qts_vendor_ops.post_la_tui_enable = goodix_ts_post_la_tui_enable; + qts_vendor_data->qts_vendor_ops.pre_la_tui_disable = NULL; + qts_vendor_data->qts_vendor_ops.post_la_tui_disable = goodix_ts_post_la_tui_disable; + qts_vendor_data->qts_vendor_ops.pre_le_tui_enable = NULL; + qts_vendor_data->qts_vendor_ops.post_le_tui_enable = goodix_ts_post_le_tui_enable; + qts_vendor_data->qts_vendor_ops.pre_le_tui_disable = NULL; + qts_vendor_data->qts_vendor_ops.post_le_tui_disable = goodix_ts_post_le_tui_disable; + qts_vendor_data->qts_vendor_ops.irq_handler = goodix_irq_handler; +} + +/** + * goodix_ts_probe - called by kernel when Goodix touch + * platform driver is added. + */ +static int goodix_ts_probe(struct platform_device *pdev) +{ + struct goodix_ts_core *core_data = NULL; + struct goodix_bus_interface *bus_interface; + int ret; + struct device_node *node; + bool qts_en = false; + struct qts_vendor_data qts_vendor_data; + bool is_primary; + + ts_info("goodix_ts_probe IN"); + + if (!core_module_prob_sate) + mutex_init(&goodix_later_init_tmutex); + + bus_interface = pdev->dev.platform_data; + if (!bus_interface) { + ts_err("Invalid touch device"); + core_module_prob_sate = CORE_MODULE_PROB_FAILED; + return -ENODEV; + } + node = bus_interface->dev->of_node; + is_primary = (goodix_get_touch_type(node) == PRIMARY_TOUCH_IDX) ? 1 : 0; + +#if defined(CONFIG_DRM) + ret = goodix_check_dt(node); + if (ret == -EPROBE_DEFER) + return ret; + + if (ret) { + if (!goodix_check_default_tp(node, "qcom,touch-active")) + ret = -EPROBE_DEFER; + else + ret = -ENODEV; + + return ret; + } +#endif + + core_data = devm_kzalloc(&pdev->dev, + sizeof(struct goodix_ts_core), GFP_KERNEL); + if (!core_data) { + core_module_prob_sate = CORE_MODULE_PROB_FAILED; + return -ENOMEM; + } + + core_data->bus = bus_interface; + + if (IS_ENABLED(CONFIG_OF) && bus_interface->dev->of_node) { + /* parse devicetree property */ + ret = goodix_parse_dt(node, &core_data->board_data); + if (ret) { + ts_err("failed parse device info form dts, %d", ret); + ret = -EINVAL; + goto err_out; + } +#if defined(CONFIG_DRM) + of_property_read_string(node, "qcom,touch-environment", + &core_data->touch_environment); +#endif + } else { + ts_err("no valid device tree node found"); + ret = -ENODEV; + goto err_out; + } + + core_data->hw_ops = goodix_get_hw_ops(); + if (!core_data->hw_ops) { + ts_err("hw ops is NULL"); + ret = -EINVAL; + goto err_out; + } + + if (is_primary) + goodix_core_module_init(); + /* touch core layer is a platform driver */ + core_data->pdev = pdev; + platform_set_drvdata(pdev, core_data); + + /* get GPIO resource */ +#ifdef CONFIG_ARCH_QTI_VM + goto skip_to_power_gpio_setup; +#endif + ret = goodix_ts_gpio_setup(core_data); + if (ret) { + ts_err("failed init gpio"); + goto err_out; + } + + ret = goodix_ts_power_init(core_data); + if (ret) { + ts_err("failed init power"); + goto err_out; + } + + ret = goodix_ts_power_on(core_data); + if (ret) { + ts_err("failed power on"); + goto err_out; + } + +#ifdef CONFIG_ARCH_QTI_VM +skip_to_power_gpio_setup: +#endif + + qts_en = of_property_read_bool(node, "goodix,qts_en"); + if (qts_en) { + mutex_init(&core_data->tui_transition_lock); + goodix_ts_fill_qts_vendor_data(&qts_vendor_data, core_data); + + ret = qts_client_register(qts_vendor_data); + if (ret) { + pr_err("qts client register failed, rc %d\n", ret); + goto err_out; + } + core_data->qts_en = qts_en; + } + + /* generic notifier callback */ + core_data->ts_notifier.notifier_call = goodix_generic_noti_callback; + goodix_ts_register_notifier(&core_data->ts_notifier); + + /* debug node init */ + if (is_primary) + goodix_tools_init(); + + core_data->init_stage = CORE_INIT_STAGE1; + goodix_modules.core_data = core_data; + core_module_prob_sate = CORE_MODULE_PROB_SUCCESS; + core_data->ready = true; + + /* Try start a thread to get config-bin info */ + goodix_start_later_init(core_data); + + ts_info("goodix_ts_core probe success"); + return 0; + +err_out: + devm_kfree(&pdev->dev, core_data); + core_module_prob_sate = CORE_MODULE_PROB_FAILED; + ts_err("goodix_ts_core failed, ret:%d", ret); + return ret; +} + +static int goodix_ts_remove(struct platform_device *pdev) +{ + struct goodix_ts_core *core_data = platform_get_drvdata(pdev); + struct goodix_ts_hw_ops *hw_ops = core_data->hw_ops; + struct goodix_ts_esd *ts_esd = &core_data->ts_esd; + + goodix_ts_unregister_notifier(&core_data->ts_notifier); + goodix_tools_exit(); + + if (core_data->init_stage >= CORE_INIT_STAGE2) { + #ifdef GOODIX_SUSPEND_GESTURE_ENABLE + gesture_module_exit(); + #endif + inspect_module_exit(); + hw_ops->irq_enable(core_data, false); + + #if defined(CONFIG_DRM) + if (core_data->notifier_cookie) + panel_event_notifier_unregister(core_data->notifier_cookie); + #elif IS_ENABLED(CONFIG_FB) + fb_unregister_client(&core_data->fb_notifier); + #endif + core_module_prob_sate = CORE_MODULE_REMOVED; + if (atomic_read(&core_data->ts_esd.esd_on)) + goodix_ts_esd_off(core_data); + goodix_ts_unregister_notifier(&ts_esd->esd_notifier); + + goodix_fw_update_uninit(); + goodix_ts_input_dev_remove(core_data); + goodix_ts_pen_dev_remove(core_data); + goodix_ts_sysfs_exit(core_data); + goodix_ts_procfs_exit(core_data); + goodix_ts_power_off(core_data); + } + + return 0; +} + +#if defined(CONFIG_PM) && !defined(CONFIG_DRM) +static const struct dev_pm_ops dev_pm_ops = { +#if !defined(CONFIG_FB) && !defined(CONFIG_HAS_EARLYSUSPEND) + .suspend = goodix_ts_pm_suspend, + .resume = goodix_ts_pm_resume, +#endif +}; +#endif + +static const struct platform_device_id ts_core_ids[] = { + {.name = GOODIX_CORE_DEVICE_NAME}, + {.name = GOODIX_CORE_DEVICE_2_NAME}, + {} +}; +MODULE_DEVICE_TABLE(platform, ts_core_ids); + +static struct platform_driver goodix_ts_driver = { + .driver = { + .name = GOODIX_CORE_DRIVER_NAME, + .owner = THIS_MODULE, +#if defined(CONFIG_PM) && !defined(CONFIG_DRM) + .pm = &dev_pm_ops, +#endif + }, + .probe = goodix_ts_probe, + .remove = goodix_ts_remove, + .id_table = ts_core_ids, +}; + +static int __init goodix_ts_core_init(void) +{ + int ret = 0; + + ts_info("Core layer init:%s", GOODIX_DRIVER_VERSION); + + ret = goodix_spi_bus_init(); + ret |= goodix_i2c_bus_init(); + if (ret) { + ts_err("failed add bus driver"); + return ret; + } + return platform_driver_register(&goodix_ts_driver); +} + +static void __exit goodix_ts_core_exit(void) +{ + ts_info("Core layer exit"); + platform_driver_unregister(&goodix_ts_driver); + + goodix_spi_bus_exit(); + + goodix_i2c_bus_exit(); +} + +#ifdef CONFIG_ARCH_QTI_VM +module_init(goodix_ts_core_init); +#else +late_initcall(goodix_ts_core_init); +#endif +module_exit(goodix_ts_core_exit); + +MODULE_DESCRIPTION("Goodix Touchscreen Core Module"); +MODULE_AUTHOR("Goodix, Inc."); +MODULE_LICENSE("GPL v2"); diff --git a/qcom/opensource/touch-drivers/goodix_berlin_driver/goodix_ts_core.h b/qcom/opensource/touch-drivers/goodix_berlin_driver/goodix_ts_core.h new file mode 100644 index 0000000000..debfddaf66 --- /dev/null +++ b/qcom/opensource/touch-drivers/goodix_berlin_driver/goodix_ts_core.h @@ -0,0 +1,707 @@ +/* + * Goodix Touchscreen Driver + * Copyright (C) 2020 - 2021 Goodix, Inc. + * + * 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 a reference + * to you, when you are integrating the GOODiX's CTP IC into your system, + * 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. + * + */ +#ifndef _GOODIX_TS_CORE_H_ +#define _GOODIX_TS_CORE_H_ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if IS_ENABLED(CONFIG_OF) +#include +#include +#endif +#if IS_ENABLED(CONFIG_FB) +#include +#include +#endif +#include +#include +#include "../qts/qts_core_common.h" + +#define GOODIX_CORE_DRIVER_NAME "goodix_ts" +#define GOODIX_PEN_DRIVER_NAME "goodix_ts,pen" +#define GOODIX_CORE_DEVICE_NAME "goodix_ts" +#define GOODIX_CORE_DEVICE_2_NAME "goodix_ts2" +#define GOODIX_DRIVER_VERSION "v1.2.4" +#define GOODIX_MAX_TOUCH 10 +#define GOODIX_PEN_MAX_PRESSURE 4096 +#define GOODIX_MAX_PEN_KEY 2 +#define GOODIX_PEN_MAX_TILT 90 +#define GOODIX_CFG_MAX_SIZE 4096 +#define GOODIX_FW_MAX_SIEZE (300 * 1024) +#define GOODIX_MAX_STR_LABLE_LEN 32 +#define GOODIX_MAX_FRAMEDATA_LEN 1700 +#define GOODIX_GESTURE_DATA_LEN 16 + +#define GOODIX_NORMAL_RESET_DELAY_MS 100 +#define GOODIX_HOLD_CPU_RESET_DELAY_MS 5 + +#define GOODIX_RETRY_3 3 +#define GOODIX_RETRY_5 5 +#define GOODIX_RETRY_10 10 + +#define TS_DEFAULT_FIRMWARE "goodix_firmware.bin" +#define TS_DEFAULT_CFG_BIN "goodix_cfg_group.bin" + +enum GOODIX_GESTURE_TYP { + GESTURE_SINGLE_TAP = (1 << 0), + GESTURE_DOUBLE_TAP = (1 << 1), + GESTURE_FOD_PRESS = (1 << 2) +}; + +enum CORD_PROB_STA { + CORE_MODULE_UNPROBED = 0, + CORE_MODULE_PROB_SUCCESS = 1, + CORE_MODULE_PROB_FAILED = -1, + CORE_MODULE_REMOVED = -2, +}; + +enum GOODIX_ERR_CODE { + GOODIX_EBUS = (1<<0), + GOODIX_ECHECKSUM = (1<<1), + GOODIX_EVERSION = (1<<2), + GOODIX_ETIMEOUT = (1<<3), + GOODIX_EMEMCMP = (1<<4), + + GOODIX_EOTHER = (1<<7) +}; + +enum IC_TYPE_ID { + IC_TYPE_NONE, + IC_TYPE_NORMANDY, + IC_TYPE_NANJING, + IC_TYPE_YELLOWSTONE, + IC_TYPE_BERLIN_A, + IC_TYPE_BERLIN_B, + IC_TYPE_BERLIN_D +}; + +enum GOODIX_IC_CONFIG_TYPE { + CONFIG_TYPE_TEST = 0, + CONFIG_TYPE_NORMAL = 1, + CONFIG_TYPE_HIGHSENSE = 2, + CONFIG_TYPE_CHARGER = 3, + CONFIG_TYPE_CHARGER_HS = 4, + CONFIG_TYPE_HOLSTER = 5, + CONFIG_TYPE_HOSTER_CH = 6, + CONFIG_TYPE_OTHER = 7, + /* keep this at the last */ + GOODIX_MAX_CONFIG_GROUP = 8, +}; + +enum CHECKSUM_MODE { + CHECKSUM_MODE_U8_LE, + CHECKSUM_MODE_U16_LE, +}; + +enum DUAL_TOUCH_TYPE { + PRIMARY_TOUCH_IDX, + SECONDARY_TOUCH_IDX, + MAX_SUPPORTED_TOUCH_PANELS, +}; + +#define MAX_SCAN_FREQ_NUM 8 +#define MAX_SCAN_RATE_NUM 8 +#define MAX_FREQ_NUM_STYLUS 8 +#define MAX_STYLUS_SCAN_FREQ_NUM 6 +#pragma pack(1) +struct frame_head { + uint8_t sync; + uint16_t frame_index; + uint16_t cur_frame_len; + uint16_t next_frame_len; + uint32_t data_en; /* 0- 7 for pack_en; 8 - 31 for type en */ + uint8_t touch_pack_index; + uint8_t stylus_pack_index; + uint8_t res; + uint16_t checksum; +}; + +struct goodix_fw_version { + u8 rom_pid[6]; /* rom PID */ + u8 rom_vid[3]; /* Mask VID */ + u8 rom_vid_reserved; + u8 patch_pid[8]; /* Patch PID */ + u8 patch_vid[4]; /* Patch VID */ + u8 patch_vid_reserved; + u8 sensor_id; + u8 reserved[2]; + u16 checksum; +}; + +struct goodix_ic_info_version { + u8 info_customer_id; + u8 info_version_id; + u8 ic_die_id; + u8 ic_version_id; + u32 config_id; + u8 config_version; + u8 frame_data_customer_id; + u8 frame_data_version_id; + u8 touch_data_customer_id; + u8 touch_data_version_id; + u8 reserved[3]; +}; + +struct goodix_ic_info_feature { /* feature info*/ + u16 freqhop_feature; + u16 calibration_feature; + u16 gesture_feature; + u16 side_touch_feature; + u16 stylus_feature; +}; + +struct goodix_ic_info_param { /* param */ + u8 drv_num; + u8 sen_num; + u8 button_num; + u8 force_num; + u8 active_scan_rate_num; + u16 active_scan_rate[MAX_SCAN_RATE_NUM]; + u8 mutual_freq_num; + u16 mutual_freq[MAX_SCAN_FREQ_NUM]; + u8 self_tx_freq_num; + u16 self_tx_freq[MAX_SCAN_FREQ_NUM]; + u8 self_rx_freq_num; + u16 self_rx_freq[MAX_SCAN_FREQ_NUM]; + u8 stylus_freq_num; + u16 stylus_freq[MAX_FREQ_NUM_STYLUS]; +}; + +struct goodix_ic_info_misc { /* other data */ + u32 cmd_addr; + u16 cmd_max_len; + u32 cmd_reply_addr; + u16 cmd_reply_len; + u32 fw_state_addr; + u16 fw_state_len; + u32 fw_buffer_addr; + u16 fw_buffer_max_len; + u32 frame_data_addr; + u16 frame_data_head_len; + u16 fw_attr_len; + u16 fw_log_len; + u8 pack_max_num; + u8 pack_compress_version; + u16 stylus_struct_len; + u16 mutual_struct_len; + u16 self_struct_len; + u16 noise_struct_len; + u32 touch_data_addr; + u16 touch_data_head_len; + u16 point_struct_len; + u16 reserved1; + u16 reserved2; + u32 mutual_rawdata_addr; + u32 mutual_diffdata_addr; + u32 mutual_refdata_addr; + u32 self_rawdata_addr; + u32 self_diffdata_addr; + u32 self_refdata_addr; + u32 iq_rawdata_addr; + u32 iq_refdata_addr; + u32 im_rawdata_addr; + u16 im_readata_len; + u32 noise_rawdata_addr; + u16 noise_rawdata_len; + u32 stylus_rawdata_addr; + u16 stylus_rawdata_len; + u32 noise_data_addr; + u32 esd_addr; +}; + +struct goodix_ic_info { + u16 length; + struct goodix_ic_info_version version; + struct goodix_ic_info_feature feature; + struct goodix_ic_info_param parm; + struct goodix_ic_info_misc misc; +}; +#pragma pack() + +/* + * struct ts_rawdata_info + * + */ +#define TS_RAWDATA_BUFF_MAX 7000 +#define TS_RAWDATA_RESULT_MAX 100 +struct ts_rawdata_info { + int used_size; //fill in rawdata size + s16 buff[TS_RAWDATA_BUFF_MAX]; + char result[TS_RAWDATA_RESULT_MAX]; +}; + +/* + * struct goodix_module - external modules container + * @head: external modules list + * @initilized: whether this struct is initilized + * @mutex: mutex lock + * @wq: workqueue to do register work + * @core_data: core_data pointer + */ +struct goodix_module { + struct list_head head; + bool initilized; + struct mutex mutex; + struct workqueue_struct *wq; + struct goodix_ts_core *core_data; +}; + +/* + * struct goodix_ts_board_data - board data + * @avdd_name: name of analoy regulator + * @iovdd_name: name of analoy regulator + * @reset_gpio: reset gpio number + * @irq_gpio: interrupt gpio number + * @irq_flag: irq trigger type + * @swap_axis: whether swaw x y axis + * @panel_max_x/y/w/p: resolution and size + * @invert_xy: invert x and y for inversely mounted IC + * @pannel_key_map: key map + * @fw_name: name of the firmware image + */ +struct goodix_ts_board_data { + char avdd_name[GOODIX_MAX_STR_LABLE_LEN]; + char iovdd_name[GOODIX_MAX_STR_LABLE_LEN]; + int reset_gpio; + int irq_gpio; + int avdd_gpio; + int iovdd_gpio; + unsigned int irq_flags; + + unsigned int swap_axis; + unsigned int panel_max_x; + unsigned int panel_max_y; + unsigned int panel_max_w; /*major and minor*/ + unsigned int panel_max_p; /*pressure*/ + bool invert_xy; + + bool pen_enable; + char fw_name[GOODIX_MAX_STR_LABLE_LEN]; + char cfg_bin_name[GOODIX_MAX_STR_LABLE_LEN]; +}; + +enum goodix_fw_update_mode { + UPDATE_MODE_DEFAULT = 0, + UPDATE_MODE_FORCE = (1 << 0), /* force update mode */ + UPDATE_MODE_BLOCK = (1 << 1), /* update in block mode */ + UPDATE_MODE_FLASH_CFG = (1 << 2), /* reflash config */ + UPDATE_MODE_SRC_SYSFS = (1 << 4), /* firmware file from sysfs */ + UPDATE_MODE_SRC_HEAD = (1 << 5), /* firmware file from head file */ + UPDATE_MODE_SRC_REQUEST = (1 << 6), /* request firmware */ + UPDATE_MODE_SRC_ARGS = (1 << 7), /* firmware data from function args */ +}; + +#define MAX_CMD_DATA_LEN 10 +#define MAX_CMD_BUF_LEN 16 +#pragma pack(1) +struct goodix_ts_cmd { + union { + struct { + u8 state; + u8 ack; + u8 len; + u8 cmd; + u8 data[MAX_CMD_DATA_LEN]; + }; + u8 buf[MAX_CMD_BUF_LEN]; + }; +}; +#pragma pack() + +/* interrupt event type */ +enum ts_event_type { + EVENT_INVALID = 0, + EVENT_TOUCH = (1 << 0), /* finger touch event */ + EVENT_PEN = (1 << 1), /* pen event */ + EVENT_REQUEST = (1 << 2), + EVENT_GESTURE = (1 << 3), +}; + +enum ts_request_type { + REQUEST_TYPE_CONFIG = 1, + REQUEST_TYPE_RESET = 3, +}; + +/* notifier event */ +enum ts_notify_event { + NOTIFY_FWUPDATE_START, + NOTIFY_FWUPDATE_FAILED, + NOTIFY_FWUPDATE_SUCCESS, + NOTIFY_SUSPEND, + NOTIFY_RESUME, + NOTIFY_ESD_OFF, + NOTIFY_ESD_ON, + NOTIFY_CFG_BIN_FAILED, + NOTIFY_CFG_BIN_SUCCESS, +}; + +enum touch_point_status { + TS_NONE, + TS_RELEASE, + TS_TOUCH, +}; +/* coordinate package */ +struct goodix_ts_coords { + int status; /* NONE, RELEASE, TOUCH */ + unsigned int x, y, w, p; +}; + +struct goodix_pen_coords { + int status; /* NONE, RELEASE, TOUCH */ + int tool_type; /* BTN_TOOL_RUBBER BTN_TOOL_PEN */ + unsigned int x, y, p; + signed char tilt_x; + signed char tilt_y; +}; + +/* touch event data */ +struct goodix_touch_data { + int touch_num; + struct goodix_ts_coords coords[GOODIX_MAX_TOUCH]; +}; + +struct goodix_ts_key { + int status; + int code; +}; + +struct goodix_pen_data { + struct goodix_pen_coords coords; + struct goodix_ts_key keys[GOODIX_MAX_PEN_KEY]; +}; + +/* + * struct goodix_ts_event - touch event struct + * @event_type: touch event type, touch data or + * request event + * @event_data: event data + */ +struct goodix_ts_event { + enum ts_event_type event_type; + u8 request_code; /* represent the request type */ + u8 gesture_type; + u8 gesture_data[GOODIX_GESTURE_DATA_LEN]; + struct goodix_touch_data touch_data; + struct goodix_pen_data pen_data; +}; + +enum goodix_ic_bus_type { + GOODIX_BUS_TYPE_I2C, + GOODIX_BUS_TYPE_SPI, + GOODIX_BUS_TYPE_I3C, +}; + +struct goodix_bus_interface { + int bus_type; + int ic_type; + struct device *dev; + int (*read)(struct device *dev, unsigned int addr, + unsigned char *data, unsigned int len); + int (*write)(struct device *dev, unsigned int addr, + unsigned char *data, unsigned int len); +}; + +struct goodix_ts_hw_ops { + int (*power_on)(struct goodix_ts_core *cd, bool on); + int (*resume)(struct goodix_ts_core *cd); + int (*suspend)(struct goodix_ts_core *cd); + int (*gesture)(struct goodix_ts_core *cd, int gesture_type); + int (*reset)(struct goodix_ts_core *cd, int delay_ms); + int (*irq_enable)(struct goodix_ts_core *cd, bool enable); + int (*read)(struct goodix_ts_core *cd, unsigned int addr, + unsigned char *data, unsigned int len); + int (*write)(struct goodix_ts_core *cd, unsigned int addr, + unsigned char *data, unsigned int len); + int (*send_cmd)(struct goodix_ts_core *cd, + struct goodix_ts_cmd *cmd); + int (*send_config)(struct goodix_ts_core *cd, + u8 *config, int len); + int (*read_config)(struct goodix_ts_core *cd, + u8 *config_data, int size); + int (*read_version)(struct goodix_ts_core *cd, + struct goodix_fw_version *version); + int (*get_ic_info)(struct goodix_ts_core *cd, + struct goodix_ic_info *ic_info); + int (*esd_check)(struct goodix_ts_core *cd); + int (*event_handler)(struct goodix_ts_core *cd, + struct goodix_ts_event *ts_event); + int (*after_event_handler)(struct goodix_ts_core *cd); + int (*get_capacitance_data)(struct goodix_ts_core *cd, + struct ts_rawdata_info *info); +}; + +/* + * struct goodix_ts_esd - esd protector structure + * @esd_work: esd delayed work + * @esd_on: 1 - turn on esd protection, 0 - turn + * off esd protection + */ +struct goodix_ts_esd { + bool irq_status; + atomic_t esd_on; + struct delayed_work esd_work; + struct notifier_block esd_notifier; + struct goodix_ts_core *ts_core; +}; + +enum goodix_core_init_stage { + CORE_UNINIT, + CORE_INIT_FAIL, + CORE_INIT_STAGE1, + CORE_INIT_STAGE2 +}; + +struct goodix_ic_config { + int len; + u8 data[GOODIX_CFG_MAX_SIZE]; +}; + +struct goodix_ts_core { + int init_stage; + struct platform_device *pdev; + struct goodix_fw_version fw_version; + struct goodix_ic_info ic_info; + struct goodix_bus_interface *bus; + struct goodix_ts_board_data board_data; + struct goodix_ts_hw_ops *hw_ops; + struct input_dev *input_dev; + struct input_dev *pen_dev; + /* TODO counld we remove this from core data? */ + struct goodix_ts_event ts_event; + + /* every pointer of this array represent a kind of config */ + struct goodix_ic_config *ic_configs[GOODIX_MAX_CONFIG_GROUP]; + struct regulator *avdd; + struct regulator *iovdd; + unsigned char gesture_type; + + int power_on; + int irq; + size_t irq_trig_cnt; + + atomic_t irq_enabled; + atomic_t suspended; + /* when this flag is true, driver should not clean the sync flag */ + bool tools_ctrl_sync; + + struct notifier_block ts_notifier; + struct goodix_ts_esd ts_esd; + bool esd_initialized; + bool ready; +#if defined(CONFIG_DRM) + struct notifier_block fb_notifier; + void *notifier_cookie; + const char *touch_environment; +#elif defined(CONFIG_FB) + struct notifier_block fb_notifier; +#endif + +#ifdef CONFIG_GOODIX_TRUSTED_TOUCH + struct trusted_touch_vm_info *vm_info; + struct mutex fts_clk_io_ctrl_mutex; + struct completion trusted_touch_powerdown; + struct clk *core_clk; + struct clk *iface_clk; + atomic_t trusted_touch_initialized; + atomic_t trusted_touch_enabled; + atomic_t trusted_touch_transition; + atomic_t trusted_touch_event; + atomic_t trusted_touch_abort_status; + atomic_t delayed_vm_probe_pending; + atomic_t trusted_touch_mode; +#endif + bool qts_en; + struct mutex tui_transition_lock; +}; + +/* external module structures */ +enum goodix_ext_priority { + EXTMOD_PRIO_RESERVED = 0, + EXTMOD_PRIO_FWUPDATE, + EXTMOD_PRIO_GESTURE, + EXTMOD_PRIO_HOTKNOT, + EXTMOD_PRIO_DBGTOOL, + EXTMOD_PRIO_DEFAULT, +}; + +#define EVT_HANDLED 0 +#define EVT_CONTINUE 0 +#define EVT_CANCEL 1 +#define EVT_CANCEL_IRQEVT 1 +#define EVT_CANCEL_SUSPEND 1 +#define EVT_CANCEL_RESUME 1 +#define EVT_CANCEL_RESET 1 + +struct goodix_ext_module; +/* external module's operations callback */ +struct goodix_ext_module_funcs { + int (*init)(struct goodix_ts_core *core_data, + struct goodix_ext_module *module); + int (*exit)(struct goodix_ts_core *core_data, + struct goodix_ext_module *module); + int (*before_reset)(struct goodix_ts_core *core_data, + struct goodix_ext_module *module); + int (*after_reset)(struct goodix_ts_core *core_data, + struct goodix_ext_module *module); + int (*before_suspend)(struct goodix_ts_core *core_data, + struct goodix_ext_module *module); + int (*after_suspend)(struct goodix_ts_core *core_data, + struct goodix_ext_module *module); + int (*before_resume)(struct goodix_ts_core *core_data, + struct goodix_ext_module *module); + int (*after_resume)(struct goodix_ts_core *core_data, + struct goodix_ext_module *module); + int (*irq_event)(struct goodix_ts_core *core_data, + struct goodix_ext_module *module); +}; + +/* + * struct goodix_ext_module - external module struct + * @list: list used to link into modules manager + * @name: name of external module + * @priority: module priority vlaue, zero is invalid + * @funcs: operations callback + * @priv_data: private data region + * @kobj: kobject + * @work: used to queue one work to do registration + */ +struct goodix_ext_module { + struct list_head list; + char *name; + enum goodix_ext_priority priority; + const struct goodix_ext_module_funcs *funcs; + void *priv_data; + struct kobject kobj; + struct work_struct work; +}; + +/* + * struct goodix_ext_attribute - exteranl attribute struct + * @attr: attribute + * @show: show interface of external attribute + * @store: store interface of external attribute + */ +struct goodix_ext_attribute { + struct attribute attr; + ssize_t (*show)(struct goodix_ext_module *module, char *buf); + ssize_t (*store)(struct goodix_ext_module *module, + const char *buf, size_t len); +}; + +/* external attrs helper macro */ +#define __EXTMOD_ATTR(_name, _mode, _show, _store) { \ + .attr = {.name = __stringify(_name), .mode = _mode }, \ + .show = _show, \ + .store = _store, \ +} + +/* external attrs helper macro, used to define external attrs */ +#define DEFINE_EXTMOD_ATTR(_name, _mode, _show, _store) \ +static struct goodix_ext_attribute ext_attr_##_name = \ + __EXTMOD_ATTR(_name, _mode, _show, _store) + +/* log macro */ +extern bool debug_log_flag; +#define ts_info(fmt, arg...) \ + pr_info("[GTP-INF][%s:%d] "fmt"\n", __func__, __LINE__, ##arg) +#define ts_err(fmt, arg...) \ + pr_err("[GTP-ERR][%s:%d] "fmt"\n", __func__, __LINE__, ##arg) +#define ts_debug(fmt, arg...) \ + {if (debug_log_flag) \ + pr_info("[GTP-DBG][%s:%d] "fmt"\n", __func__, __LINE__, ##arg);} + +/* + * get board data pointer + */ +static inline struct goodix_ts_board_data *board_data( + struct goodix_ts_core *core) +{ + if (!core) + return NULL; + return &(core->board_data); +} + +/** + * goodix_register_ext_module - interface for external module + * to register into touch core modules structure + * + * @module: pointer to external module to be register + * return: 0 ok, <0 failed + */ +int goodix_register_ext_module(struct goodix_ext_module *module); +/* register module no wait */ +int goodix_register_ext_module_no_wait(struct goodix_ext_module *module); +/** + * goodix_unregister_ext_module - interface for external module + * to unregister external modules + * + * @module: pointer to external module + * return: 0 ok, <0 failed + */ +int goodix_unregister_ext_module(struct goodix_ext_module *module); +/* remove all registered ext module + * return 0 on success, otherwise return < 0 + */ +int goodix_ts_blocking_notify(enum ts_notify_event evt, void *v); +struct kobj_type *goodix_get_default_ktype(void); +struct kobject *goodix_get_default_kobj(void); + +struct goodix_ts_hw_ops *goodix_get_hw_ops(void); +int goodix_get_config_proc(struct goodix_ts_core *cd); + +int goodix_spi_bus_init(void); +void goodix_spi_bus_exit(void); +int goodix_i2c_bus_init(void); +void goodix_i2c_bus_exit(void); + +u32 goodix_append_checksum(u8 *data, int len, int mode); +int checksum_cmp(const u8 *data, int size, int mode); +int is_risk_data(const u8 *data, int size); +u32 goodix_get_file_config_id(u8 *ic_config); +void goodix_rotate_abcd2cbad(int tx, int rx, s16 *data); + +int goodix_fw_update_init(struct goodix_ts_core *core_data); +void goodix_fw_update_uninit(void); +int goodix_do_fw_update(struct goodix_ic_config *ic_config, int mode); + +int goodix_get_ic_type(struct device_node *node); +int gesture_module_init(void); +void gesture_module_exit(void); +int inspect_module_init(void); +void inspect_module_exit(void); +int goodix_tools_init(void); +void goodix_tools_exit(void); +int goodix_get_touch_type(struct device_node *np); + +/* goodix FB test */ +/* +void goodix_fb_ext_ctrl(int suspend); +*/ + +#endif diff --git a/qcom/opensource/touch-drivers/goodix_berlin_driver/goodix_ts_gesture.c b/qcom/opensource/touch-drivers/goodix_berlin_driver/goodix_ts_gesture.c new file mode 100644 index 0000000000..f177a1db5d --- /dev/null +++ b/qcom/opensource/touch-drivers/goodix_berlin_driver/goodix_ts_gesture.c @@ -0,0 +1,446 @@ +/* + * Goodix Gesture Module + * + * Copyright (C) 2019 - 2020 Goodix, Inc. + * + * 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 a reference + * to you, when you are integrating the GOODiX's CTP IC into your system, + * 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "goodix_ts_core.h" + + +#define GOODIX_GESTURE_DOUBLE_TAP 0xCC +#define GOODIX_GESTURE_SINGLE_TAP 0x4C +#define GOODIX_GESTURE_FOD_DOWN 0x46 +#define GOODIX_GESTURE_FOD_UP 0x55 + +/* + * struct gesture_module - gesture module data + * @registered: module register state + * @sysfs_node_created: sysfs node state + * @gesture_type: valid gesture type, each bit represent one gesture type + * @gesture_data: store latest gesture code get from irq event + * @gesture_ts_cmd: gesture command data + */ +struct gesture_module { + atomic_t registered; + struct goodix_ts_core *ts_core; + struct goodix_ext_module module; +}; + +static struct gesture_module *gsx_gesture; /*allocated in gesture init module*/ +static bool module_initialized; + +static ssize_t gsx_double_type_show(struct goodix_ext_module *module, + char *buf) +{ + struct gesture_module *gsx = module->priv_data; + unsigned char type = gsx->ts_core->gesture_type; + + if (!gsx) + return -EIO; + + if (atomic_read(&gsx->registered) == 0) { + ts_err("gesture module is not registered"); + return 0; + } + + return scnprintf(buf, PAGE_SIZE, "%s\n", + (type & GESTURE_DOUBLE_TAP) ? "enable" : "disable"); +} + +static ssize_t gsx_double_type_store(struct goodix_ext_module *module, + const char *buf, size_t count) +{ + struct gesture_module *gsx = module->priv_data; + + if (!gsx) + return -EIO; + + if (atomic_read(&gsx->registered) == 0) { + ts_err("gesture module is not registered"); + return 0; + } + + if (buf[0] == '1') { + ts_info("enable double tap"); + gsx->ts_core->gesture_type |= GESTURE_DOUBLE_TAP; + } else if (buf[0] == '0') { + ts_info("disable double tap"); + gsx->ts_core->gesture_type &= ~GESTURE_DOUBLE_TAP; + } else + ts_err("invalid cmd[%d]", buf[0]); + + return count; +} + +static ssize_t gsx_single_type_show(struct goodix_ext_module *module, + char *buf) +{ + struct gesture_module *gsx = module->priv_data; + unsigned char type = gsx->ts_core->gesture_type; + + if (!gsx) + return -EIO; + + if (atomic_read(&gsx->registered) == 0) { + ts_err("gesture module is not registered"); + return 0; + } + + return scnprintf(buf, PAGE_SIZE, "%s\n", + (type & GESTURE_SINGLE_TAP) ? "enable" : "disable"); +} + +static ssize_t gsx_single_type_store(struct goodix_ext_module *module, + const char *buf, size_t count) +{ + struct gesture_module *gsx = module->priv_data; + + if (!gsx) + return -EIO; + + if (atomic_read(&gsx->registered) == 0) { + ts_err("gesture module is not registered"); + return 0; + } + + if (buf[0] == '1') { + ts_info("enable single tap"); + gsx->ts_core->gesture_type |= GESTURE_SINGLE_TAP; + } else if (buf[0] == '0') { + ts_info("disable single tap"); + gsx->ts_core->gesture_type &= ~GESTURE_SINGLE_TAP; + } else + ts_err("invalid cmd[%d]", buf[0]); + + return count; +} + +static ssize_t gsx_fod_type_show(struct goodix_ext_module *module, + char *buf) +{ + struct gesture_module *gsx = module->priv_data; + unsigned char type = gsx->ts_core->gesture_type; + + if (!gsx) + return -EIO; + + if (atomic_read(&gsx->registered) == 0) { + ts_err("gesture module is not registered"); + return 0; + } + + return scnprintf(buf, PAGE_SIZE, "%s\n", + (type & GESTURE_FOD_PRESS) ? "enable" : "disable"); +} + +static ssize_t gsx_fod_type_store(struct goodix_ext_module *module, + const char *buf, size_t count) +{ + struct gesture_module *gsx = module->priv_data; + + if (!gsx) + return -EIO; + + if (atomic_read(&gsx->registered) == 0) { + ts_err("gesture module is not registered"); + return 0; + } + + if (buf[0] == '1') { + ts_info("enable fod"); + gsx->ts_core->gesture_type |= GESTURE_FOD_PRESS; + } else if (buf[0] == '0') { + ts_info("disable fod"); + gsx->ts_core->gesture_type &= ~GESTURE_FOD_PRESS; + } else + ts_err("invalid cmd[%d]", buf[0]); + + return count; +} + + +const struct goodix_ext_attribute gesture_attrs[] = { + __EXTMOD_ATTR(double_en, 0664, + gsx_double_type_show, gsx_double_type_store), + __EXTMOD_ATTR(single_en, 0664, + gsx_single_type_show, gsx_single_type_store), + __EXTMOD_ATTR(fod_en, 0664, + gsx_fod_type_show, gsx_fod_type_store), +}; + +static int gsx_gesture_init(struct goodix_ts_core *cd, + struct goodix_ext_module *module) +{ + struct gesture_module *gsx = module->priv_data; + + if (!cd || !cd->hw_ops->gesture) { + ts_err("gesture unsupported"); + return -EINVAL; + } + + gsx->ts_core = cd; + gsx->ts_core->gesture_type = 0; + atomic_set(&gsx->registered, 1); + + return 0; +} + +static int gsx_gesture_exit(struct goodix_ts_core *cd, + struct goodix_ext_module *module) +{ + struct gesture_module *gsx = module->priv_data; + + if (!cd || !cd->hw_ops->gesture) { + ts_err("gesture unsupported"); + return -EINVAL; + } + + atomic_set(&gsx->registered, 0); + + return 0; +} + +/** + * gsx_gesture_ist - Gesture Irq handle + * This functions is excuted when interrupt happended and + * ic in doze mode. + * + * @cd: pointer to touch core data + * @module: pointer to goodix_ext_module struct + * return: 0 goon execute, EVT_CANCEL_IRQEVT stop execute + */ +static int gsx_gesture_ist(struct goodix_ts_core *cd, + struct goodix_ext_module *module) +{ + struct goodix_ts_hw_ops *hw_ops = cd->hw_ops; + struct goodix_ts_event gs_event = {0}; + int fodx, fody, overlay_area; + int ret; + + if (atomic_read(&cd->suspended) == 0 || cd->gesture_type == 0) + return EVT_CONTINUE; + + ret = hw_ops->event_handler(cd, &gs_event); + if (ret) { + ts_err("failed get gesture data"); + goto re_send_ges_cmd; + } + + if (!(gs_event.event_type & EVENT_GESTURE)) { + ts_err("invalid event type: 0x%x", + cd->ts_event.event_type); + goto re_send_ges_cmd; + } + + switch (gs_event.gesture_type) { + case GOODIX_GESTURE_SINGLE_TAP: + if (cd->gesture_type & GESTURE_SINGLE_TAP) { + ts_info("get SINGLE-TAP gesture"); + input_report_key(cd->input_dev, KEY_WAKEUP, 1); + // input_report_key(cd->input_dev, KEY_GOTO, 1); + input_sync(cd->input_dev); + input_report_key(cd->input_dev, KEY_WAKEUP, 0); + // input_report_key(cd->input_dev, KEY_GOTO, 0); + input_sync(cd->input_dev); + } else { + ts_debug("not enable SINGLE-TAP"); + } + break; + case GOODIX_GESTURE_DOUBLE_TAP: + if (cd->gesture_type & GESTURE_DOUBLE_TAP) { + ts_info("get DOUBLE-TAP gesture"); + input_report_key(cd->input_dev, KEY_WAKEUP, 1); + input_sync(cd->input_dev); + input_report_key(cd->input_dev, KEY_WAKEUP, 0); + input_sync(cd->input_dev); + } else { + ts_debug("not enable DOUBLE-TAP"); + } + break; + case GOODIX_GESTURE_FOD_DOWN: + if (cd->gesture_type & GESTURE_FOD_PRESS) { + ts_info("get FOD-DOWN gesture"); + fodx = le16_to_cpup((__le16 *)gs_event.gesture_data); + fody = le16_to_cpup((__le16 *)(gs_event.gesture_data + 2)); + overlay_area = gs_event.gesture_data[4]; + ts_debug("fodx:%d fody:%d overlay_area:%d", fodx, fody, overlay_area); + input_report_key(cd->input_dev, BTN_TOUCH, 1); + input_mt_slot(cd->input_dev, 0); + input_mt_report_slot_state(cd->input_dev, MT_TOOL_FINGER, 1); + input_report_abs(cd->input_dev, ABS_MT_POSITION_X, fodx); + input_report_abs(cd->input_dev, ABS_MT_POSITION_Y, fody); + input_report_abs(cd->input_dev, ABS_MT_WIDTH_MAJOR, overlay_area); + input_sync(cd->input_dev); + } else { + ts_debug("not enable FOD-DOWN"); + } + break; + case GOODIX_GESTURE_FOD_UP: + if (cd->gesture_type & GESTURE_FOD_PRESS) { + ts_info("get FOD-UP gesture"); + fodx = le16_to_cpup((__le16 *)gs_event.gesture_data); + fody = le16_to_cpup((__le16 *)(gs_event.gesture_data + 2)); + overlay_area = gs_event.gesture_data[4]; + input_report_key(cd->input_dev, BTN_TOUCH, 0); + input_mt_slot(cd->input_dev, 0); + input_mt_report_slot_state(cd->input_dev, + MT_TOOL_FINGER, 0); + input_sync(cd->input_dev); + } else { + ts_debug("not enable FOD-UP"); + } + break; + default: + ts_err("not support gesture type[%02X]", gs_event.gesture_type); + break; + } + +re_send_ges_cmd: + if (hw_ops->gesture(cd, 0)) + ts_info("warning: failed re_send gesture cmd"); + return EVT_CANCEL_IRQEVT; +} + +/** + * gsx_gesture_before_suspend - execute gesture suspend routine + * This functions is excuted to set ic into doze mode + * + * @cd: pointer to touch core data + * @module: pointer to goodix_ext_module struct + * return: 0 goon execute, EVT_IRQCANCLED stop execute + */ +static int gsx_gesture_before_suspend(struct goodix_ts_core *cd, + struct goodix_ext_module *module) +{ + int ret; + const struct goodix_ts_hw_ops *hw_ops = cd->hw_ops; + + if (cd->gesture_type == 0) + return EVT_CONTINUE; + + ret = hw_ops->gesture(cd, 0); + if (ret) + ts_err("failed enter gesture mode"); + else + ts_info("enter gesture mode, type[0x%02X]", cd->gesture_type); + + hw_ops->irq_enable(cd, true); + enable_irq_wake(cd->irq); + + return EVT_CANCEL_SUSPEND; +} + +static int gsx_gesture_before_resume(struct goodix_ts_core *cd, + struct goodix_ext_module *module) +{ + const struct goodix_ts_hw_ops *hw_ops = cd->hw_ops; + + if (cd->gesture_type == 0) + return EVT_CONTINUE; + + disable_irq_wake(cd->irq); + hw_ops->reset(cd, GOODIX_NORMAL_RESET_DELAY_MS); + + return EVT_CANCEL_RESUME; +} + +static struct goodix_ext_module_funcs gsx_gesture_funcs = { + .irq_event = gsx_gesture_ist, + .init = gsx_gesture_init, + .exit = gsx_gesture_exit, + .before_suspend = gsx_gesture_before_suspend, + .before_resume = gsx_gesture_before_resume, +}; + +int gesture_module_init(void) +{ + int ret; + int i; + struct kobject *def_kobj = goodix_get_default_kobj(); + struct kobj_type *def_kobj_type = goodix_get_default_ktype(); + + gsx_gesture = kzalloc(sizeof(struct gesture_module), GFP_KERNEL); + if (!gsx_gesture) + return -ENOMEM; + + gsx_gesture->module.funcs = &gsx_gesture_funcs; + gsx_gesture->module.priority = EXTMOD_PRIO_GESTURE; + gsx_gesture->module.name = "Goodix_gsx_gesture"; + gsx_gesture->module.priv_data = gsx_gesture; + + atomic_set(&gsx_gesture->registered, 0); + + /* gesture sysfs init */ + ret = kobject_init_and_add(&gsx_gesture->module.kobj, + def_kobj_type, def_kobj, "gesture"); + if (ret) { + ts_err("failed create gesture sysfs node!"); + goto err_out; + } + + for (i = 0; i < ARRAY_SIZE(gesture_attrs) && !ret; i++) + ret = sysfs_create_file(&gsx_gesture->module.kobj, + &gesture_attrs[i].attr); + if (ret) { + ts_err("failed create gst sysfs files"); + while (--i >= 0) + sysfs_remove_file(&gsx_gesture->module.kobj, + &gesture_attrs[i].attr); + + kobject_put(&gsx_gesture->module.kobj); + goto err_out; + } + + module_initialized = true; + goodix_register_ext_module_no_wait(&gsx_gesture->module); + ts_info("gesture module init success"); + + return 0; + +err_out: + ts_err("gesture module init failed!"); + kfree(gsx_gesture); + return ret; +} + +void gesture_module_exit(void) +{ + int i; + + ts_info("gesture module exit"); + if (!module_initialized) + return; + + goodix_unregister_ext_module(&gsx_gesture->module); + + /* deinit sysfs */ + for (i = 0; i < ARRAY_SIZE(gesture_attrs); i++) + sysfs_remove_file(&gsx_gesture->module.kobj, + &gesture_attrs[i].attr); + + kobject_put(&gsx_gesture->module.kobj); + kfree(gsx_gesture); + module_initialized = false; +} diff --git a/qcom/opensource/touch-drivers/goodix_berlin_driver/goodix_ts_inspect.c b/qcom/opensource/touch-drivers/goodix_berlin_driver/goodix_ts_inspect.c new file mode 100644 index 0000000000..3dd2f4bcd4 --- /dev/null +++ b/qcom/opensource/touch-drivers/goodix_berlin_driver/goodix_ts_inspect.c @@ -0,0 +1,2981 @@ +/* + * Goodix Touchscreen Driver + * Copyright (C) 2020 - 2021 Goodix, Inc. + * + * 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 a reference + * to you, when you are integrating the GOODiX's CTP IC into your system, + * 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. + * + */ + +#include "goodix_ts_core.h" +#include +#include +#include +#include +#include + + +/* test config */ +#define TOTAL_FRAME_NUM 1 /* rawdata test frames */ +#define NOISEDATA_TEST_TIMES 1 /* noise test frames */ +// #define SAVE_IN_CSV + +#define GOODIX_RESULT_SAVE_PATH "/vendor/etc/Test_Data.csv" +#define GOODIX_TEST_FILE_NAME "goodix" +#define MAX_DATA_BUFFER 28000 +#define MAX_SHORT_NUM 15 +#define MAX_LINE_LEN (1024 * 3 * 7) +#define MAX_DRV_NUM 52 +#define MAX_SEN_NUM 75 + +#define STATISTICS_DATA_LEN 32 +#define MAX_STR_LEN 32 +#define MAX_TEST_ITEMS 10 /* 0P-1P-2P-3P-5P total test items */ +#define GTP_CAP_TEST 1 +#define GTP_DELTA_TEST 2 +#define GTP_NOISE_TEST 3 +#define GTP_SHORT_TEST 5 +#define GTP_SELFCAP_TEST 6 +#define GTP_SELFNOISE_TEST 7 + +#define GTP_TEST_PASS 1 +#define GTP_PANEL_REASON 2 +#define SYS_SOFTWARE_REASON 3 + +#define CHN_VDD 0xFF +#define CHN_GND 0x7F +#define DRV_CHANNEL_FLAG 0x80 + +#define CSV_TP_SPECIAL_RAW_MIN "special_raw_min" +#define CSV_TP_SPECIAL_RAW_MAX "special_raw_max" +#define CSV_TP_SPECIAL_RAW_DELTA "special_raw_delta" +#define CSV_TP_SHORT_THRESHOLD "shortciurt_threshold" +#define CSV_TP_SPECIAL_SELFRAW_MAX "special_selfraw_max" +#define CSV_TP_SPECIAL_SELFRAW_MIN "special_selfraw_min" +#define CSV_TP_NOISE_LIMIT "noise_data_limit" +#define CSV_TP_SELFNOISE_LIMIT "noise_selfdata_limit" +#define CSV_TP_TEST_CONFIG "test_config" + +#define MAX_TEST_TIME_MS 15000 +#define DEFAULT_TEST_TIME_MS 7000 + +/* berlin A */ +#define MAX_DRV_NUM_BRA 21 +#define MAX_SEN_NUM_BRA 42 +#define SHORT_TEST_TIME_REG_BRA 0x11FF2 +#define DFT_ADC_DUMP_NUM_BRA 1396 +#define DFT_SHORT_THRESHOLD_BRA 16 +#define DFT_DIFFCODE_SHORT_THRESHOLD_BRA 16 +#define SHORT_TEST_STATUS_REG_BRA 0x10400 +#define SHORT_TEST_RESULT_REG_BRA 0x10410 +#define DRV_DRV_SELFCODE_REG_BRA 0x1045E +#define SEN_SEN_SELFCODE_REG_BRA 0x1084E +#define DRV_SEN_SELFCODE_REG_BRA 0x11712 +#define DIFF_CODE_DATA_REG_BRA 0x11F72 + +/* berlin B */ +#define MAX_DRV_NUM_BRB 52 +#define MAX_SEN_NUM_BRB 75 +#define SHORT_TEST_TIME_REG_BRB 0x26AE0 +#define DFT_ADC_DUMP_NUM_BRB 762 +#define DFT_SHORT_THRESHOLD_BRB 100 +#define DFT_DIFFCODE_SHORT_THRESHOLD_BRB 32 +#define SHORT_TEST_STATUS_REG_BRB 0x20400 +#define SHORT_TEST_RESULT_REG_BRB 0x20410 +#define DRV_DRV_SELFCODE_REG_BRB 0x2049A +#define SEN_SEN_SELFCODE_REG_BRB 0x21AF2 +#define DRV_SEN_SELFCODE_REG_BRB 0x248A6 +#define DIFF_CODE_DATA_REG_BRB 0x269E0 + +/* berlinD */ +#define MAX_DRV_NUM_BRD 20 +#define MAX_SEN_NUM_BRD 40 +#define SHORT_TEST_TIME_REG_BRD 0x14D7A +#define DFT_ADC_DUMP_NUM_BRD 762 +#define DFT_SHORT_THRESHOLD_BRD 100 +#define DFT_DIFFCODE_SHORT_THRESHOLD_BRD 32 +#define SHORT_TEST_STATUS_REG_BRD 0x13400 +#define SHORT_TEST_RESULT_REG_BRD 0x13408 +#define DRV_DRV_SELFCODE_REG_BRD 0x1344E +#define SEN_SEN_SELFCODE_REG_BRD 0x137E6 +#define DRV_SEN_SELFCODE_REG_BRD 0x14556 +#define DIFF_CODE_DATA_REG_BRD 0x14D00 + + +#define ABS(val) ((val < 0)? -(val) : val) +#define MAX(a, b) ((a > b)? a : b) + +static bool module_initialized; + +/* berlin A drv-sen map */ +static u8 brl_a_drv_map[] = { + 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 62 +}; + +static u8 brl_a_sen_map[] = { + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41 +}; + +/* berlin B drv-sen map */ +static u8 brl_b_drv_map[] = { + 75, 76, 77, 78, 79, 80, 81, 82, + 83, 84, 85, 86, 87, 88, 89, 90, + 91, 92, 93, 94, 95, 96, 97, 98, + 99, 100, 101, 102, 103, 104, 105, + 106, 107, 108, 109, 110, 111, 112, + 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 126 +}; + +static u8 brl_b_sen_map[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 30, 31, 32, 33, 34, + 35, 36, 37, 38, 39, 40, 41, 42, + 43, 44, 45, 46, 47, 48, 49, 50, + 51, 52, 53, 54, 55, 56, 57, 58, + 59, 60, 61, 62, 63, 64, 65, 66, + 67, 68, 69, 70, 71, 72, 73, 74 +}; + +/* berlin D drv-sen map */ +static u8 brl_d_drv_map[] = { + 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, +}; + +static u8 brl_d_sen_map[] = { + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, +}; + +typedef struct __attribute__((packed)) { + u8 result; + u8 drv_drv_num; + u8 sen_sen_num; + u8 drv_sen_num; + u8 drv_gnd_avdd_num; + u8 sen_gnd_avdd_num; + u16 checksum; +} test_result_t; + +struct params_info_t { + u32 max_drv_num; + u32 max_sen_num; + u8 *drv_map; + u8 *sen_map; + u32 short_test_time_reg; + u32 short_test_status_reg; + u32 short_test_result_reg; + u32 drv_drv_selfcode_reg; + u32 sen_sen_selfcode_reg; + u32 drv_sen_selfcode_reg; + u32 diffcode_data_reg; + u16 short_test_dump_num; + u16 dft_short_threshold; + u16 short_diffcode_threshold; +}; + +struct params_info_t params_bra = { + MAX_DRV_NUM_BRA, + MAX_SEN_NUM_BRA, + brl_a_drv_map, + brl_a_sen_map, + SHORT_TEST_TIME_REG_BRA, + SHORT_TEST_STATUS_REG_BRA, + SHORT_TEST_RESULT_REG_BRA, + DRV_DRV_SELFCODE_REG_BRA, + SEN_SEN_SELFCODE_REG_BRA, + DRV_SEN_SELFCODE_REG_BRA, + DIFF_CODE_DATA_REG_BRA, + DFT_ADC_DUMP_NUM_BRA, + DFT_SHORT_THRESHOLD_BRA, + DFT_DIFFCODE_SHORT_THRESHOLD_BRA, +}; + +struct params_info_t params_brb = { + MAX_DRV_NUM_BRB, + MAX_SEN_NUM_BRB, + brl_b_drv_map, + brl_b_sen_map, + SHORT_TEST_TIME_REG_BRB, + SHORT_TEST_STATUS_REG_BRB, + SHORT_TEST_RESULT_REG_BRB, + DRV_DRV_SELFCODE_REG_BRB, + SEN_SEN_SELFCODE_REG_BRB, + DRV_SEN_SELFCODE_REG_BRB, + DIFF_CODE_DATA_REG_BRB, + DFT_ADC_DUMP_NUM_BRB, + DFT_SHORT_THRESHOLD_BRB, + DFT_DIFFCODE_SHORT_THRESHOLD_BRB, +}; + +struct params_info_t params_brd = { + MAX_DRV_NUM_BRD, + MAX_SEN_NUM_BRD, + brl_d_drv_map, + brl_d_sen_map, + SHORT_TEST_TIME_REG_BRD, + SHORT_TEST_STATUS_REG_BRD, + SHORT_TEST_RESULT_REG_BRD, + DRV_DRV_SELFCODE_REG_BRD, + SEN_SEN_SELFCODE_REG_BRD, + DRV_SEN_SELFCODE_REG_BRD, + DIFF_CODE_DATA_REG_BRD, + DFT_ADC_DUMP_NUM_BRD, + DFT_SHORT_THRESHOLD_BRD, + DFT_DIFFCODE_SHORT_THRESHOLD_BRD, +}; + +struct ts_test_params { + bool test_items[MAX_TEST_ITEMS]; + + u32 rawdata_addr; + u32 noisedata_addr; + u32 self_rawdata_addr; + u32 self_noisedata_addr; + + u32 drv_num; + u32 sen_num; + + struct params_info_t *params_info; + + s32 cfg_buf[GOODIX_CFG_MAX_SIZE]; + s32 max_limits[MAX_DRV_NUM * MAX_SEN_NUM]; + s32 min_limits[MAX_DRV_NUM * MAX_SEN_NUM]; + s32 deviation_limits[MAX_DRV_NUM * MAX_SEN_NUM]; + s32 self_max_limits[MAX_DRV_NUM + MAX_SEN_NUM]; + s32 self_min_limits[MAX_DRV_NUM + MAX_SEN_NUM]; + s32 noise_threshold; + s32 self_noise_threshold; + + u32 short_threshold; + u32 r_drv_drv_threshold; + u32 r_drv_sen_threshold; + u32 r_sen_sen_threshold; + u32 r_drv_gnd_threshold; + u32 r_sen_gnd_threshold; + u32 avdd_value; +}; + +struct ts_test_rawdata { + s16 data[MAX_DRV_NUM * MAX_SEN_NUM]; + u32 size; +}; + +struct ts_test_self_rawdata { + s16 data[MAX_DRV_NUM + MAX_SEN_NUM]; + u32 size; +}; + +struct ts_short_res { + u8 short_num; + s16 short_msg[4 * MAX_SHORT_NUM]; +}; + +struct ts_open_res { + u8 beyond_max_limit_cnt[MAX_DRV_NUM * MAX_SEN_NUM]; + u8 beyond_min_limit_cnt[MAX_DRV_NUM * MAX_SEN_NUM]; + u8 beyond_accord_limit_cnt[MAX_DRV_NUM * MAX_SEN_NUM]; +}; + +struct goodix_ts_test { + struct goodix_ts_core *ts; + struct ts_test_params test_params; + struct ts_test_rawdata rawdata[TOTAL_FRAME_NUM]; + struct ts_test_rawdata accord_arr[TOTAL_FRAME_NUM]; + struct ts_test_rawdata noisedata[NOISEDATA_TEST_TIMES]; + struct goodix_ic_config test_config; + struct ts_test_self_rawdata self_rawdata; + struct ts_test_self_rawdata self_noisedata; + struct ts_short_res short_res; + struct ts_open_res open_res; + + /*[0][0][0][0][0].. 0 without test; 1 pass, 2 panel failed; 3 software failed */ + char test_result[MAX_TEST_ITEMS]; + char test_info[TS_RAWDATA_RESULT_MAX]; +}; + +static int cal_cha_to_cha_res(struct goodix_ts_test *ts_test, int v1, int v2) +{ + if (ts_test->ts->bus->ic_type == IC_TYPE_BERLIN_A) + return (v1 - v2) * 63 / v2; + else if (ts_test->ts->bus->ic_type == IC_TYPE_BERLIN_B) + return (v1 - v2) * 74 / v2 + 20; + else + return (v1 / v2 - 1) * 70 + 59; +} + +static int cal_cha_to_avdd_res(struct goodix_ts_test *ts_test, int v1, int v2) +{ + if (ts_test->ts->bus->ic_type == IC_TYPE_BERLIN_A) + return 64 * (2 * v2 - 25) * 40 / v1 - 40; + else if (ts_test->ts->bus->ic_type == IC_TYPE_BERLIN_B) + return 64 * (2 * v2 - 25) * 99 / v1 - 60; + else + return 64 * (2 * v2 - 25) * 93 / v1 - 20; +} + +static int cal_cha_to_gnd_res(struct goodix_ts_test *ts_test, int v) +{ + if (ts_test->ts->bus->ic_type == IC_TYPE_BERLIN_A) + return 64148 / v - 40; + else if (ts_test->ts->bus->ic_type == IC_TYPE_BERLIN_B) + return 150500 / v - 60; + else + return 145000 / v - 15; +} + +static int ts_test_reset(struct goodix_ts_test *ts_test, u32 delay_ms) +{ + return ts_test->ts->hw_ops->reset(ts_test->ts, delay_ms); +} + +static int ts_test_read(struct goodix_ts_test *ts_test, + u32 addr, u8 *data, u32 len) +{ + return ts_test->ts->hw_ops->read(ts_test->ts, addr, data, len); +} + +static int ts_test_write(struct goodix_ts_test *ts_test, + u32 addr, u8 *data, u32 len) +{ + return ts_test->ts->hw_ops->write(ts_test->ts, addr, data, len); +} + +static int ts_test_send_cmd(struct goodix_ts_test *ts_test, + struct goodix_ts_cmd *cmd) +{ + return ts_test->ts->hw_ops->send_cmd(ts_test->ts, cmd); +} + +static int ts_test_irq_enable(struct goodix_ts_test *ts_test, bool flag) +{ + return ts_test->ts->hw_ops->irq_enable(ts_test->ts, flag); +} + +static int ts_test_send_config(struct goodix_ts_test *ts_test, int type) +{ + struct goodix_ic_config *cfg; + + if (type >= GOODIX_MAX_CONFIG_GROUP) { + ts_err("unsupproted config type %d", type); + return -EINVAL; + } + cfg = ts_test->ts->ic_configs[type]; + if (!cfg || cfg->len <= 0) { + ts_err("no valid normal config found"); + return -EINVAL; + } + + return ts_test->ts->hw_ops->send_config(ts_test->ts, cfg->data, cfg->len); +} + +static int ts_test_read_version(struct goodix_ts_test *ts_test, + struct goodix_fw_version *version) +{ + return ts_test->ts->hw_ops->read_version(ts_test->ts, version); +} + +static void goto_next_line(char **ptr) +{ + do { + *ptr = *ptr + 1; + } while (**ptr != '\n' && **ptr != '\0'); + + if (**ptr == '\0') + return; + + *ptr = *ptr + 1; +} + +static void copy_this_line(char *dest, char *src) +{ + char *copy_from; + char *copy_to; + + copy_from = src; + copy_to = dest; + do { + *copy_to = *copy_from; + copy_from++; + copy_to++; + } while((*copy_from != '\n') && (*copy_from != '\r') && (*copy_from != '\0')); + *copy_to = '\0'; +} + +static int getrid_space(s8* data, s32 len) +{ + u8* buf = NULL; + s32 i; + u32 count = 0; + + buf = (char*)kzalloc(len + 5, GFP_KERNEL); + if (!buf) { + ts_err("get space kzalloc error"); + return -ESRCH; + } + + for (i = 0; i < len; i++) { + if (data[i] == ' ' || data[i] == '\r' || data[i] == '\n') + continue; + + buf[count++] = data[i]; + } + + buf[count++] = '\0'; + + memcpy(data, buf, count); + kfree(buf); + + return count; +} + +static int parse_valid_data(char *buf_start, loff_t buf_size, + char *ptr, s32 *data, s32 rows) +{ + int i = 0; + int j = 0; + char *token = NULL; + char *tok_ptr = NULL; + char *row_data = NULL; + long temp_val; + + if (!ptr) { + ts_err("ptr is NULL"); + return -EINVAL; + } + if (!data) { + ts_err("data is NULL"); + return -EINVAL; + } + + row_data = (char *)kzalloc(MAX_LINE_LEN, GFP_KERNEL); + if (!row_data) { + ts_err("alloc bytes %d failed.", MAX_LINE_LEN); + return -ENOMEM; + } + + for (i = 0; i < rows; i++) { + memset(row_data, 0, MAX_LINE_LEN); + copy_this_line(row_data, ptr); + getrid_space(row_data, strlen(row_data)); + tok_ptr = row_data; + while ((token = strsep(&tok_ptr,","))) { + if (strlen(token) == 0) + continue; + if (kstrtol(token, 0, &temp_val)) { + kfree(row_data); + return -EINVAL; + } + data[j++] = (s32)temp_val; + } + if (i == rows - 1) + break; + goto_next_line(&ptr); //next row + if(!ptr || (strlen(ptr) == 0) || (ptr >= (buf_start + buf_size))) { + ts_info("invalid ptr, return"); + kfree(row_data); + row_data = NULL; + return -EPERM; + } + } + kfree(row_data); + return j; +} + +static int parse_csvfile(char *buf, size_t size, char *target_name, + s32 *data, s32 rows, s32 col) +{ + int ret = 0; + char *ptr = NULL; + int read_ret; + + read_ret = size; + if (read_ret > 0) { + ptr = buf; + ptr = strstr(ptr, target_name); + if (!ptr) { + ts_info("load %s failed 1, maybe not this item", target_name); + return -EINTR; + } + + goto_next_line(&ptr); + if (!ptr || (strlen(ptr) == 0 )) { + ts_err("load %s failed 2!", target_name); + return -EIO; + } + + if (data) { + ret = parse_valid_data(buf, size, ptr, data, rows); + } else { + ts_err("load %s failed 3!", target_name); + return -EINTR; + } + } else { + ts_err("ret=%d, read_ret=%d", ret, read_ret); + ret = -ENXIO; + } + + return ret; +} + +static void goodix_init_params(struct goodix_ts_test *ts_test) +{ + struct goodix_ts_core *ts = ts_test->ts; + struct ts_test_params *test_params = &ts_test->test_params; + + test_params->rawdata_addr = ts->ic_info.misc.mutual_rawdata_addr; + test_params->noisedata_addr = ts->ic_info.misc.mutual_diffdata_addr; + test_params->self_rawdata_addr = ts->ic_info.misc.self_rawdata_addr; + test_params->self_noisedata_addr = ts->ic_info.misc.self_diffdata_addr; + + test_params->drv_num = ts->ic_info.parm.drv_num; + test_params->sen_num = ts->ic_info.parm.sen_num; + + if (ts_test->ts->bus->ic_type == IC_TYPE_BERLIN_A) + test_params->params_info = ¶ms_bra; + else if (ts_test->ts->bus->ic_type == IC_TYPE_BERLIN_B) + test_params->params_info = ¶ms_brb; + else + test_params->params_info = ¶ms_brd; +} + +static int goodix_init_testlimits(struct goodix_ts_test *ts_test) +{ + int ret; + int i; + u32 data_buf[10] = {0}; + char *temp_buf = NULL; + struct ts_test_params *test_params = &ts_test->test_params; + struct goodix_ts_core *ts_core = ts_test->ts; + const struct firmware *firmware = NULL; + struct device *dev = &ts_core->pdev->dev; + char limit_file[100] = {0}; + u32 tx = test_params->drv_num; + u32 rx = test_params->sen_num; + + scnprintf(limit_file, ARRAY_SIZE(limit_file), "%s_test_limits_%d.csv", + GOODIX_TEST_FILE_NAME, ts_core->fw_version.sensor_id); + ts_info("limit_file_name:%s", limit_file); + + ret = request_firmware(&firmware, limit_file, dev); + if (ret < 0) { + ts_err("limits file [%s] not available", limit_file); + return -EINVAL; + } + if (!firmware) { + ts_err("request_firmware failed"); + return -EINVAL; + } + if (firmware->size <= 0) { + ts_err("request_firmware, limits param length error,len:%zu", + firmware->size); + ret = -EINVAL; + goto exit_free; + } + temp_buf = kzalloc(firmware->size + 1, GFP_KERNEL); + if (!temp_buf) { + ts_err("kzalloc bytes failed."); + ret = -ENOMEM; + goto exit_free; + } + memcpy(temp_buf, firmware->data, firmware->size); + + /* obtain config data */ + ret = parse_csvfile(temp_buf, firmware->size, CSV_TP_TEST_CONFIG, + test_params->cfg_buf, 1, GOODIX_CFG_MAX_SIZE); + if (ret < 0) { + ts_info("Can't find %s", CSV_TP_TEST_CONFIG); + } else { + ts_info("parse_csvfile %s OK, cfg_len:%d", CSV_TP_TEST_CONFIG, ret); + for (i = 0; i < ret; i++) + ts_test->test_config.data[i] = (u8)test_params->cfg_buf[i]; + ts_test->test_config.len = ret; + } + + /* obtain mutual_raw min */ + ret = parse_csvfile(temp_buf, firmware->size, CSV_TP_SPECIAL_RAW_MIN, + test_params->min_limits, rx, tx); + if (ret < 0) { + ts_err("Failed get min_limits"); + goto exit_free; + } else + ts_info("parse_csvfile %s OK", CSV_TP_SPECIAL_RAW_MIN); + + /* obtain mutual_raw max */ + ret = parse_csvfile(temp_buf, firmware->size, CSV_TP_SPECIAL_RAW_MAX, + test_params->max_limits, rx, tx); + if (ret < 0) { + ts_err("Failed get max_limits"); + goto exit_free; + } else + ts_info("parse_csvfile %s OK", CSV_TP_SPECIAL_RAW_MAX); + + /* obtain delta limit */ + ret = parse_csvfile(temp_buf, firmware->size, CSV_TP_SPECIAL_RAW_DELTA, + test_params->deviation_limits, rx, tx); + if (ret < 0) { + ts_err("Failed get delta limit"); + goto exit_free; + } else { + ts_info("parse_csvfile %s OK", CSV_TP_SPECIAL_RAW_DELTA); + } + + /* obtain self_raw min */ + ret = parse_csvfile(temp_buf, firmware->size, CSV_TP_SPECIAL_SELFRAW_MIN, + test_params->self_min_limits, 1, tx + rx); + /* obtain self_raw max */ + ret |= parse_csvfile(temp_buf, firmware->size, CSV_TP_SPECIAL_SELFRAW_MAX, + test_params->self_max_limits, 1, tx + rx); + if (ret < 0) { + ts_info("Can't find self_min_max_limits, ship this item"); + ret = 0; + test_params->test_items[GTP_SELFCAP_TEST] = false; + } else { + ts_info("parse_csvfile %s OK", CSV_TP_SPECIAL_SELFRAW_MIN); + ts_info("parse_csvfile %s OK", CSV_TP_SPECIAL_SELFRAW_MAX); + test_params->test_items[GTP_SELFCAP_TEST] = true; + } + + /* obtain noise_threshold */ + ret = parse_csvfile(temp_buf, firmware->size, CSV_TP_NOISE_LIMIT, + &test_params->noise_threshold, 1, 1); + if (ret < 0) { + ts_info("Can't find noise_threshold, skip this item"); + ret = 0; + test_params->test_items[GTP_NOISE_TEST] = false; + } else { + ts_info("parse_csvfile %s OK", CSV_TP_NOISE_LIMIT); + test_params->test_items[GTP_NOISE_TEST] = true; + } + + /* obtain self_noise_threshold */ + ret = parse_csvfile(temp_buf, firmware->size, CSV_TP_SELFNOISE_LIMIT, + &test_params->self_noise_threshold, 1, 1); + if (ret < 0) { + ts_info("Can't find self_noise_threshold, skip this item"); + ret = 0; + test_params->test_items[GTP_SELFNOISE_TEST] = false; + } else { + ts_info("parse_csvfile %s OK", CSV_TP_SELFNOISE_LIMIT); + test_params->test_items[GTP_SELFNOISE_TEST] = true; + } + + /* obtain short_params */ + ret = parse_csvfile(temp_buf, firmware->size, CSV_TP_SHORT_THRESHOLD, + (s32 *)data_buf, 1, 7); + if (ret < 0) { + ts_info("Can't find short shortciurt_threshold, skip this item"); + ret = 0; + test_params->test_items[GTP_SHORT_TEST] = false; + } else { + ts_info("parse_csvfile %s OK", CSV_TP_SHORT_THRESHOLD); + test_params->test_items[GTP_SHORT_TEST] = true; + test_params->short_threshold = data_buf[0]; + test_params->r_drv_drv_threshold = data_buf[1]; + test_params->r_drv_sen_threshold = data_buf[2]; + test_params->r_sen_sen_threshold = data_buf[3]; + test_params->r_drv_gnd_threshold = data_buf[4]; + test_params->r_sen_gnd_threshold = data_buf[5]; + test_params->avdd_value = data_buf[6]; + } + +exit_free: + kfree(temp_buf); + release_firmware(firmware); + return ret; +} + +static int goodix_tptest_prepare(struct goodix_ts_test *ts_test) +{ + int ret; + struct goodix_ic_config *cfg = &ts_test->test_config; + + ts_info("TP test prepare IN"); + + goodix_init_params(ts_test); + /* parse test limits from csv */ + ret = goodix_init_testlimits(ts_test); + if (ret < 0) { + ts_err("Failed to init testlimits from csv."); + return ret; + } + + /* disable irq */ + ts_test_irq_enable(ts_test, false); + /* close esd */ + goodix_ts_blocking_notify(NOTIFY_ESD_OFF, NULL); + + /* send test config if exist */ + if (cfg->len > 0) { + ts_info("Test config exists and send it"); + ret = ts_test->ts->hw_ops->send_config(ts_test->ts, cfg->data, cfg->len); + if (ret < 0) { + ts_err("Send test config failed, exit"); + goodix_ts_blocking_notify(NOTIFY_ESD_ON, NULL); + ts_test_irq_enable(ts_test, true); + return ret; + } + } + + return 0; +} + +static void goodix_tptest_finish(struct goodix_ts_test *ts_test) +{ + ts_info("TP test finish IN"); + /* reset chip */ + ts_test_reset(ts_test, 100); + /* send normal config */ + if (ts_test->test_config.len > 0) { + if (ts_test_send_config(ts_test, CONFIG_TYPE_NORMAL)) + ts_err("Send normal config failed"); + } + + /* open esd */ + goodix_ts_blocking_notify(NOTIFY_ESD_ON, NULL); + /* enable irq */ + ts_test_irq_enable(ts_test, true); +} + +#define SHORT_TEST_RUN_REG 0x10400 +#define SHORT_TEST_RUN_FLAG 0xAA +#define INSPECT_FW_SWITCH_CMD 0x85 +#define TEST_FW_PID "OST" +static int goodix_short_test_prepare(struct goodix_ts_test *ts_test) +{ + struct goodix_ts_cmd tmp_cmd; + struct goodix_fw_version fw_ver; + int ret; + int retry; + int resend = 3; + u8 status; + + ts_info("short test prepare IN"); + ts_test->test_result[GTP_SHORT_TEST] = SYS_SOFTWARE_REASON; + tmp_cmd.len = 4; + tmp_cmd.cmd = INSPECT_FW_SWITCH_CMD; + +resend_cmd: + ret = ts_test_send_cmd(ts_test, &tmp_cmd); + if (ret < 0) { + ts_err("send test mode failed"); + return ret; + } + + retry = 3; + while (retry--) { + msleep(40); + if (ts_test->ts->bus->ic_type == IC_TYPE_BERLIN_A) { + ret = ts_test_read_version(ts_test, &fw_ver); + if (ret < 0) { + ts_err("read test version failed"); + return ret; + } + ret = memcmp(&(fw_ver.patch_pid[3]), TEST_FW_PID, strlen(TEST_FW_PID)); + if (ret == 0) + return 0; + ts_info("patch ID dismatch %s != %s", fw_ver.patch_pid, TEST_FW_PID); + } else { + ret = ts_test_read(ts_test, SHORT_TEST_RUN_REG, &status, 1); + if (!ret && status == SHORT_TEST_RUN_FLAG) + return 0; + ts_info("short_mode_status=0x%02x ret=%d", status, ret); + } + } + + if (resend--) { + ts_test_reset(ts_test, 100); + goto resend_cmd; + } + + return -EINVAL; +} + +static u32 map_die2pin(struct ts_test_params *test_params, u32 chn_num) +{ + int i = 0; + u32 res = 255; + + if (chn_num & DRV_CHANNEL_FLAG) + chn_num = (chn_num & ~DRV_CHANNEL_FLAG) + test_params->params_info->max_sen_num; + + for (i = 0; i < test_params->params_info->max_sen_num; i++) { + if (test_params->params_info->sen_map[i] == chn_num) { + res = i; + break; + } + } + /* res != 255 mean found the corresponding channel num */ + if (res != 255) + return res; + /* if cannot find in SenMap try find in DrvMap */ + for (i = 0; i < test_params->params_info->max_drv_num; i++) { + if (test_params->params_info->drv_map[i] == chn_num) { + res = i; + break; + } + } + if (i >= test_params->params_info->max_drv_num) + ts_err("Faild found corrresponding channel num:%d", chn_num); + else + res |= DRV_CHANNEL_FLAG; + + return res; +} + +static void goodix_save_short_res(struct ts_test_params *params, + u16 chn1, u16 chn2, int r) +{ + int i; + u8 repeat_cnt = 0; + u8 repeat = 0; + struct goodix_ts_test *ts_test = container_of(params, + struct goodix_ts_test, test_params); + struct ts_short_res *short_res = &ts_test->short_res; + + if (chn1 == chn2 || short_res->short_num >= MAX_SHORT_NUM) + return; + + for (i = 0; i < short_res->short_num; i++) { + repeat_cnt = 0; + if (short_res->short_msg[4 * i] == chn1) + repeat_cnt++; + if (short_res->short_msg[4 * i] == chn2) + repeat_cnt++; + if (short_res->short_msg[4 * i + 1] == chn1) + repeat_cnt++; + if (short_res->short_msg[4 * i + 1] == chn2) + repeat_cnt++; + if (repeat_cnt >= 2){ + repeat = 1; + break; + } + } + if (repeat == 0) { + short_res->short_msg[4 * short_res->short_num + 0] = chn1; + short_res->short_msg[4 * short_res->short_num + 1] = chn2; + short_res->short_msg[4 * short_res->short_num + 2] = (r >> 8) & 0xFF; + short_res->short_msg[4 * short_res->short_num + 3] = r & 0xFF; + if (short_res->short_num < MAX_SHORT_NUM) + short_res->short_num++; + } +} + +static int gdix_check_tx_tx_shortcircut(struct goodix_ts_test *ts_test, + u8 short_ch_num) +{ + int ret = 0, err = 0; + u32 r_threshold = 0, short_r = 0; + int size = 0, i = 0, j = 0; + u16 adc_signal = 0; + u8 master_pin_num, slave_pin_num; + u8 *data_buf; + u32 data_reg; + struct ts_test_params *test_params = &ts_test->test_params; + int max_drv_num = test_params->params_info->max_drv_num; + int max_sen_num = test_params->params_info->max_sen_num; + u16 self_capdata, short_die_num = 0; + + size = 4 + max_drv_num * 2 + 2; + data_buf = kzalloc(size, GFP_KERNEL); + if (!data_buf) { + ts_err("Failed to alloc memory"); + return -ENOMEM; + } + /* drv&drv shortcircut check */ + data_reg = test_params->params_info->drv_drv_selfcode_reg; + for (i = 0; i < short_ch_num; i++) { + ret = ts_test_read(ts_test, data_reg, data_buf, size); + if (ret < 0) { + ts_err("Failed read Drv-to-Drv short rawdata"); + err = -EINVAL; + break; + } + + if (checksum_cmp(data_buf, size, CHECKSUM_MODE_U8_LE)) { + ts_err("Drv-to-Drv adc data checksum error"); + err = -EINVAL; + break; + } + + r_threshold = test_params->r_drv_drv_threshold; + short_die_num = le16_to_cpup((__le16 *)&data_buf[0]); + short_die_num -= max_sen_num; + if (short_die_num >= max_drv_num) { + ts_info("invalid short pad num:%d", + short_die_num + max_sen_num); + continue; + } + + /* TODO: j start position need recheck */ + self_capdata = le16_to_cpup((__le16 *)&data_buf[2]); + if (self_capdata == 0xffff || self_capdata == 0) { + ts_info("invalid self_capdata:0x%x", self_capdata); + continue; + } + + for (j = short_die_num + 1; j < max_drv_num; j++) { + adc_signal = le16_to_cpup((__le16 *)&data_buf[4 + j * 2]); + + if (adc_signal < test_params->short_threshold) + continue; + + short_r = (u32)cal_cha_to_cha_res(ts_test, self_capdata, adc_signal); + if (short_r < r_threshold) { + master_pin_num = + map_die2pin(test_params, short_die_num + max_sen_num); + slave_pin_num = + map_die2pin(test_params, j + max_sen_num); + if (master_pin_num == 0xFF || slave_pin_num == 0xFF) { + ts_info("WARNNING invalid pin"); + continue; + } + goodix_save_short_res(test_params, master_pin_num, + slave_pin_num, short_r); + ts_err("short circut:R=%dK,R_Threshold=%dK", + short_r, r_threshold); + ts_err("%s%d--%s%d shortcircut", + (master_pin_num & DRV_CHANNEL_FLAG) ? "DRV" : "SEN", + (master_pin_num & ~DRV_CHANNEL_FLAG), + (slave_pin_num & DRV_CHANNEL_FLAG) ? "DRV" : "SEN", + (slave_pin_num & ~DRV_CHANNEL_FLAG)); + err = -EINVAL; + } + } + data_reg += size; + } + + kfree(data_buf); + return err; +} + +static int gdix_check_rx_rx_shortcircut(struct goodix_ts_test *ts_test, + u8 short_ch_num) +{ + int ret = 0, err = 0; + u32 r_threshold = 0, short_r = 0; + int size = 0, i = 0, j = 0; + u16 adc_signal = 0; + u8 master_pin_num, slave_pin_num; + u8 *data_buf; + u32 data_reg; + struct ts_test_params *test_params = &ts_test->test_params; + int max_sen_num = test_params->params_info->max_sen_num; + u16 self_capdata, short_die_num = 0; + + size = 4 + max_sen_num * 2 + 2; + data_buf = kzalloc(size, GFP_KERNEL); + if (!data_buf) { + ts_err("Failed to alloc memory"); + return -ENOMEM; + } + /* drv&drv shortcircut check */ + data_reg = test_params->params_info->sen_sen_selfcode_reg; + for (i = 0; i < short_ch_num; i++) { + ret = ts_test_read(ts_test, data_reg, data_buf, size); + if (ret) { + ts_err("Failed read Sen-to-Sen short rawdata"); + err = -EINVAL; + break; + } + + if (checksum_cmp(data_buf, size, CHECKSUM_MODE_U8_LE)) { + ts_err("Sen-to-Sen adc data checksum error"); + err = -EINVAL; + break; + } + + r_threshold = test_params->r_sen_sen_threshold; + short_die_num = le16_to_cpup((__le16 *)&data_buf[0]); + if (short_die_num >= max_sen_num) { + ts_info("invalid short pad num:%d", short_die_num); + continue; + } + + /* TODO: j start position need recheck */ + self_capdata = le16_to_cpup((__le16 *)&data_buf[2]); + if (self_capdata == 0xffff || self_capdata == 0) { + ts_info("invalid self_capdata:0x%x", self_capdata); + continue; + } + + for (j = short_die_num + 1; j < max_sen_num; j++) { + adc_signal = le16_to_cpup((__le16 *)&data_buf[4 + j * 2]); + + if (adc_signal < test_params->short_threshold) + continue; + + short_r = (u32)cal_cha_to_cha_res(ts_test, self_capdata, adc_signal); + if (short_r < r_threshold) { + master_pin_num = map_die2pin(test_params, short_die_num); + slave_pin_num = map_die2pin(test_params, j); + if (master_pin_num == 0xFF || slave_pin_num == 0xFF) { + ts_info("WARNNING invalid pin"); + continue; + } + goodix_save_short_res(test_params, master_pin_num, + slave_pin_num, short_r); + ts_err("short circut:R=%dK,R_Threshold=%dK", + short_r, r_threshold); + ts_err("%s%d--%s%d shortcircut", + (master_pin_num & DRV_CHANNEL_FLAG) ? "DRV" : "SEN", + (master_pin_num & ~DRV_CHANNEL_FLAG), + (slave_pin_num & DRV_CHANNEL_FLAG) ? "DRV" : "SEN", + (slave_pin_num & ~DRV_CHANNEL_FLAG)); + err = -EINVAL; + } + } + data_reg += size; + } + + kfree(data_buf); + return err; +} + +static int gdix_check_tx_rx_shortcircut(struct goodix_ts_test *ts_test, + u8 short_ch_num) +{ + int ret = 0, err = 0; + u32 r_threshold = 0, short_r = 0; + int size = 0, i = 0, j = 0; + u16 adc_signal = 0; + u8 master_pin_num, slave_pin_num; + u8 *data_buf = NULL; + u32 data_reg; + struct ts_test_params *test_params = &ts_test->test_params; + int max_drv_num = test_params->params_info->max_drv_num; + int max_sen_num = test_params->params_info->max_sen_num; + u16 self_capdata, short_die_num = 0; + + size = 4 + max_drv_num * 2 + 2; + data_buf = kzalloc(size, GFP_KERNEL); + if (!data_buf) { + ts_err("Failed to alloc memory"); + return -ENOMEM; + } + /* drv&sen shortcircut check */ + data_reg = test_params->params_info->drv_sen_selfcode_reg; + for (i = 0; i < short_ch_num; i++) { + ret = ts_test_read(ts_test, data_reg, data_buf, size); + if (ret) { + ts_err("Failed read Drv-to-Sen short rawdata"); + err = -EINVAL; + break; + } + + if (checksum_cmp(data_buf, size, CHECKSUM_MODE_U8_LE)) { + ts_err("Drv-to-Sen adc data checksum error"); + err = -EINVAL; + break; + } + + r_threshold = test_params->r_drv_sen_threshold; + short_die_num = le16_to_cpup((__le16 *)&data_buf[0]); + if (short_die_num >= max_sen_num) { + ts_info("invalid short pad num:%d", short_die_num); + continue; + } + + /* TODO: j start position need recheck */ + self_capdata = le16_to_cpup((__le16 *)&data_buf[2]); + if (self_capdata == 0xffff || self_capdata == 0) { + ts_info("invalid self_capdata:0x%x", self_capdata); + continue; + } + + for (j = 0; j < max_drv_num; j++) { + adc_signal = le16_to_cpup((__le16 *)&data_buf[4 + j * 2]); + + if (adc_signal < test_params->short_threshold) + continue; + + short_r = (u32)cal_cha_to_cha_res(ts_test, self_capdata, adc_signal); + if (short_r < r_threshold) { + master_pin_num = map_die2pin(test_params, short_die_num); + slave_pin_num = map_die2pin(test_params, j + max_sen_num); + if (master_pin_num == 0xFF || slave_pin_num == 0xFF) { + ts_info("WARNNING invalid pin"); + continue; + } + goodix_save_short_res(test_params, master_pin_num, + slave_pin_num, short_r); + ts_err("short circut:R=%dK,R_Threshold=%dK", + short_r, r_threshold); + ts_err("%s%d--%s%d shortcircut", + (master_pin_num & DRV_CHANNEL_FLAG) ? "DRV" : "SEN", + (master_pin_num & ~DRV_CHANNEL_FLAG), + (slave_pin_num & DRV_CHANNEL_FLAG) ? "DRV" : "SEN", + (slave_pin_num & ~DRV_CHANNEL_FLAG)); + err = -EINVAL; + } + } + data_reg += size; + } + + kfree(data_buf); + return err; +} + +static int gdix_check_resistance_to_gnd(struct ts_test_params *test_params, + u16 adc_signal, u32 pos) +{ + long r = 0; + u16 r_th = 0, avdd_value = 0; + u16 chn_id_tmp = 0; + u8 pin_num = 0; + unsigned short short_type; + struct goodix_ts_test *ts_test = container_of(test_params, + struct goodix_ts_test, test_params); + int max_drv_num = test_params->params_info->max_drv_num; + int max_sen_num = test_params->params_info->max_sen_num; + + avdd_value = test_params->avdd_value; + short_type = adc_signal & 0x8000; + adc_signal &= ~0x8000; + if (adc_signal == 0) + adc_signal = 1; + + if (short_type == 0) { + /* short to GND */ + r = cal_cha_to_gnd_res(ts_test, adc_signal); + } else { + /* short to VDD */ + r = cal_cha_to_avdd_res(ts_test, adc_signal, avdd_value); + } + + if (pos < max_drv_num) + r_th = test_params->r_drv_gnd_threshold; + else + r_th = test_params->r_sen_gnd_threshold; + + chn_id_tmp = pos; + if (chn_id_tmp < max_drv_num) + chn_id_tmp += max_sen_num; + else + chn_id_tmp -= max_drv_num; + + if (r < r_th) { + pin_num = map_die2pin(test_params, chn_id_tmp); + goodix_save_short_res(test_params, pin_num, + short_type ? CHN_VDD : CHN_GND, r); + ts_err("%s%d shortcircut to %s,R=%ldK,R_Threshold=%dK", + (pin_num & DRV_CHANNEL_FLAG) ? "DRV" : "SEN", + (pin_num & ~DRV_CHANNEL_FLAG), + short_type ? "VDD" : "GND", + r, r_th); + + return -EINVAL; + } + + return 0; +} + +static int gdix_check_gndvdd_shortcircut(struct goodix_ts_test *ts_test) +{ + int ret = 0, err = 0; + int size = 0, i = 0; + u16 adc_signal = 0; + u32 data_reg; + u8 *data_buf = NULL; + int max_drv_num = ts_test->test_params.params_info->max_drv_num; + int max_sen_num = ts_test->test_params.params_info->max_sen_num; + + size = (max_drv_num + max_sen_num) * 2 + 2; + data_buf = kzalloc(size, GFP_KERNEL); + if (!data_buf) { + ts_err("Failed to alloc memory"); + return -ENOMEM; + } + /* read diff code, diff code will be used to calculate + * resistance between channel and GND */ + data_reg = ts_test->test_params.params_info->diffcode_data_reg; + ret = ts_test_read(ts_test, data_reg, data_buf, size); + if (ret < 0) { + ts_err("Failed read to-gnd rawdata"); + err = -EINVAL; + goto err_out; + } + + if (checksum_cmp(data_buf, size, CHECKSUM_MODE_U8_LE)) { + ts_err("diff code checksum error"); + err = -EINVAL; + goto err_out; + } + + for (i = 0; i < max_drv_num + max_sen_num; i++) { + adc_signal = le16_to_cpup((__le16 *)&data_buf[i * 2]); + ret = gdix_check_resistance_to_gnd(&ts_test->test_params, + adc_signal, i); + if (ret != 0) { + ts_err("Resistance to-gnd/vdd short"); + err = ret; + } + } + +err_out: + kfree(data_buf); + return err; +} + +static int goodix_shortcircut_analysis(struct goodix_ts_test *ts_test) +{ + int ret; + int err = 0; + test_result_t test_result; + + ret = ts_test_read(ts_test, + ts_test->test_params.params_info->short_test_result_reg, + (u8 *)&test_result, sizeof(test_result)); + if (ret < 0) { + ts_err("Read TEST_RESULT_REG failed"); + return ret; + } + + if (checksum_cmp((u8 *)&test_result, sizeof(test_result), + CHECKSUM_MODE_U8_LE)) { + ts_err("shrot result checksum err"); + return -EINVAL; + } + + if (!(test_result.result & 0x0F)) { + ts_info(">>>>> No shortcircut"); + return 0; + } + ts_info("short flag 0x%02x, drv&drv:%d, sen&sen:%d, drv&sen:%d, drv/GNDVDD:%d, sen/GNDVDD:%d", + test_result.result, test_result.drv_drv_num, test_result.sen_sen_num, + test_result.drv_sen_num, test_result.drv_gnd_avdd_num, test_result.sen_gnd_avdd_num); + + if (test_result.drv_drv_num) + err |= gdix_check_tx_tx_shortcircut(ts_test, test_result.drv_drv_num); + if (test_result.sen_sen_num) + err |= gdix_check_rx_rx_shortcircut(ts_test, test_result.sen_sen_num); + if (test_result.drv_sen_num) + err |= gdix_check_tx_rx_shortcircut(ts_test, test_result.drv_sen_num); + if (test_result.drv_gnd_avdd_num || test_result.sen_gnd_avdd_num) + err |= gdix_check_gndvdd_shortcircut(ts_test); + + ts_info(">>>>> short check return 0x%x", err); + + return err; +} + +#define SHORT_FW_CMD_REG 0x10400 +static int send_test_cmd(struct goodix_ts_test *ts_test, + struct goodix_ts_cmd *cmd) +{ + int ret; + u32 reg = SHORT_FW_CMD_REG; + + cmd->state = 0; + cmd->ack = 0; + goodix_append_checksum(&(cmd->buf[2]), cmd->len - 2, CHECKSUM_MODE_U8_LE); + ret = ts_test_write(ts_test, reg, cmd->buf, cmd->len + 2); + if (ret < 0) + return ret; + usleep_range(10000, 11000); + return ret; +} + + +#define INSPECT_PARAM_CMD 0xAA +#define SHORT_TEST_FINISH_FLAG 0x88 +#define SHORT_TEST_THRESHOLD_REG 0x20402 +static void goodix_shortcircut_test(struct goodix_ts_test *ts_test) +{ + int ret = 0; + int retry; + u16 test_time; + u8 status; + int ic_type = ts_test->ts->bus->ic_type; + struct goodix_ts_cmd test_parm_cmd; + // u8 test_param[6]; + + ts_info("---------------------- short_test begin ----------------------"); + ret = goodix_short_test_prepare(ts_test); + if (ret < 0) { + ts_err("Failed enter short test mode"); + return; + } + + /* get short test time */ + ret = ts_test_read(ts_test, + ts_test->test_params.params_info->short_test_time_reg, + (u8 *)&test_time, 2); + if (ret < 0) { + ts_err("Failed to get test_time, default %dms", DEFAULT_TEST_TIME_MS); + test_time = DEFAULT_TEST_TIME_MS; + } else { + if (ic_type == IC_TYPE_BERLIN_A) + test_time /= 10; + if (test_time > MAX_TEST_TIME_MS) { + ts_info("test time too long %d > %d", + test_time, MAX_TEST_TIME_MS); + test_time = MAX_TEST_TIME_MS; + } + ts_info("get test time %dms", test_time); + } + + /* start short test */ + if (ic_type == IC_TYPE_BERLIN_A) { + test_parm_cmd.len = 0x0A; + test_parm_cmd.cmd = INSPECT_PARAM_CMD; + test_parm_cmd.data[0] = + ts_test->test_params.params_info->dft_short_threshold & 0xFF; + test_parm_cmd.data[1] = + (ts_test->test_params.params_info->dft_short_threshold >> 8) & 0xFF; + test_parm_cmd.data[2] = + ts_test->test_params.params_info->short_diffcode_threshold & 0xFF; + test_parm_cmd.data[3] = + (ts_test->test_params.params_info->short_diffcode_threshold >> 8) & 0xFF; + test_parm_cmd.data[4] = + ts_test->test_params.params_info->short_test_dump_num & 0xFF; + test_parm_cmd.data[5] = + (ts_test->test_params.params_info->short_test_dump_num >> 8) & 0xFF; + ret = send_test_cmd(ts_test, &test_parm_cmd); + if (ret < 0) { + ts_err("send INSPECT_PARAM_CMD failed"); + return; + } + } else { + // test_param[0] = ts_test->test_params.params_info->dft_short_threshold & 0xFF; + // test_param[1] = + // (ts_test->test_params.params_info->dft_short_threshold >> 8) & 0xFF; + // test_param[2] = + // ts_test->test_params.params_info->short_diffcode_threshold & 0xFF; + // test_param[3] = + // (ts_test->test_params.params_info->short_diffcode_threshold >> 8) & 0xFF; + // test_param[4] = + // ts_test->test_params.params_info->short_test_dump_num & 0xFF; + // test_param[5] = + // (ts_test->test_params.params_info->short_test_dump_num >> 8) & 0xFF; + // ts_test_write(ts_test, SHORT_TEST_THRESHOLD_REG, + // test_param, sizeof(test_param)); + status = 0; + ts_test_write(ts_test, SHORT_TEST_RUN_REG, &status, 1); + } + + /* wait short test finish */ + msleep(test_time); + retry = 50; + while (retry--) { + ret = ts_test_read(ts_test, + ts_test->test_params.params_info->short_test_status_reg, &status, 1); + if (!ret && status == SHORT_TEST_FINISH_FLAG) + break; + msleep(50); + } + if (retry < 0) { + ts_err("short test failed, status:0x%02x", status); + return; + } + + /* start analysis short result */ + ts_info("short_test finished, start analysis"); + ret = goodix_shortcircut_analysis(ts_test); + if (ret < 0) + ts_test->test_result[GTP_SHORT_TEST] = GTP_PANEL_REASON; + else + ts_test->test_result[GTP_SHORT_TEST] = GTP_TEST_PASS; +} + +#define GOODIX_CMD_RAWDATA 2 +#define GOODIX_TOUCH_EVENT 0x80 +static int goodix_cap_test_prepare(struct goodix_ts_test *ts_test) +{ + int ret; + struct goodix_ts_cmd temp_cmd; + + ts_info("cap test prepare IN"); + ts_test->test_result[GTP_CAP_TEST] = SYS_SOFTWARE_REASON; + ts_test->test_result[GTP_DELTA_TEST] = SYS_SOFTWARE_REASON; + if (ts_test->test_params.test_items[GTP_SELFCAP_TEST]) + ts_test->test_result[GTP_SELFCAP_TEST] = SYS_SOFTWARE_REASON; + if (ts_test->test_params.test_items[GTP_NOISE_TEST]) + ts_test->test_result[GTP_NOISE_TEST] = SYS_SOFTWARE_REASON; + if (ts_test->test_params.test_items[GTP_SELFNOISE_TEST]) + ts_test->test_result[GTP_SELFNOISE_TEST] = SYS_SOFTWARE_REASON; + + /* switch rawdata mode */ + if (ts_test->ts->bus->ic_type == IC_TYPE_BERLIN_D) { + temp_cmd.cmd = 0x90; + temp_cmd.data[0] = 0x81; + temp_cmd.len = 5; + } else { + temp_cmd.cmd = GOODIX_CMD_RAWDATA; + temp_cmd.len = 4; + } + ret = ts_test_send_cmd(ts_test, &temp_cmd); + if (ret < 0) + ts_err("Enter rawdata mode failed"); + + return ret; +} + +static int goodix_cap_test_finish(struct goodix_ts_test *ts_test) +{ + ts_info("cap_test finished"); + /* switch coor mode */ + ts_test_reset(ts_test, 100); + return 0; +} + +static int goodix_cache_rawdata(struct goodix_ts_test *ts_test) +{ + int ret; + int i; + int retry; + u8 val; + unsigned char frame_buf[GOODIX_MAX_FRAMEDATA_LEN]; + struct frame_head *frame_head; + struct goodix_ts_core *cd = ts_test->ts; + unsigned char *cur_ptr; + u32 sen_num = ts_test->test_params.sen_num; + u32 drv_num = ts_test->test_params.drv_num; + u32 data_size = sen_num * drv_num; + u32 data_addr = ts_test->test_params.rawdata_addr; + u32 flag_addr = ts_test->ts->ic_info.misc.touch_data_addr; + + if (ts_test->ts->bus->ic_type == IC_TYPE_BERLIN_D) + flag_addr = ts_test->ts->ic_info.misc.frame_data_addr; + + for (i = 0; i < TOTAL_FRAME_NUM; i++) { + val = 0; + ret = ts_test_write(ts_test, flag_addr, &val, 1); + if (ret < 0) { + ts_err("clean touch event failed, exit"); + return -EAGAIN; + } + retry = 20; + while (retry--) { + usleep_range(5000, 5100); + ret = ts_test_read(ts_test, flag_addr, &val, 1); + if (!ret && (val & 0x80)) + break; + } + if (retry < 0) { + ts_err("rawdata is not ready val:0x%02x i:%d, exit", val, i); + return -EAGAIN; + } + + if (cd->bus->ic_type == IC_TYPE_BERLIN_D) { + ret = ts_test_read(ts_test, flag_addr, frame_buf, sizeof(frame_buf)); + if (ret < 0) + return ret; + if (checksum_cmp(frame_buf, + cd->ic_info.misc.frame_data_head_len, CHECKSUM_MODE_U8_LE)) { + ts_err("frame head checksum error"); + return -EINVAL; + } + frame_head = (struct frame_head *)frame_buf; + if (checksum_cmp(frame_buf, + frame_head->cur_frame_len, CHECKSUM_MODE_U16_LE)) { + ts_err("frame body checksum error"); + return -EINVAL; + } + cur_ptr = frame_buf; + cur_ptr += cd->ic_info.misc.frame_data_head_len; + cur_ptr += cd->ic_info.misc.fw_attr_len; + cur_ptr += cd->ic_info.misc.fw_log_len; + memcpy((u8 *)ts_test->rawdata[i].data, cur_ptr + 8, + cd->ic_info.misc.mutual_struct_len - 8); + } else { + ret = ts_test_read(ts_test, data_addr, + (u8 *)ts_test->rawdata[i].data, data_size * sizeof(s16)); + if (ret < 0) + return ret; + } + + ts_test->rawdata[i].size = data_size; + goodix_rotate_abcd2cbad(drv_num, sen_num, ts_test->rawdata[i].data); + } + + return ret; +} + +static void goodix_cache_deltadata(struct goodix_ts_test *ts_test) +{ + u32 data_size; + int tx = ts_test->test_params.drv_num; + int i; + int j; + int max_val; + int raw; + int temp; + + for (i = 0; i < TOTAL_FRAME_NUM; i++) { + data_size = ts_test->rawdata[i].size; + if (data_size == 0) + continue; + + for (j = 0; j < data_size; j++) { + raw = ts_test->rawdata[i].data[j]; + max_val = 0; + /* calcu delta with above node */ + if (j - tx >= 0) { + temp = ts_test->rawdata[i].data[j - tx]; + temp = ABS(temp - raw); + max_val = MAX(max_val, temp); + } + /* calcu delta with bellow node */ + if (j + tx < data_size) { + temp = ts_test->rawdata[i].data[j + tx]; + temp = ABS(temp - raw); + max_val = MAX(max_val, temp); + } + /* calcu delta with left node */ + if (j > 0 && j % tx) { + temp = ts_test->rawdata[i].data[j - 1]; + temp = ABS(temp - raw); + max_val = MAX(max_val, temp); + } + /* calcu delta with right node */ + if ((j + 1) % tx) { + temp = ts_test->rawdata[i].data[j + 1]; + temp = ABS(temp - raw); + max_val = MAX(max_val, temp); + } + ts_test->accord_arr[i].data[j] = max_val * 1000 / raw; + } + ts_test->accord_arr[i].size = data_size; + } +} + +static int goodix_cache_self_rawdata(struct goodix_ts_test *ts_test) +{ + int ret; + u32 sen_num = ts_test->test_params.sen_num; + u32 drv_num = ts_test->test_params.drv_num; + u32 data_size = sen_num + drv_num; + u32 data_addr = ts_test->test_params.self_rawdata_addr; + u32 flag_addr = ts_test->ts->ic_info.misc.frame_data_addr; + struct frame_head *frame_head; + struct goodix_ts_core *cd = ts_test->ts; + unsigned char frame_buf[GOODIX_MAX_FRAMEDATA_LEN]; + unsigned char *cur_ptr; + + if (cd->bus->ic_type == IC_TYPE_BERLIN_D) { + ret = ts_test_read(ts_test, flag_addr, frame_buf, sizeof(frame_buf)); + if (ret < 0) + return ret; + + if (checksum_cmp(frame_buf, + cd->ic_info.misc.frame_data_head_len, CHECKSUM_MODE_U8_LE)) { + ts_err("frame head checksum error"); + return -EINVAL; + } + frame_head = (struct frame_head *)frame_buf; + if (checksum_cmp(frame_buf, frame_head->cur_frame_len, CHECKSUM_MODE_U16_LE)) { + ts_err("frame body checksum error"); + return -EINVAL; + } + cur_ptr = frame_buf; + cur_ptr += cd->ic_info.misc.frame_data_head_len; + cur_ptr += cd->ic_info.misc.fw_attr_len; + cur_ptr += cd->ic_info.misc.fw_log_len; + cur_ptr += cd->ic_info.misc.mutual_struct_len; + memcpy((u8 *)ts_test->self_rawdata.data, cur_ptr + 10, + cd->ic_info.misc.self_struct_len - 10); + } else { + ret = ts_test_read(ts_test, data_addr, + (u8 *)ts_test->self_rawdata.data, + data_size * sizeof(s16)); + if (ret < 0) + return ret; + } + ts_test->self_rawdata.size = data_size; + + return ret; +} + +static int goodix_cache_noisedata(struct goodix_ts_test *ts_test) +{ + int ret; + int i; + int cnt; + int retry; + u8 val; + unsigned char frame_buf[GOODIX_MAX_FRAMEDATA_LEN]; + unsigned char *cur_ptr; + struct frame_head *frame_head; + struct goodix_ts_cmd temp_cmd; + struct goodix_ts_core *cd = ts_test->ts; + u32 sen_num = ts_test->test_params.sen_num; + u32 drv_num = ts_test->test_params.drv_num; + u32 data_size = sen_num * drv_num; + u32 data_addr = ts_test->test_params.noisedata_addr; + u32 flag_addr = ts_test->ts->ic_info.misc.touch_data_addr; + + if (cd->bus->ic_type == IC_TYPE_BERLIN_D) { + flag_addr = ts_test->ts->ic_info.misc.frame_data_addr; + temp_cmd.cmd = 0x90; + temp_cmd.data[0] = 0x82; + temp_cmd.len = 5; + ret = ts_test_send_cmd(ts_test, &temp_cmd); + if (ret < 0) { + ts_err("switch diffdata mode failed, exit!"); + return ret; + } + } + + for (cnt = 0; cnt < NOISEDATA_TEST_TIMES; cnt++) { + val = 0; + ret = ts_test_write(ts_test, flag_addr, &val, 1); + if (ret < 0) { + ts_err("clean touch event failed, exit"); + return -EAGAIN; + } + retry = 20; + while (retry--) { + usleep_range(5000, 5100); + ret = ts_test_read(ts_test, flag_addr, &val, 1); + if (!ret && (val & 0x80)) + break; + } + if (retry < 0) { + ts_err("noisedata is not ready val:0x%02x i:%d, exit", val, cnt); + return -EAGAIN; + } + + if (cd->bus->ic_type == IC_TYPE_BERLIN_D) { + ret = ts_test_read(ts_test, flag_addr, frame_buf, sizeof(frame_buf)); + if (ret < 0) + return ret; + if (checksum_cmp(frame_buf, + cd->ic_info.misc.frame_data_head_len, + CHECKSUM_MODE_U8_LE)) { + ts_err("frame head checksum error"); + return -EINVAL; + } + frame_head = (struct frame_head *)frame_buf; + if (checksum_cmp(frame_buf, + frame_head->cur_frame_len, + CHECKSUM_MODE_U16_LE)) { + ts_err("frame body checksum error"); + return -EINVAL; + } + cur_ptr = frame_buf; + cur_ptr += cd->ic_info.misc.frame_data_head_len; + cur_ptr += cd->ic_info.misc.fw_attr_len; + cur_ptr += cd->ic_info.misc.fw_log_len; + memcpy((u8 *)ts_test->noisedata[cnt].data, cur_ptr + 8, + cd->ic_info.misc.mutual_struct_len - 8); + } else { + ret = ts_test_read(ts_test, data_addr, + (u8 *)ts_test->noisedata[cnt].data, data_size * sizeof(s16)); + if (ret < 0) + return ret; + } + + ts_test->noisedata[cnt].size = data_size; + goodix_rotate_abcd2cbad(drv_num, sen_num, ts_test->noisedata[cnt].data); + for (i = 0; i < data_size; i++) + ts_test->noisedata[cnt].data[i] = ABS(ts_test->noisedata[cnt].data[i]); + } + + return ret; +} + +static int goodix_cache_self_noisedata(struct goodix_ts_test *ts_test) +{ + int ret; + int i; + u32 sen_num = ts_test->test_params.sen_num; + u32 drv_num = ts_test->test_params.drv_num; + u32 data_size = sen_num + drv_num; + u32 data_addr = ts_test->test_params.self_noisedata_addr; + u32 flag_addr = ts_test->ts->ic_info.misc.frame_data_addr; + struct frame_head *frame_head; + struct goodix_ts_core *cd = ts_test->ts; + unsigned char frame_buf[GOODIX_MAX_FRAMEDATA_LEN]; + unsigned char *cur_ptr; + + if (cd->bus->ic_type == IC_TYPE_BERLIN_D) { + ret = ts_test_read(ts_test, flag_addr, frame_buf, sizeof(frame_buf)); + if (ret < 0) + return ret; + if (checksum_cmp(frame_buf, + cd->ic_info.misc.frame_data_head_len, CHECKSUM_MODE_U8_LE)) { + ts_err("frame head checksum error"); + return -EINVAL; + } + frame_head = (struct frame_head *)frame_buf; + if (checksum_cmp(frame_buf, frame_head->cur_frame_len, CHECKSUM_MODE_U16_LE)) { + ts_err("frame body checksum error"); + return -EINVAL; + } + cur_ptr = frame_buf; + cur_ptr += cd->ic_info.misc.frame_data_head_len; + cur_ptr += cd->ic_info.misc.fw_attr_len; + cur_ptr += cd->ic_info.misc.fw_log_len; + cur_ptr += cd->ic_info.misc.mutual_struct_len; + memcpy((u8 *)ts_test->self_noisedata.data, cur_ptr + 10, + cd->ic_info.misc.self_struct_len - 10); + } else { + ret = ts_test_read(ts_test, data_addr, + (u8 *)ts_test->self_noisedata.data, data_size * sizeof(s16)); + if (ret < 0) + return ret; + } + + ts_test->self_noisedata.size = data_size; + for (i = 0; i < data_size; i++) + ts_test->self_noisedata.data[i] = ABS(ts_test->self_noisedata.data[i]); + + return ret; +} + +static int goodix_analysis_rawdata(struct goodix_ts_test *ts_test) +{ + int i; + int j; + bool fail_flag = false; + int err_cnt = 0; + int times = TOTAL_FRAME_NUM; + s16 val; + u32 data_size = ts_test->rawdata[0].size; + + for (i = 0; i < times; i++) { + for (j = 0; j < data_size; j++) { + val = ts_test->rawdata[i].data[j]; + if (val < ts_test->test_params.min_limits[j]) { + fail_flag = true; + ts_test->open_res.beyond_min_limit_cnt[j]++; + } + if (val > ts_test->test_params.max_limits[j]) { + fail_flag = true; + ts_test->open_res.beyond_max_limit_cnt[j]++; + } + } + if (fail_flag) + err_cnt++; + fail_flag = false; + } + + if (err_cnt > 0) + ts_err("rawdata have %d frames out of range", err_cnt); + + err_cnt *= 100; + if (err_cnt > times * 100 * 9 / 10) + return -EINVAL; + + return 0; +} + +static int goodix_analysis_deltadata(struct goodix_ts_test *ts_test) +{ + int i; + int j; + int ret = 0; + s16 val; + u32 data_size = ts_test->accord_arr[0].size; + + for (i = 0; i < TOTAL_FRAME_NUM; i++) { + for (j = 0; j < data_size; j++) { + val = ts_test->accord_arr[i].data[j]; + if (val > ts_test->test_params.deviation_limits[j]) { + ts_test->open_res.beyond_accord_limit_cnt[j]++; + ret = -EINVAL; + } + } + } + + return ret; +} + +static int goodix_analysis_self_rawdata(struct goodix_ts_test *ts_test) +{ + int i; + s16 val; + u32 data_size = ts_test->self_rawdata.size; + + for (i = 0; i < data_size; i++) { + val = ts_test->self_rawdata.data[i]; + if (val < ts_test->test_params.self_min_limits[i] || + val > ts_test->test_params.self_max_limits[i]) { + ts_err("self_rawdata isn't in range, val:%d threshold:[%d,%d]", + val, ts_test->test_params.self_min_limits[i], + ts_test->test_params.self_max_limits[i]); + return -EINVAL; + } + } + + return 0; +} + +static int goodix_analysis_noisedata(struct goodix_ts_test *ts_test) +{ + int cnt; + int i; + bool fail_flag = false; + int err_cnt = 0; + int times = NOISEDATA_TEST_TIMES; + s16 val; + u32 data_size = ts_test->noisedata[0].size; + + for (cnt = 0; cnt < times; cnt++) { + for (i = 0; i < data_size; i++) { + val = ts_test->noisedata[cnt].data[i]; + if (val > ts_test->test_params.noise_threshold) + fail_flag = true; + } + if (fail_flag) + err_cnt++; + fail_flag = false; + } + + if (err_cnt > 0) + ts_err("noisedata have %d frames out of range", err_cnt); + + err_cnt *= 100; + if (err_cnt > times * 100 * 2 / 10) + return -EINVAL; + + return 0; +} + +static int goodix_analysis_self_noisedata(struct goodix_ts_test *ts_test) +{ + int i; + s16 val; + u32 data_size = ts_test->self_noisedata.size; + + for (i = 0; i < data_size; i++) { + val = ts_test->self_noisedata.data[i]; + if (val > ts_test->test_params.self_noise_threshold) { + ts_err("self noisedata isn't in range, val:%d threshold:[0,%d]", + val, ts_test->test_params.self_noise_threshold); + return -EINVAL; + } + } + + return 0; +} + +static void goodix_capacitance_test(struct goodix_ts_test *ts_test) +{ + int ret; + + ts_info("---------------------- cap_test begin ----------------------"); + ret = goodix_cap_test_prepare(ts_test); + if (ret < 0) { + ts_err("cap_test prepare failed, exit"); + goto exit; + } + ts_info("cap rawdata prepare OK"); + + /* obtain rawdata */ + ret = goodix_cache_rawdata(ts_test); + if (ret < 0) { + if (ret == -EAGAIN) { + ts_err("Capacitance exit"); + goto exit; + } else + ts_err("Failed to read capdata"); + + } else { + ts_info("get rawdata finish, start analysis"); + ret = goodix_analysis_rawdata(ts_test); + if (ret < 0) + ts_test->test_result[GTP_CAP_TEST] = GTP_PANEL_REASON; + else + ts_test->test_result[GTP_CAP_TEST] = GTP_TEST_PASS; + } + + /* obtain delta_data */ + goodix_cache_deltadata(ts_test); + ts_info("get deltadata finish, start analysis"); + ret = goodix_analysis_deltadata(ts_test); + if (ret < 0) + ts_test->test_result[GTP_DELTA_TEST] = GTP_PANEL_REASON; + else + ts_test->test_result[GTP_DELTA_TEST] = GTP_TEST_PASS; + + /* obtain self_rawdata */ + if (ts_test->test_params.test_items[GTP_SELFCAP_TEST]) { + ret = goodix_cache_self_rawdata(ts_test); + if (ret < 0) { + ts_err("Failed to read self_capdata"); + } else { + ts_info("get self_rawdata finish, start analysis"); + ret = goodix_analysis_self_rawdata(ts_test); + if (ret < 0) + ts_test->test_result[GTP_SELFCAP_TEST] = GTP_PANEL_REASON; + else + ts_test->test_result[GTP_SELFCAP_TEST] = GTP_TEST_PASS; + } + } + + /* obtain noisedata */ + if (ts_test->test_params.test_items[GTP_NOISE_TEST]) { + ret = goodix_cache_noisedata(ts_test); + if (ret < 0) { + ts_err("Failed to read noisedata"); + } else { + ts_info("get noisedata finish, start analysis"); + ret = goodix_analysis_noisedata(ts_test); + if (ret < 0) + ts_test->test_result[GTP_NOISE_TEST] = GTP_PANEL_REASON; + else + ts_test->test_result[GTP_NOISE_TEST] = GTP_TEST_PASS; + } + } + + /* obtain self_noisedata */ + if (ts_test->test_params.test_items[GTP_SELFNOISE_TEST]) { + ret = goodix_cache_self_noisedata(ts_test); + if (ret < 0) { + ts_err("Failed to read self_noisedata"); + } else { + ts_info("get self_noisedata finish, start analysis"); + ret = goodix_analysis_self_noisedata(ts_test); + if (ret < 0) + ts_test->test_result[GTP_SELFNOISE_TEST] = GTP_PANEL_REASON; + else + ts_test->test_result[GTP_SELFNOISE_TEST] = GTP_TEST_PASS; + } + } + +exit: + goodix_cap_test_finish(ts_test); +} + +char *goodix_strncat(char *dest, char *src, size_t dest_size) +{ + size_t dest_len = 0; + + dest_len = strnlen(dest, dest_size); + strlcat(&dest[dest_len], src, dest_size - dest_len - 1); + return dest; +} + +char *goodix_strncatint(char *dest, int src, char *format, size_t dest_size) +{ + char src_str[MAX_STR_LEN] = {0}; + + snprintf(src_str, MAX_STR_LEN, format, src); + return goodix_strncat(dest, src_str, dest_size); +} + +static void goodix_data_cal(s16 *data, size_t data_size, s16 *stat_result) +{ + int i = 0; + s16 avg = 0; + s16 min = 0; + s16 max = 0; + long long sum = 0; + + min = data[0]; + max = data[0]; + for (i = 0; i < data_size; i++) { + sum += data[i]; + if (max < data[i]) + max = data[i]; + if (min > data[i]) + min = data[i]; + } + avg = div_s64(sum, data_size); + stat_result[0] = avg; + stat_result[1] = max; + stat_result[2] = min; +} + +static void goodix_data_statistics(s16 *data, size_t data_size, + char *result, size_t res_size) +{ + s16 stat_value[3]; + + if (!data || !result) { + ts_err("parameters error please check *data and *result value"); + return; + } + + if (data_size <= 0 || res_size <= 0) { + ts_err("input parameter is illegva:data_size=%ld, res_size=%ld", + data_size, res_size); + return; + } + goodix_data_cal(data, data_size, stat_value); + + memset(result, 0, res_size); + snprintf(result, res_size, "[%d,%d,%d]", + stat_value[0], stat_value[1], stat_value[2]); + return; +} + +#ifdef SAVE_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) +{ + loff_t pos; + ssize_t len; + + 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 goodix_save_test_config(struct goodix_ts_test *ts_test, + struct file *fp) +{ + int ret = 0; + int i; + int bytes = 0; + char *data; + struct goodix_ic_config *cfg = &ts_test->test_config; + + if (cfg->len <= 0) { + ts_info("Can't find vaild test config"); + return 0; + } + + data = kzalloc(MAX_DATA_BUFFER, GFP_KERNEL); + if (!data) { + ts_err("alloc memory failed"); + return -ENOMEM; + } + + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + for (i = 0; i < cfg->len; i++) + bytes += scnprintf(&data[bytes], + MAX_DATA_BUFFER - bytes, "0x%02x,", cfg->data[i]); + + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + ret = fs_write(data, bytes, fp); + if (ret < 0) { + ts_err("test config write failed"); + goto save_end; + } + +save_end: + kfree(data); + return ret; +} + +static int goodix_save_header(struct goodix_ts_test *ts_test, + struct file *fp) +{ + int ret; + int i; + int bytes = 0; + bool result = false; + char *data = NULL; + struct goodix_ts_core *ts = ts_test->ts; + + data = kzalloc(MAX_DATA_BUFFER, GFP_KERNEL); + if (!data) { + ts_err("alloc memory failed"); + return -ENOMEM; + } + + bytes += scnprintf(&data[bytes], + MAX_DATA_BUFFER - bytes, + "\n"); + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "
\n"); + /* sava test result */ + for (i = 0; i < MAX_TEST_ITEMS; i++) { + if ((ts_test->test_result[i] > 0) && + (ts_test->test_result[i] != GTP_TEST_PASS)) { + result = true; + break; + } + } + if (result) + bytes += scnprintf(&data[bytes], + MAX_DATA_BUFFER - bytes, + "NG\n"); + else + bytes += scnprintf(&data[bytes], + MAX_DATA_BUFFER - bytes, + "OK\n"); + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "GT%s\n", + ts->fw_version.patch_pid); + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "%d\n", + ts_test->ts->fw_version.sensor_id); + + ret = fs_write(data, bytes, fp); + if (ret < 0) { + ts_err("header write failed"); + goto save_end; + } + bytes = 0; + /* save test config */ + ret = goodix_save_test_config(ts_test, fp); + if (ret < 0) { + ts_err("save test config failed"); + goto save_end; + } + + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "
\n"); + ret = fs_write(data, bytes, fp); + if (ret < 0) { + ts_err("header write failed"); + goto save_end; + } + bytes = 0; + + /* item list */ + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + if (ts_test->test_result[GTP_CAP_TEST]) { + if (GTP_TEST_PASS == ts_test->test_result[GTP_CAP_TEST]) + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "\n"); + else + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "\n"); + } + + if (ts_test->test_result[GTP_DELTA_TEST]) { + if (GTP_TEST_PASS == ts_test->test_result[GTP_DELTA_TEST]) + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "\n"); + else + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "\n"); + } + + if (ts_test->test_result[GTP_NOISE_TEST]) { + if (GTP_TEST_PASS == ts_test->test_result[GTP_NOISE_TEST]) + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "\n"); + else + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "\n"); + } + + if (ts_test->test_result[GTP_SELFNOISE_TEST]) { + if (GTP_TEST_PASS == ts_test->test_result[GTP_SELFNOISE_TEST]) + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "\n"); + else + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "\n"); + } + + if (ts_test->test_result[GTP_SELFCAP_TEST]) { + if (GTP_TEST_PASS == ts_test->test_result[GTP_SELFCAP_TEST]) + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "\n"); + else + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "\n"); + } + + if (ts_test->test_result[GTP_SHORT_TEST]) { + if (GTP_TEST_PASS == ts_test->test_result[GTP_SHORT_TEST]) + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "\n"); + else + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "\n"); + } + + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + ret = fs_write(data, bytes, fp); + if (ret < 0) { + ts_err("item list write failed"); + goto save_end; + } + +save_end: + kfree(data); + return ret; +} + +static int goodix_save_limits(struct goodix_ts_test *ts_test, + struct file *fp) +{ + int ret; + int i; + int bytes = 0; + char *data = NULL; + int tx = ts_test->test_params.drv_num; + int rx = ts_test->test_params.sen_num; + int chn1; + int chn2; + int r; + + data = kzalloc(MAX_DATA_BUFFER, GFP_KERNEL); + if (!data) { + return -ENOMEM; + + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + + /* save short result */ + if (ts_test->test_result[GTP_SHORT_TEST]) { + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "\n"); + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "%d\n", + ts_test->short_res.short_num); + for (i = 0; i < ts_test->short_res.short_num; i++) { + chn1 = ts_test->short_res.short_msg[4 * i]; + chn2 = ts_test->short_res.short_msg[4 * i + 1]; + r = (ts_test->short_res.short_msg[4 * i + 2] << 8) + + ts_test->short_res.short_msg[4 * i + 3]; + if (chn1 == CHN_VDD) + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "\n", r); + else if (chn2 == CHN_GND) + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "Chn2=\"GND\" ShortResistor= \"%dKom\"/>\n", r); + else if (chn2 & DRV_CHANNEL_FLAG) + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "Chn2=\"Tx%d\" ShortResistor= \"%dKom\"/>\n", + chn2 & 0x7f, r); + else + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "Chn2=\"Rx%d\" ShortResistor= \"%dKom\"/>\n", + chn2 & 0x7f, r); + } + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + ret = fs_write(data, bytes, fp); + if (ret < 0) { + ts_err("short res write fail."); + goto save_end; + } + bytes = 0; + } + + /* rawdata max limit */ + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "\n"); + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "%d\n", TOTAL_FRAME_NUM); + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + for (i = 0; i < tx * rx; i++) { + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "%d,", + ts_test->test_params.max_limits[i]); + if ((i + 1) % tx == 0) + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + } + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + /* BeyondRawdataUpperLimit */ + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "\n"); + for (i = 0; i < tx * rx; i++) { + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "%d,", + ts_test->open_res.beyond_max_limit_cnt[i]); + if ((i + 1) % tx == 0) + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + } + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "\n"); + ret = fs_write(data, bytes, fp); + if (ret < 0) { + ts_err("rawdata limit write failed"); + goto save_end; + } + bytes = 0; + + /* rawdata min limit */ + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + for (i = 0; i < tx * rx; i++) { + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "%d,", + ts_test->test_params.min_limits[i]); + if ((i + 1) % tx == 0) + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + } + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + /* BeyondRawdataLower limit */ + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "\n"); + for (i = 0; i < tx * rx; i++) { + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "%d,", + ts_test->open_res.beyond_min_limit_cnt[i]); + if ((i + 1) % tx == 0) + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + } + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "\n"); + ret = fs_write(data, bytes, fp); + if (ret < 0) { + ts_err("rawdata limit write failed"); + goto save_end; + } + bytes = 0; + + /* Max Accord limit */ + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + for (i = 0; i < tx * rx; i++) { + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "%d,", + ts_test->test_params.deviation_limits[i]); + if ((i + 1) % tx == 0) + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + } + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + /* BeyondAccordLimitCnt */ + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + for (i = 0; i < tx * rx; i++) { + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "%d,", + ts_test->open_res.beyond_accord_limit_cnt[i]); + if ((i + 1) % tx == 0) + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + } + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + ret = fs_write(data, bytes, fp); + if (ret < 0) { + ts_err("rawdata limit write failed"); + goto save_end; + } + bytes = 0; + + /* save noise limit */ + if (ts_test->test_result[GTP_NOISE_TEST]) { + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "\n"); + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "%d\n", + NOISEDATA_TEST_TIMES); + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "%d\n", + ts_test->test_params.noise_threshold); + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + ret = fs_write(data, bytes, fp); + if (ret < 0) { + ts_err("noise limit write failed"); + goto save_end; + } + bytes = 0; + } + + /* save self rawdata limit */ + if (ts_test->test_result[GTP_SELFCAP_TEST]) { + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "\n"); + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "1\n"); + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + for (i = 0; i < tx + rx; i++) { + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "%d,", + ts_test->test_params.self_max_limits[i]); + if ((i + 1) % tx == 0) + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + } + if ((tx + rx) % tx != 0) + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + for (i = 0; i < tx + rx; i++) { + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "%d,", + ts_test->test_params.self_min_limits[i]); + if ((i + 1) % tx == 0) + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + } + if ((tx + rx) % tx != 0) + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + ret = fs_write(data, bytes, fp); + if (ret < 0) { + ts_err("self rawdata limit write failed"); + goto save_end; + } + bytes = 0; + } + + /* save selfnoise limit */ + if (ts_test->test_result[GTP_SELFNOISE_TEST]) { + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "\n"); + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "1\n"); + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "%d\n", + ts_test->test_params.self_noise_threshold); + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + ret = fs_write(data, bytes, fp); + if (ret < 0) { + ts_err("raw limit write failed"); + goto save_end; + } + bytes = 0; + } + + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + ret = fs_write(data, bytes, fp); + if (ret < 0) + ts_err("limit write fail."); + +save_end: + kfree(data); + return ret; +} + +static int goodix_save_rawdata(struct goodix_ts_test *ts_test, + struct file *fp) +{ + int i; + int j; + int ret; + int bytes = 0; + s16 stat_result[3]; + char *data = NULL; + int tx = ts_test->test_params.drv_num; + int rx = ts_test->test_params.sen_num; + int len = tx * rx; + + data = kzalloc(MAX_DATA_BUFFER, GFP_KERNEL); + if (!data) + return -ENOMEM; + + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + for (i = 0; i < TOTAL_FRAME_NUM; i++) { + goodix_data_cal(ts_test->rawdata[i].data, len, stat_result); + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "\n", + i, len, stat_result[1], stat_result[2], stat_result[0]); + for (j = 0; j < len; j++) { + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "%d,", ts_test->rawdata[i].data[j]); + if ((j + 1) % tx == 0) + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + } + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + goodix_data_cal(ts_test->accord_arr[i].data, len, stat_result); + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "\n", + i, len, stat_result[1], stat_result[2], stat_result[0]); + for (j = 0; j < len; j++) { + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "%d,", ts_test->accord_arr[i].data[j]); + if ((j + 1) % tx == 0) + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + } + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + ret = fs_write(data, bytes, fp); + if (ret < 0) { + ts_err("rawdata write fail."); + goto save_end; + } + bytes = 0; + } + + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + ret = fs_write(data, bytes, fp); + if (ret < 0) + ts_err("rawdata write fail."); + +save_end: + kfree(data); + return ret; +} + +static int goodix_save_noise_data(struct goodix_ts_test *ts_test, + struct file *fp) +{ + int i; + int j; + int ret = 0; + int bytes = 0; + s16 stat_result[3]; + char *data = NULL; + int tx = ts_test->test_params.drv_num; + int rx = ts_test->test_params.sen_num; + int len = tx * rx; + + data = kzalloc(MAX_DATA_BUFFER, GFP_KERNEL); + if (!data) + return -ENOMEM; + + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + for (i = 0; i < NOISEDATA_TEST_TIMES; i++) { + goodix_data_cal(ts_test->noisedata[i].data, len, stat_result); + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "\n", + i, len, stat_result[1], stat_result[2], stat_result[0]); + for (j = 0; j < len; j++) { + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "%d,", ts_test->noisedata[i].data[j]); + if ((j + 1) % tx == 0) + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + } + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + ret = fs_write(data, bytes, fp); + if (ret < 0) { + ts_err("noisedata write fail."); + goto save_end; + } + bytes = 0; + } + + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + ret = fs_write(data, bytes, fp); + if (ret < 0) + ts_err("noisedata write fail."); + +save_end: + kfree(data); + return ret; +} + +static int goodix_save_self_data(struct goodix_ts_test *ts_test, + struct file *fp, s16 *src_data, u8 *title, int len) +{ + int i; + int ret = 0; + s32 bytes = 0; + char *data; + s16 stat_result[3]; + int tx = ts_test->test_params.drv_num; + + data = kzalloc(MAX_DATA_BUFFER, GFP_KERNEL); + if (!data) + return -ENOMEM; + + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "<%s>\n",title); + ret = fs_write(data, bytes, fp); + if (ret < 0) { + ts_err("rawdata write fail."); + goto save_end; + } + bytes = 0; + + goodix_data_cal(src_data, len, stat_result); + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, + "\n", + len, stat_result[1], stat_result[2], stat_result[0]); + for (i = 0; i < len; i++) { + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "%d,", src_data[i]); + if ((i + 1) % tx == 0) + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + } + if (len % tx != 0) + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n",title); + ret = fs_write(data, bytes, fp); + if (ret < 0) + ts_err("rawdata write fail."); + +save_end: + kfree(data); + return ret; +} + +static int goodix_save_data(struct goodix_ts_test *ts_test, + struct file *fp) +{ + int ret; + int bytes = 0; + char *data = NULL; + + data = kzalloc(MAX_DATA_BUFFER, GFP_KERNEL); + if (!data) + return -ENOMEM; + + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + ret = fs_write(data, bytes, fp); + if (ret < 0) { + ts_err("rawdata record lable failed"); + goto save_end; + } + bytes = 0; + + ret = goodix_save_rawdata(ts_test, fp); + if (ret < 0) + goto save_end; + + if (ts_test->test_result[GTP_NOISE_TEST]) { + ret = goodix_save_noise_data(ts_test, fp); + if (ret < 0) + goto save_end; + } + + if (ts_test->test_result[GTP_SELFCAP_TEST]) { + ret = goodix_save_self_data(ts_test, fp, + ts_test->self_rawdata.data, + "selfDataRecord", + ts_test->self_rawdata.size); + if (ret < 0) + goto save_end; + } + + if (ts_test->test_result[GTP_SELFNOISE_TEST]) { + ret = goodix_save_self_data(ts_test, fp, + ts_test->self_noisedata.data, + "selfDiffDataRecord", + ts_test->self_noisedata.size); + if (ret < 0) + goto save_end; + } + + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER - bytes, "\n"); + ret = fs_write(data, bytes, fp); + if (ret < 0) + ts_err("rawdata data record lable fail."); + +save_end: + kfree(data); + return ret; +} + +/* save end tag in csv file */ +static int goodix_save_tail(struct goodix_ts_test *ts_test, + struct file *fp) +{ + int ret = 0; + int bytes = 0; + char *data = NULL; + + data = kzalloc(MAX_DATA_BUFFER, GFP_KERNEL); + if (!data) + return -ENOMEM; + + bytes += scnprintf(&data[bytes], MAX_DATA_BUFFER, "
\n"); + ret = fs_write(data, bytes, fp); + if (ret < 0) + ts_err("tail write failed"); + + kfree(data); + return ret; +} + +static void goodix_save_result_data(struct goodix_ts_test *ts_test) +{ + int ret = 0; + char save_path[100]; + struct file *fp = NULL; + + /* format result file */ + scnprintf(save_path, ARRAY_SIZE(save_path), GOODIX_RESULT_SAVE_PATH); + ts_info("save result IN, file_name:%s", save_path); + + fp = filp_open(save_path, O_CREAT | O_WRONLY | O_TRUNC, 0666); + if (IS_ERR(fp)) { + ts_err("create file:%s failed, fp:%ld", save_path, PTR_ERR(fp)); + return; + } + + /* save header */ + ret = goodix_save_header(ts_test, fp); + if (ret < 0) + goto save_end; + + /* save limits */ + ret = goodix_save_limits(ts_test, fp); + if (ret < 0) + goto save_end; + + /* save data */ + ret = goodix_save_data(ts_test, fp); + if (ret < 0) + goto save_end; + + /* save tail */ + ret = goodix_save_tail(ts_test, fp); + if (ret < 0) + goto save_end; + + ts_info("the test result save in %s", save_path); +save_end: + filp_close(fp, NULL); +} +#endif // SAVE_IN_CSV + +static void goodix_put_test_result(struct goodix_ts_test *ts_test, + struct ts_rawdata_info *info) +{ + int i; + bool have_bus_error = false; + bool have_panel_error = false; + char statistics_data[STATISTICS_DATA_LEN] = {0}; + struct goodix_ts_core *ts = ts_test->ts; + + ts_info("put test result IN"); + + info->buff[0] = ts_test->test_params.sen_num; + info->buff[1] = ts_test->test_params.drv_num; + info->used_size = 2; + /* save rawdata to info->buff, only one frame */ + if (ts_test->rawdata[0].size) { + for (i = 0; i < ts_test->rawdata[0].size; i++) + info->buff[info->used_size + i] = ts_test->rawdata[0].data[i]; + info->used_size += ts_test->rawdata[0].size; + } + + /* save noisedata to info->buff */ + if (ts_test->noisedata[0].size) { + for (i = 0; i < ts_test->noisedata[0].size; i++) + info->buff[info->used_size + i] = ts_test->noisedata[0].data[i]; + info->used_size += ts_test->noisedata[0].size; + } + + /* save self_noisedata to info->buff */ + if (ts_test->self_noisedata.size) { + for (i = 0; i < ts_test->self_noisedata.size; i++) + info->buff[info->used_size + i] = ts_test->self_noisedata.data[i]; + info->used_size += ts_test->self_noisedata.size; + } + + /* save self_rawdata to info->buff */ + if (ts_test->self_rawdata.size) { + for (i = 0; i < ts_test->self_rawdata.size; i++) + info->buff[info->used_size + i] = ts_test->self_rawdata.data[i]; + info->used_size += ts_test->self_rawdata.size; + } + + /* check if there have bus error */ + for (i = 0; i < MAX_TEST_ITEMS; i++) { + if (ts_test->test_result[i] == SYS_SOFTWARE_REASON) + have_bus_error = true; + else if (ts_test->test_result[i] == GTP_PANEL_REASON) + have_panel_error = true; + } + ts_info("Have bus error:%d", have_bus_error); + if (have_bus_error || have_panel_error) + goodix_strncat(ts_test->test_info, "[FAIL]-", + TS_RAWDATA_RESULT_MAX); + else + goodix_strncat(ts_test->test_info, "[PASS]-", + TS_RAWDATA_RESULT_MAX); + + if (have_bus_error) + goodix_strncat(ts_test->test_info, "0F-", + TS_RAWDATA_RESULT_MAX); + else + goodix_strncat(ts_test->test_info, "0P-", + TS_RAWDATA_RESULT_MAX); + + for (i = 0; i < MAX_TEST_ITEMS; i++) { + /* if have tested, show result */ + if (ts_test->test_result[i]) { + if (ts_test->test_result[i] == GTP_TEST_PASS) + goodix_strncatint(ts_test->test_info, i, "%dP-", + TS_RAWDATA_RESULT_MAX); + else + goodix_strncatint(ts_test->test_info, i, "%dF-", + TS_RAWDATA_RESULT_MAX); + } + } + + /* calculate rawdata min avg max value*/ + if (ts_test->rawdata[0].size) { + goodix_data_statistics( + ts_test->rawdata[0].data, + ts_test->rawdata[0].size, + statistics_data, + STATISTICS_DATA_LEN); + goodix_strncat(ts_test->test_info, statistics_data, + TS_RAWDATA_RESULT_MAX); + } else { + ts_err("NO valiable rawdata"); + goodix_strncat(ts_test->test_info, "[0,0,0]", + TS_RAWDATA_RESULT_MAX); + } + + /* calculate noisedata min avg max value*/ + if (ts_test->test_params.test_items[GTP_NOISE_TEST]) { + if (ts_test->noisedata[0].size) { + goodix_data_statistics( + ts_test->noisedata[0].data, + ts_test->noisedata[0].size, + statistics_data, + STATISTICS_DATA_LEN); + goodix_strncat(ts_test->test_info, statistics_data, + TS_RAWDATA_RESULT_MAX); + } else { + ts_err("NO valiable noisedata"); + goodix_strncat(ts_test->test_info, "[0,0,0]", + TS_RAWDATA_RESULT_MAX); + } + } + + /* calculate self_rawdata min avg max value*/ + if (ts_test->test_params.test_items[GTP_SELFCAP_TEST]) { + if (ts_test->self_rawdata.size <= + sizeof(ts_test->self_rawdata.data) / sizeof(ts_test->self_rawdata.data[0])) { + goodix_data_statistics( + ts_test->self_rawdata.data, + ts_test->self_rawdata.size, + statistics_data, + STATISTICS_DATA_LEN); + goodix_strncat(ts_test->test_info, statistics_data, + TS_RAWDATA_RESULT_MAX); + } else { + ts_err("NO valiable self_rawdata"); + goodix_strncat(ts_test->test_info, "[0,0,0]", + TS_RAWDATA_RESULT_MAX); + } + } + + /* calculate self_noisedata min avg max value*/ + if (ts_test->test_params.test_items[GTP_SELFNOISE_TEST]) { + if (ts_test->self_noisedata.size <= + sizeof(ts_test->self_noisedata.data) / sizeof(ts_test->self_noisedata.data[0])) { + goodix_data_statistics( + ts_test->self_noisedata.data, + ts_test->self_noisedata.size, + statistics_data, + STATISTICS_DATA_LEN); + goodix_strncat(ts_test->test_info, statistics_data, + TS_RAWDATA_RESULT_MAX); + } else { + ts_err("NO valiable self_noisedata"); + goodix_strncat(ts_test->test_info, "[0,0,0]", + TS_RAWDATA_RESULT_MAX); + } + } + + goodix_strncat(ts_test->test_info, "-GT", + TS_RAWDATA_RESULT_MAX); + goodix_strncat(ts_test->test_info, ts->fw_version.patch_pid, + TS_RAWDATA_RESULT_MAX); + goodix_strncat(ts_test->test_info, "\n", + TS_RAWDATA_RESULT_MAX); + strlcpy(info->result, ts_test->test_info, TS_RAWDATA_RESULT_MAX - 1); + +#ifdef SAVE_IN_CSV + /* save result to file */ + goodix_save_result_data(ts_test); +#endif +} + +static int goodix_do_inspect(struct goodix_ts_core *cd, struct ts_rawdata_info *info) +{ + int ret; + struct goodix_ts_test *ts_test = NULL; + + if (!cd || !info) { + ts_err("core_data or info is NULL"); + return -ENODEV; + } + + ts_test = kzalloc(sizeof(*ts_test), GFP_KERNEL); + if (!ts_test) + return -ENOMEM; + + ts_test->ts = cd; + ret = goodix_tptest_prepare(ts_test); + if (ret < 0) { + ts_err("Failed to prepare TP test, exit"); + strlcpy(info->result, "[FAIL]-0F-software reason\n", + TS_RAWDATA_RESULT_MAX - 1); + goto exit_finish; + } + ts_info("TP test prepare OK"); + + goodix_capacitance_test(ts_test); /* 1F 3F 6F 7F test */ + if (ts_test->test_params.test_items[GTP_SHORT_TEST]) + goodix_shortcircut_test(ts_test); /* 5F test */ + goodix_put_test_result(ts_test, info); + goodix_tptest_finish(ts_test); + +exit_finish: + kfree(ts_test); + return ret; +} + +/* show rawdata */ +static ssize_t get_rawdata_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret = 0; + struct ts_rawdata_info *info = NULL; + struct goodix_ts_core *cd = dev_get_drvdata(dev); + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + goodix_do_inspect(cd, info); + + ret = snprintf(buf, PAGE_SIZE, "resultInfo: %s", info->result); + + kfree(info); + return ret; +} + +static DEVICE_ATTR(get_rawdata, 0444, get_rawdata_show, NULL); + +int inspect_module_init(void) +{ + int ret; + struct kobject *def_kobj = goodix_get_default_kobj(); + + /* create sysfs */ + ret = sysfs_create_file(def_kobj, &dev_attr_get_rawdata.attr); + if (ret < 0) { + ts_err("create sysfs of get_rawdata failed"); + goto err_out; + } + + module_initialized = true; + ts_info("inspect module init success"); + return 0; + +err_out: + ts_err("inspect module init failed!"); + return ret; +} + +void inspect_module_exit(void) +{ + struct kobject *def_kobj = goodix_get_default_kobj(); + + ts_info("inspect module exit"); + if (!module_initialized) + return; + + sysfs_remove_file(def_kobj, &dev_attr_get_rawdata.attr); + module_initialized = false; +} diff --git a/qcom/opensource/touch-drivers/goodix_berlin_driver/goodix_ts_tools.c b/qcom/opensource/touch-drivers/goodix_berlin_driver/goodix_ts_tools.c new file mode 100644 index 0000000000..0807cb97ca --- /dev/null +++ b/qcom/opensource/touch-drivers/goodix_berlin_driver/goodix_ts_tools.c @@ -0,0 +1,503 @@ + /* + * Goodix Touchscreen Driver + * Copyright (C) 2020 - 2021 Goodix, Inc. + * + * 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 a reference + * to you, when you are integrating the GOODiX's CTP IC into your system, + * 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. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "goodix_ts_core.h" + +#define GOODIX_TOOLS_NAME "gtp_tools" +#define GOODIX_TOOLS_VER_MAJOR 1 +#define GOODIX_TOOLS_VER_MINOR 0 +static const u16 goodix_tools_ver = ((GOODIX_TOOLS_VER_MAJOR << 8) + + (GOODIX_TOOLS_VER_MINOR)); + +#define GOODIX_TS_IOC_MAGIC 'G' +#define NEGLECT_SIZE_MASK (~(_IOC_SIZEMASK << _IOC_SIZESHIFT)) + +#define GTP_IRQ_ENABLE _IO(GOODIX_TS_IOC_MAGIC, 0) +#define GTP_DEV_RESET _IO(GOODIX_TS_IOC_MAGIC, 1) +#define GTP_SEND_COMMAND (_IOW(GOODIX_TS_IOC_MAGIC, 2, u8) & NEGLECT_SIZE_MASK) +#define GTP_SEND_CONFIG (_IOW(GOODIX_TS_IOC_MAGIC, 3, u8) & NEGLECT_SIZE_MASK) +#define GTP_ASYNC_READ (_IOR(GOODIX_TS_IOC_MAGIC, 4, u8) & NEGLECT_SIZE_MASK) +#define GTP_SYNC_READ (_IOR(GOODIX_TS_IOC_MAGIC, 5, u8) & NEGLECT_SIZE_MASK) +#define GTP_ASYNC_WRITE (_IOW(GOODIX_TS_IOC_MAGIC, 6, u8) & NEGLECT_SIZE_MASK) +#define GTP_READ_CONFIG (_IOW(GOODIX_TS_IOC_MAGIC, 7, u8) & NEGLECT_SIZE_MASK) +#define GTP_ESD_ENABLE _IO(GOODIX_TS_IOC_MAGIC, 8) +#define GTP_TOOLS_VER (_IOR(GOODIX_TS_IOC_MAGIC, 9, u8) & NEGLECT_SIZE_MASK) +#define GTP_TOOLS_CTRL_SYNC (_IOW(GOODIX_TS_IOC_MAGIC, 10, u8) & NEGLECT_SIZE_MASK) + +#define MAX_BUF_LENGTH (16*1024) +#define IRQ_FALG (0x01 << 2) + +#define I2C_MSG_HEAD_LEN 20 + +/* + * struct goodix_tools_dev - goodix tools device struct + * @ts_core: The core data struct of ts driver + * @ops_mode: represent device work mode + * @rawdiffcmd: Set slave device into rawdata mode + * @normalcmd: Set slave device into normal mode + * @wq: Wait queue struct use in synchronous data read + * @mutex: Protect goodix_tools_dev + * @in_use: device in use + */ +struct goodix_tools_dev { + struct goodix_ts_core *ts_core; + struct list_head head; + unsigned int ops_mode; + struct goodix_ts_cmd rawdiffcmd, normalcmd; + wait_queue_head_t wq; + struct mutex mutex; + atomic_t in_use; + struct goodix_ext_module module; +} *goodix_tools_dev; + + +/* read data asynchronous, + * success return data length, otherwise return < 0 + */ +static int async_read(struct goodix_tools_dev *dev, void __user *arg) +{ + u8 *databuf = NULL; + int ret = 0; + u32 reg_addr, length; + u8 i2c_msg_head[I2C_MSG_HEAD_LEN]; + const struct goodix_ts_hw_ops *hw_ops = dev->ts_core->hw_ops; + + ret = copy_from_user(&i2c_msg_head, arg, I2C_MSG_HEAD_LEN); + if (ret) + return -EFAULT; + + reg_addr = i2c_msg_head[0] + (i2c_msg_head[1] << 8) + + (i2c_msg_head[2] << 16) + (i2c_msg_head[3] << 24); + length = i2c_msg_head[4] + (i2c_msg_head[5] << 8) + + (i2c_msg_head[6] << 16) + (i2c_msg_head[7] << 24); + if (length > MAX_BUF_LENGTH) { + ts_err("buffer too long:%d > %d", length, MAX_BUF_LENGTH); + return -EINVAL; + } + databuf = kzalloc(length, GFP_KERNEL); + if (!databuf) { + ts_err("Alloc memory failed"); + return -ENOMEM; + } + + if (hw_ops->read(dev->ts_core, reg_addr, databuf, length)) { + ret = -EBUSY; + ts_err("Read i2c failed"); + goto err_out; + } + ret = copy_to_user((u8 *)arg + I2C_MSG_HEAD_LEN, databuf, length); + if (ret) { + ret = -EFAULT; + ts_err("Copy_to_user failed"); + goto err_out; + } + ret = length; +err_out: + kfree(databuf); + return ret; +} + +/* if success return config data length */ +static int read_config_data(struct goodix_ts_core *ts_core, void __user *arg) +{ + int ret = 0; + u32 reg_addr, length; + u8 i2c_msg_head[I2C_MSG_HEAD_LEN]; + u8 *tmp_buf; + + ret = copy_from_user(&i2c_msg_head, arg, I2C_MSG_HEAD_LEN); + if (ret) { + ts_err("Copy data from user failed"); + return -EFAULT; + } + reg_addr = i2c_msg_head[0] + (i2c_msg_head[1] << 8) + + (i2c_msg_head[2] << 16) + (i2c_msg_head[3] << 24); + length = i2c_msg_head[4] + (i2c_msg_head[5] << 8) + + (i2c_msg_head[6] << 16) + (i2c_msg_head[7] << 24); + ts_info("read config,reg_addr=0x%x, length=%d", reg_addr, length); + if (length > MAX_BUF_LENGTH) { + ts_err("buffer too long:%d > %d", length, MAX_BUF_LENGTH); + return -EINVAL; + } + tmp_buf = kzalloc(length, GFP_KERNEL); + if (!tmp_buf) { + ts_err("failed alloc memory"); + return -ENOMEM; + } + /* if reg_addr == 0, read config data with specific flow */ + if (!reg_addr) { + if (ts_core->hw_ops->read_config) + ret = ts_core->hw_ops->read_config(ts_core, tmp_buf, length); + else + ret = -EINVAL; + } else { + ret = ts_core->hw_ops->read(ts_core, reg_addr, tmp_buf, length); + if (!ret) + ret = length; + } + if (ret <= 0) + goto err_out; + + if (copy_to_user((u8 *)arg + I2C_MSG_HEAD_LEN, tmp_buf, ret)) { + ret = -EFAULT; + ts_err("Copy_to_user failed"); + } + +err_out: + kfree(tmp_buf); + return ret; +} + +/* write data to i2c asynchronous, + * success return bytes write, else return <= 0 + */ +static int async_write(struct goodix_tools_dev *dev, void __user *arg) +{ + u8 *databuf; + int ret = 0; + u32 reg_addr, length; + u8 i2c_msg_head[I2C_MSG_HEAD_LEN]; + struct goodix_ts_core *ts_core = dev->ts_core; + const struct goodix_ts_hw_ops *hw_ops = ts_core->hw_ops; + + ret = copy_from_user(&i2c_msg_head, arg, I2C_MSG_HEAD_LEN); + if (ret) { + ts_err("Copy data from user failed"); + return -EFAULT; + } + reg_addr = i2c_msg_head[0] + (i2c_msg_head[1] << 8) + + (i2c_msg_head[2] << 16) + (i2c_msg_head[3] << 24); + length = i2c_msg_head[4] + (i2c_msg_head[5] << 8) + + (i2c_msg_head[6] << 16) + (i2c_msg_head[7] << 24); + if (length > MAX_BUF_LENGTH) { + ts_err("buffer too long:%d > %d", length, MAX_BUF_LENGTH); + return -EINVAL; + } + + databuf = kzalloc(length, GFP_KERNEL); + if (!databuf) { + ts_err("Alloc memory failed"); + return -ENOMEM; + } + ret = copy_from_user(databuf, (u8 *)arg + I2C_MSG_HEAD_LEN, length); + if (ret) { + ret = -EFAULT; + ts_err("Copy data from user failed"); + goto err_out; + } + + if (hw_ops->write(ts_core, reg_addr, databuf, length)) { + ret = -EBUSY; + ts_err("Write data to device failed"); + } else { + ret = length; + } + +err_out: + kfree(databuf); + return ret; +} + +static int init_cfg_data(struct goodix_ic_config *cfg, void __user *arg) +{ + int ret = 0; + u32 length; + u8 i2c_msg_head[I2C_MSG_HEAD_LEN] = {0}; + + ret = copy_from_user(&i2c_msg_head, arg, I2C_MSG_HEAD_LEN); + if (ret) { + ts_err("Copy data from user failed"); + return -EFAULT; + } + + length = i2c_msg_head[4] + (i2c_msg_head[5] << 8) + + (i2c_msg_head[6] << 16) + (i2c_msg_head[7] << 24); + if (length > GOODIX_CFG_MAX_SIZE) { + ts_err("buffer too long:%d > %d", length, MAX_BUF_LENGTH); + return -EINVAL; + } + ret = copy_from_user(cfg->data, (u8 *)arg + I2C_MSG_HEAD_LEN, length); + if (ret) { + ts_err("Copy data from user failed"); + return -EFAULT; + } + cfg->len = length; + return 0; +} + +/** + * goodix_tools_ioctl - ioctl implementation + * + * @filp: Pointer to file opened + * @cmd: Ioctl opertion command + * @arg: Command data + * Returns >=0 - succeed, else failed + */ +static long goodix_tools_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + int ret = 0; + struct goodix_tools_dev *dev = filp->private_data; + struct goodix_ts_core *ts_core; + const struct goodix_ts_hw_ops *hw_ops; + struct goodix_ic_config *temp_cfg = NULL; + + if (!dev->ts_core) { + ts_err("Tools module not register"); + return -EINVAL; + } + ts_core = dev->ts_core; + hw_ops = ts_core->hw_ops; + + if (_IOC_TYPE(cmd) != GOODIX_TS_IOC_MAGIC) { + ts_err("Bad magic num:%c", _IOC_TYPE(cmd)); + return -ENOTTY; + } + + switch (cmd & NEGLECT_SIZE_MASK) { + case GTP_IRQ_ENABLE: + if (arg == 1) { + hw_ops->irq_enable(ts_core, true); + mutex_lock(&dev->mutex); + dev->ops_mode |= IRQ_FALG; + mutex_unlock(&dev->mutex); + ts_info("IRQ enabled"); + } else if (arg == 0) { + hw_ops->irq_enable(ts_core, false); + mutex_lock(&dev->mutex); + dev->ops_mode &= ~IRQ_FALG; + mutex_unlock(&dev->mutex); + ts_info("IRQ disabled"); + } else { + ts_info("Irq aready set with, arg = %ld", arg); + } + ret = 0; + break; + case GTP_ESD_ENABLE: + if (arg == 0) + goodix_ts_blocking_notify(NOTIFY_ESD_OFF, NULL); + else + goodix_ts_blocking_notify(NOTIFY_ESD_ON, NULL); + break; + case GTP_DEV_RESET: + hw_ops->reset(ts_core, GOODIX_NORMAL_RESET_DELAY_MS); + break; + case GTP_SEND_COMMAND: + /* deprecated command */ + ts_err("the GTP_SEND_COMMAND function has been removed"); + ret = -EINVAL; + break; + case GTP_SEND_CONFIG: + temp_cfg = kzalloc(sizeof(struct goodix_ic_config), GFP_KERNEL); + if (!temp_cfg) { + ts_err("Memory allco err"); + ret = -ENOMEM; + goto err_out; + } + + ret = init_cfg_data(temp_cfg, (void __user *)arg); + if (!ret && hw_ops->send_config) { + ret = hw_ops->send_config(ts_core, temp_cfg->data, temp_cfg->len); + if (ret) { + ts_err("Failed send config"); + ret = -EAGAIN; + } else { + ts_info("Send config success"); + ret = 0; + } + } + kfree(temp_cfg); + temp_cfg = NULL; + break; + case GTP_READ_CONFIG: + ret = read_config_data(ts_core, (void __user *)arg); + if (ret > 0) + ts_info("success read config:len=%d", ret); + else + ts_err("failed read config:ret=0x%x", ret); + break; + case GTP_ASYNC_READ: + ret = async_read(dev, (void __user *)arg); + if (ret < 0) + ts_err("Async data read failed"); + break; + case GTP_SYNC_READ: + ts_info("unsupport sync read"); + break; + case GTP_ASYNC_WRITE: + ret = async_write(dev, (void __user *)arg); + if (ret < 0) + ts_err("Async data write failed"); + break; + case GTP_TOOLS_VER: + ret = copy_to_user((u8 *)arg, &goodix_tools_ver, sizeof(u16)); + if (ret) + ts_err("failed copy driver version info to user"); + break; + case GTP_TOOLS_CTRL_SYNC: + ts_core->tools_ctrl_sync = !!arg; + ts_info("set tools ctrl sync %d", ts_core->tools_ctrl_sync); + break; + default: + ts_info("Invalid cmd"); + ret = -ENOTTY; + break; + } + +err_out: + return ret; +} + +#ifdef CONFIG_COMPAT +static long goodix_tools_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *arg32 = compat_ptr(arg); + + if (!file->f_op || !file->f_op->unlocked_ioctl) + return -ENOTTY; + return file->f_op->unlocked_ioctl(file, cmd, (unsigned long)arg32); +} +#endif + +static int goodix_tools_open(struct inode *inode, struct file *filp) +{ + int ret = 0; + + ts_info("try open tool"); + /* Only the first time open device need to register module */ + ret = goodix_register_ext_module_no_wait(&goodix_tools_dev->module); + if (ret) { + ts_info("failed register to core module"); + return -EFAULT; + } + ts_info("success open tools"); + goodix_ts_blocking_notify(NOTIFY_ESD_OFF, NULL); + filp->private_data = goodix_tools_dev; + atomic_set(&goodix_tools_dev->in_use, 1); + return 0; +} + +static int goodix_tools_release(struct inode *inode, struct file *filp) +{ + int ret = 0; + + /* when the last close this dev node unregister the module */ + goodix_tools_dev->ts_core->tools_ctrl_sync = false; + atomic_set(&goodix_tools_dev->in_use, 0); + goodix_ts_blocking_notify(NOTIFY_ESD_ON, NULL); + ret = goodix_unregister_ext_module(&goodix_tools_dev->module); + return ret; +} + +static int goodix_tools_module_init(struct goodix_ts_core *core_data, + struct goodix_ext_module *module) +{ + struct goodix_tools_dev *tools_dev = module->priv_data; + + if (core_data) + tools_dev->ts_core = core_data; + else + return -ENODEV; + + return 0; +} + +static int goodix_tools_module_exit(struct goodix_ts_core *core_data, + struct goodix_ext_module *module) +{ + struct goodix_tools_dev *tools_dev = module->priv_data; + ts_debug("tools module unregister"); + if (atomic_read(&tools_dev->in_use)) { + ts_err("tools module busy, please close it then retry"); + return -EBUSY; + } + return 0; +} + +static const struct file_operations goodix_tools_fops = { + .owner = THIS_MODULE, + .open = goodix_tools_open, + .release = goodix_tools_release, + .unlocked_ioctl = goodix_tools_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = goodix_tools_compat_ioctl, +#endif +}; + +static struct miscdevice goodix_tools_miscdev = { + .minor = MISC_DYNAMIC_MINOR, + .name = GOODIX_TOOLS_NAME, + .fops = &goodix_tools_fops, +}; + +static struct goodix_ext_module_funcs goodix_tools_module_funcs = { + .init = goodix_tools_module_init, + .exit = goodix_tools_module_exit, +}; + +/** + * goodix_tools_init - init goodix tools device and register a miscdevice + * + * return: 0 success, else failed + */ +int goodix_tools_init(void) +{ + int ret; + + goodix_tools_dev = kzalloc(sizeof(struct goodix_tools_dev), GFP_KERNEL); + if (goodix_tools_dev == NULL) { + ts_err("Memory allco err"); + return -ENOMEM; + } + + INIT_LIST_HEAD(&goodix_tools_dev->head); + goodix_tools_dev->ops_mode = 0; + goodix_tools_dev->ops_mode |= IRQ_FALG; + init_waitqueue_head(&goodix_tools_dev->wq); + mutex_init(&goodix_tools_dev->mutex); + atomic_set(&goodix_tools_dev->in_use, 0); + + goodix_tools_dev->module.funcs = &goodix_tools_module_funcs; + goodix_tools_dev->module.name = GOODIX_TOOLS_NAME; + goodix_tools_dev->module.priv_data = goodix_tools_dev; + goodix_tools_dev->module.priority = EXTMOD_PRIO_DBGTOOL; + + ret = misc_register(&goodix_tools_miscdev); + if (ret) + ts_err("Debug tools miscdev register failed"); + else + ts_info("Debug tools miscdev register success"); + + return ret; +} + +void goodix_tools_exit(void) +{ + misc_deregister(&goodix_tools_miscdev); + kfree(goodix_tools_dev); + ts_info("Debug tools miscdev exit"); +} diff --git a/qcom/opensource/touch-drivers/goodix_berlin_driver/goodix_ts_utils.c b/qcom/opensource/touch-drivers/goodix_berlin_driver/goodix_ts_utils.c new file mode 100644 index 0000000000..3916a767ea --- /dev/null +++ b/qcom/opensource/touch-drivers/goodix_berlin_driver/goodix_ts_utils.c @@ -0,0 +1,208 @@ + /* + * Goodix Touchscreen Driver + * Copyright (C) 2020 - 2021 Goodix, Inc. + * + * 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 a reference + * to you, when you are integrating the GOODiX's CTP IC into your system, + * 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. + * + */ +#include "goodix_ts_core.h" + +bool debug_log_flag = false; + +/***************************************************************************** +* goodix_append_checksum +* @summary +* Calcualte data checksum with the specified mode. +* +* @param data +* data need to be calculate +* @param len +* data length +* @param mode +* calculate for u8 or u16 checksum +* @return +* return the data checksum value. +* +*****************************************************************************/ +u32 goodix_append_checksum(u8 *data, int len, int mode) +{ + u32 checksum = 0; + int i; + + checksum = 0; + if (mode == CHECKSUM_MODE_U8_LE) { + for (i = 0; i < len; i++) + checksum += data[i]; + } else { + for (i = 0; i < len; i+=2) + checksum += (data[i] + (data[i+1] << 8)); + } + + if (mode == CHECKSUM_MODE_U8_LE) { + data[len] = checksum & 0xff; + data[len + 1] = (checksum >> 8) & 0xff; + return 0xFFFF & checksum; + } + data[len] = checksum & 0xff; + data[len + 1] = (checksum >> 8) & 0xff; + data[len + 2] = (checksum >> 16) & 0xff; + data[len + 3] = (checksum >> 24) & 0xff; + return checksum; +} + +/* checksum_cmp: check data valid or not + * @data: data need to be check + * @size: data length need to be check(include the checksum bytes) + * @mode: compare with U8 or U16 mode + * */ +int checksum_cmp(const u8 *data, int size, int mode) +{ + u32 cal_checksum = 0; + u32 r_checksum = 0; + u32 i; + + if (((mode == CHECKSUM_MODE_U8_LE) && (size < 2)) || + ((mode == CHECKSUM_MODE_U16_LE) && (size < 4)) || + ((mode == CHECKSUM_MODE_U16_LE) && (size % 2 != 0))) + return 1; + + if (mode == CHECKSUM_MODE_U8_LE) { + for (i = 0; i < size - 2; i++) + cal_checksum += data[i]; + r_checksum += data[i++]; + r_checksum += (data[i] << 8); + return (cal_checksum & 0xFFFF) == r_checksum ? 0 : 1; + } + + if (mode == CHECKSUM_MODE_U16_LE) { + for (i = 0; i < size - 4; i += 2) + cal_checksum += data[i] + (data[i + 1] << 8); + r_checksum += data[i++]; + r_checksum += (data[i++] << 8); + r_checksum += (data[i++] << 16); + r_checksum += (data[i] << 24); + return cal_checksum == r_checksum ? 0 : 1; + } + + return 1; +} + +/* return 1 if all data is zero or ff + * else return 0 + */ +int is_risk_data(const u8 *data, int size) +{ + int i; + int zero_count = 0; + int ff_count = 0; + + for (i = 0; i < size; i++) { + if (data[i] == 0) + zero_count++; + else if (data[i] == 0xff) + ff_count++; + } + if (zero_count == size || ff_count == size) { + ts_info("warning data is all %s\n", + zero_count == size ? "zero" : "0xff"); + return 1; + } + + return 0; +} + +/* get config id form config file */ +#define CONFIG_ID_OFFSET 30 +u32 goodix_get_file_config_id(u8 *ic_config) +{ + if (!ic_config) + return 0; + return le32_to_cpup((__le32 *)&ic_config[CONFIG_ID_OFFSET]); +} + +/* matrix transpose */ +void goodix_rotate_abcd2cbad(int tx, int rx, s16 *data) +{ + s16 *temp_buf = NULL; + int size = tx * rx; + int i; + int j; + int col; + + temp_buf = kcalloc(size, sizeof(s16), GFP_KERNEL); + if (!temp_buf) { + ts_err("malloc failed"); + return; + } + + for (i = 0, j = 0, col = 0; i < size; i++) { + temp_buf[i] = data[j++ * rx + col]; + if (j == tx) { + j = 0; + col++; + } + } + + memcpy(data, temp_buf, size * sizeof(s16)); + kfree(temp_buf); +} + +/* get ic type */ +int goodix_get_ic_type(struct device_node *node) +{ + const char *name_tmp; + int ret; + + ret = of_property_read_string(node, "compatible", &name_tmp); + if (ret < 0) { + ts_err("get compatible failed"); + return ret; + } + + if (strstr(name_tmp, "9897")) { + ts_info("ic type is BerlinA"); + ret = IC_TYPE_BERLIN_A; + } else if (strstr(name_tmp, "9966") || strstr(name_tmp, "7986")) { + ts_info("ic type is BerlinB"); + ret = IC_TYPE_BERLIN_B; + } else if (strstr(name_tmp, "9916")) { + ts_info("ic type is BerlinD"); + ret = IC_TYPE_BERLIN_D; + } else { + ts_info("can't find valid ic_type"); + ret = -EINVAL; + } + + return ret; +} + +/* get touch type */ +int goodix_get_touch_type(struct device_node *node) +{ + const char *touch_type; + int ret; + + ret = of_property_read_string(node, "goodix,touch-type", &touch_type); + if (ret) { + ts_err("No touch type found\n"); + return -EINVAL; + } + + if (!strcmp(touch_type, "primary")) + ret = PRIMARY_TOUCH_IDX; + else if (!strcmp(touch_type, "secondary")) + ret = SECONDARY_TOUCH_IDX; + else + ret = -EINVAL; + + return ret; +} \ No newline at end of file diff --git a/qcom/opensource/touch-drivers/nt36xxx/nt36xxx.c b/qcom/opensource/touch-drivers/nt36xxx/nt36xxx.c new file mode 100644 index 0000000000..6f36ae8740 --- /dev/null +++ b/qcom/opensource/touch-drivers/nt36xxx/nt36xxx.c @@ -0,0 +1,4608 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2010 - 2018 Novatek, Inc. + * + * $Revision: 47247 $ + * $Date: 2019-07-10 10:41:36 +0800 (Wed, 10 Jul 2019) $ + * + * 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. + * + */ + +#if !defined(NVT_NT36XXX_SPI) /* NT36XXX I2C */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(CONFIG_DRM) +#include +#endif + +#if defined(CONFIG_DRM_PANEL) +#include +#elif defined(CONFIG_FB) +#include +#include +#elif defined(CONFIG_HAS_EARLYSUSPEND) +#include +#endif + +#include "nt36xxx.h" +#if NVT_TOUCH_ESD_PROTECT +#include +#endif /* #if NVT_TOUCH_ESD_PROTECT */ + +#if NVT_TOUCH_ESD_PROTECT +static struct delayed_work nvt_esd_check_work; +static struct workqueue_struct *nvt_esd_check_wq; +static unsigned long irq_timer = 0; +uint8_t esd_check = false; +uint8_t esd_retry = 0; +#endif /* #if NVT_TOUCH_ESD_PROTECT */ + +#if NVT_TOUCH_EXT_PROC +extern int32_t nvt_extra_proc_init(void); +extern void nvt_extra_proc_deinit(void); +#endif + +#if NVT_TOUCH_MP +extern int32_t nvt_mp_proc_init(void); +extern void nvt_mp_proc_deinit(void); +#endif + +struct nvt_ts_data *ts; + +#if BOOT_UPDATE_FIRMWARE +static struct workqueue_struct *nvt_fwu_wq; +extern void Boot_Update_Firmware(struct work_struct *work); +#endif + +#if defined(CONFIG_DRM) +static struct drm_panel *active_panel; +static void nvt_i2c_panel_notifier_callback(enum panel_event_notifier_tag tag, + struct panel_event_notification *notification, void *client_data); + +#elif defined(_MSM_DRM_NOTIFY_H_) +static int nvt_drm_notifier_callback(struct notifier_block *self, unsigned long event, void *data); + +#else +static int nvt_fb_notifier_callback(struct notifier_block *self, unsigned long event, void *data); +#endif + +#if TOUCH_KEY_NUM > 0 +const uint16_t touch_key_array[TOUCH_KEY_NUM] = { + KEY_BACK, + KEY_HOME, + KEY_MENU +}; +#endif + +#if WAKEUP_GESTURE +const uint16_t gesture_key_array[] = { + KEY_POWER, //GESTURE_WORD_C + KEY_POWER, //GESTURE_WORD_W + KEY_POWER, //GESTURE_WORD_V + KEY_POWER, //GESTURE_DOUBLE_CLICK + KEY_POWER, //GESTURE_WORD_Z + KEY_POWER, //GESTURE_WORD_M + KEY_POWER, //GESTURE_WORD_O + KEY_POWER, //GESTURE_WORD_e + KEY_POWER, //GESTURE_WORD_S + KEY_POWER, //GESTURE_SLIDE_UP + KEY_POWER, //GESTURE_SLIDE_DOWN + KEY_POWER, //GESTURE_SLIDE_LEFT + KEY_POWER, //GESTURE_SLIDE_RIGHT +}; +#endif + +static uint8_t bTouchIsAwake = 0; + +#if defined(CONFIG_DRM) +static void nvt_i2c_register_for_panel_events(struct device_node *dp, + struct nvt_ts_data *ts) +{ + void *cookie = NULL; + + cookie = panel_event_notifier_register(PANEL_EVENT_NOTIFICATION_PRIMARY, + PANEL_EVENT_NOTIFIER_CLIENT_PRIMARY_TOUCH, active_panel, + &nvt_i2c_panel_notifier_callback, ts); + if (!cookie) { + pr_err("Failed to register for panel events\n"); + return; + } + ts->notifier_cookie = cookie; +} +#endif +/******************************************************* + * Description: + * Novatek touchscreen irq enable/disable function. + * + * return: + * n.a. + *******************************************************/ +static void nvt_irq_enable(bool enable) +{ + struct irq_desc *desc; + + if (enable) { + if (!ts->irq_enabled) { + enable_irq(ts->client->irq); + ts->irq_enabled = true; + } + } else { + if (ts->irq_enabled) { + disable_irq(ts->client->irq); + ts->irq_enabled = false; + } + } + + desc = irq_to_desc(ts->client->irq); + NVT_LOG("enable=%d, desc->depth=%d\n", enable, desc->depth); +} + +/******************************************************* + * Description: + * Novatek touchscreen i2c read function. + * + * return: + * Executive outcomes. 2---succeed. -5---I/O error + *******************************************************/ +int32_t CTP_I2C_READ(struct i2c_client *client, uint16_t address, uint8_t *buf, + uint16_t len) +{ + struct i2c_msg msgs[2]; + int32_t ret = -1; + int32_t retries = 0; + + mutex_lock(&ts->xbuf_lock); + + msgs[0].flags = !I2C_M_RD; + msgs[0].addr = address; + msgs[0].len = 1; + msgs[0].buf = &buf[0]; + + msgs[1].flags = I2C_M_RD; + msgs[1].addr = address; + msgs[1].len = len - 1; + msgs[1].buf = ts->xbuf; + + while (retries < 5) { + ret = i2c_transfer(client->adapter, msgs, 2); + if (ret == 2) break; + retries++; + } + + if (unlikely(retries == 5)) { + NVT_ERR("error, ret=%d\n", ret); + ret = -EIO; + } + + memcpy(buf + 1, ts->xbuf, len - 1); + + mutex_unlock(&ts->xbuf_lock); + + return ret; +} + +/******************************************************* + * Description: + * Novatek touchscreen i2c write function. + * + * return: + * Executive outcomes. 1---succeed. -5---I/O error + *******************************************************/ +int32_t CTP_I2C_WRITE(struct i2c_client *client, uint16_t address, uint8_t *buf, + uint16_t len) +{ + struct i2c_msg msg; + int32_t ret = -1; + int32_t retries = 0; + + mutex_lock(&ts->xbuf_lock); + + msg.flags = !I2C_M_RD; + msg.addr = address; + msg.len = len; + memcpy(ts->xbuf, buf, len); + msg.buf = ts->xbuf; + + while (retries < 5) { + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret == 1) break; + retries++; + } + + if (unlikely(retries == 5)) { + NVT_ERR("error, ret=%d\n", ret); + ret = -EIO; + } + + mutex_unlock(&ts->xbuf_lock); + + return ret; +} + +/******************************************************* + * Description: + * Novatek touchscreen set index/page/addr address. + * + * return: + * Executive outcomes. 0---succeed. -5---access fail. + ********************************************************/ +int32_t nvt_set_page(uint16_t i2c_addr, uint32_t addr) +{ + uint8_t buf[4] = {0}; + + buf[0] = 0xFF; //set index/page/addr command + buf[1] = (addr >> 16) & 0xFF; + buf[2] = (addr >> 8) & 0xFF; + + return CTP_I2C_WRITE(ts->client, i2c_addr, buf, 3); +} + +/******************************************************* + * Description: + * Novatek touchscreen reset MCU then into idle mode + * function. + * + * return: + * n.a. + *******************************************************/ +void nvt_sw_reset_idle(void) +{ + uint8_t buf[4]={0}; + + //---write i2c cmds to reset idle--- + buf[0]=0x00; + buf[1]=0xA5; + CTP_I2C_WRITE(ts->client, I2C_HW_Address, buf, 2); + + msleep(15); +} + +/******************************************************* + * Description: + * Novatek touchscreen reset MCU (boot) function. + * + * return: + * n.a. + *******************************************************/ +void nvt_bootloader_reset(void) +{ + uint8_t buf[8] = {0}; + + NVT_LOG("start\n"); + + //---write i2c cmds to reset--- + buf[0] = 0x00; + buf[1] = 0x69; + CTP_I2C_WRITE(ts->client, I2C_HW_Address, buf, 2); + + // need 35ms delay after bootloader reset + msleep(35); + + NVT_LOG("end\n"); +} + +/******************************************************* + * Description: + * Novatek touchscreen clear FW status function. + * + * return: + * Executive outcomes. 0---succeed. -1---fail. + *******************************************************/ +int32_t nvt_clear_fw_status(void) +{ + uint8_t buf[8] = {0}; + int32_t i = 0; + const int32_t retry = 20; + + for (i = 0; i < retry; i++) { + //---set xdata index to EVENT BUF ADDR--- + nvt_set_page(I2C_FW_Address, ts->mmap->EVENT_BUF_ADDR | EVENT_MAP_HANDSHAKING_or_SUB_CMD_BYTE); + + //---clear fw status--- + buf[0] = EVENT_MAP_HANDSHAKING_or_SUB_CMD_BYTE; + buf[1] = 0x00; + CTP_I2C_WRITE(ts->client, I2C_FW_Address, buf, 2); + + //---read fw status--- + buf[0] = EVENT_MAP_HANDSHAKING_or_SUB_CMD_BYTE; + buf[1] = 0xFF; + CTP_I2C_READ(ts->client, I2C_FW_Address, buf, 2); + + if (buf[1] == 0x00) + break; + + usleep_range(10000, 10000); + } + + if (i >= retry) { + NVT_ERR("failed, i=%d, buf[1]=0x%02X\n", i, buf[1]); + return -1; + } else { + return 0; + } +} + +/******************************************************* + * Description: + * Novatek touchscreen check FW status function. + * + * return: + * Executive outcomes. 0---succeed. -1---failed. + *******************************************************/ +int32_t nvt_check_fw_status(void) +{ + uint8_t buf[8] = {0}; + int32_t i = 0; + const int32_t retry = 50; + + for (i = 0; i < retry; i++) { + //---set xdata index to EVENT BUF ADDR--- + nvt_set_page(I2C_FW_Address, ts->mmap->EVENT_BUF_ADDR | EVENT_MAP_HANDSHAKING_or_SUB_CMD_BYTE); + + //---read fw status--- + buf[0] = EVENT_MAP_HANDSHAKING_or_SUB_CMD_BYTE; + buf[1] = 0x00; + CTP_I2C_READ(ts->client, I2C_FW_Address, buf, 2); + + if ((buf[1] & 0xF0) == 0xA0) + break; + + usleep_range(10000, 10000); + } + + if (i >= retry) { + NVT_ERR("failed, i=%d, buf[1]=0x%02X\n", i, buf[1]); + return -1; + } else { + return 0; + } +} + +/******************************************************* + * Description: + * Novatek touchscreen check FW reset state function. + * + * return: + * Executive outcomes. 0---succeed. -1---failed. + ******************************************************/ +int32_t nvt_check_fw_reset_state(RST_COMPLETE_STATE check_reset_state) +{ + uint8_t buf[8] = {0}; + int32_t ret = 0; + int32_t retry = 0; + + while (1) { + usleep_range(10000, 10000); + + //---read reset state--- + buf[0] = EVENT_MAP_RESET_COMPLETE; + buf[1] = 0x00; + CTP_I2C_READ(ts->client, I2C_FW_Address, buf, 6); + + if ((buf[1] >= check_reset_state) && (buf[1] <= RESET_STATE_MAX)) { + ret = 0; + break; + } + + retry++; + if(unlikely(retry > 100)) { + NVT_ERR("error, retry=%d, buf[1]=0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X\n", + retry, buf[1], buf[2], buf[3], buf[4], buf[5]); + ret = -1; + break; + } + } + + return ret; +} + +/******************************************************* + * Description: + * Novatek touchscreen get novatek project id information + * function. + * + * return: + * Executive outcomes. 0---success. -1---fail. + *******************************************************/ +int32_t nvt_read_pid(void) +{ + uint8_t buf[3] = {0}; + + //---set xdata index to EVENT BUF ADDR--- + nvt_set_page(I2C_FW_Address, ts->mmap->EVENT_BUF_ADDR | EVENT_MAP_PROJECTID); + + //---read project id--- + buf[0] = EVENT_MAP_PROJECTID; + buf[1] = 0x00; + buf[2] = 0x00; + CTP_I2C_READ(ts->client, I2C_FW_Address, buf, 3); + + ts->nvt_pid = (buf[2] << 8) + buf[1]; + + NVT_LOG("PID=%04X\n", ts->nvt_pid); + + return 0; +} + +/******************************************************* +Description: + Novatek touchscreen get firmware related information + function. + +return: + Executive outcomes. 0---success. -1---fail. +*******************************************************/ +int32_t nvt_get_fw_info(void) +{ + uint8_t buf[64] = {0}; + uint32_t retry_count = 0; + int32_t ret = 0; + +info_retry: + //---set xdata index to EVENT BUF ADDR--- + nvt_set_page(I2C_FW_Address, ts->mmap->EVENT_BUF_ADDR | EVENT_MAP_FWINFO); + + //---read fw info--- + buf[0] = EVENT_MAP_FWINFO; + CTP_I2C_READ(ts->client, I2C_FW_Address, buf, 17); + ts->fw_ver = buf[1]; + ts->x_num = buf[3]; + ts->y_num = buf[4]; + ts->abs_x_max = (uint16_t)((buf[5] << 8) | buf[6]); + ts->abs_y_max = (uint16_t)((buf[7] << 8) | buf[8]); + ts->max_button_num = buf[11]; + + //---clear x_num, y_num if fw info is broken--- + if ((buf[1] + buf[2]) != 0xFF) { + NVT_ERR("FW info is broken! fw_ver=0x%02X, ~fw_ver=0x%02X\n", buf[1], buf[2]); + ts->fw_ver = 0; + ts->x_num = 18; + ts->y_num = 32; + ts->abs_x_max = TOUCH_DEFAULT_MAX_WIDTH; + ts->abs_y_max = TOUCH_DEFAULT_MAX_HEIGHT; + ts->max_button_num = TOUCH_KEY_NUM; + + if(retry_count < 3) { + retry_count++; + NVT_ERR("retry_count=%d\n", retry_count); + goto info_retry; + } else { + NVT_ERR("Set default fw_ver=%d, x_num=%d, y_num=%d, " + "abs_x_max=%d, abs_y_max=%d, max_button_num=%d!\n", + ts->fw_ver, ts->x_num, ts->y_num, + ts->abs_x_max, ts->abs_y_max, ts->max_button_num); + ret = -1; + } + } else { + ret = 0; + } + + //---Get Novatek PID--- + nvt_read_pid(); + + return ret; +} + +/******************************************************* + * Create Device Node (Proc Entry) + *******************************************************/ +#if NVT_TOUCH_PROC +static struct proc_dir_entry *NVT_proc_entry; +#define DEVICE_NAME "NVTflash" + +/******************************************************* + * Description: + * Novatek touchscreen /proc/NVTflash read function. + * + * return: + * Executive outcomes. 2---succeed. -5,-14---failed. + *******************************************************/ +static ssize_t nvt_flash_read(struct file *file, char __user *buff, + size_t count, loff_t *offp) +{ + uint8_t str[68] = {0}; + int32_t ret = -1; + int32_t retries = 0; + int8_t i2c_wr = 0; + + if (count > sizeof(str)) { + NVT_ERR("error count=%zu\n", count); + return -EFAULT; + } + + if (copy_from_user(str, buff, count)) { + NVT_ERR("copy from user error\n"); + return -EFAULT; + } + +#if NVT_TOUCH_ESD_PROTECT + /* + * stop esd check work to avoid case that 0x77 report righ after here to enable esd check again + * finally lead to trigger esd recovery bootloader reset + */ + cancel_delayed_work_sync(&nvt_esd_check_work); + nvt_esd_check_enable(false); +#endif /* #if NVT_TOUCH_ESD_PROTECT */ + + i2c_wr = str[0] >> 7; + + if (i2c_wr == 0) { //I2C write + while (retries < 20) { + ret = CTP_I2C_WRITE(ts->client, (str[0] & 0x7F), &str[2], str[1]); + if (ret == 1) + break; + else + NVT_ERR("error, retries=%d, ret=%d\n", retries, ret); + + retries++; + } + + if (unlikely(retries == 20)) { + NVT_ERR("error, ret = %d\n", ret); + return -EIO; + } + + return ret; + } else if (i2c_wr == 1) { //I2C read + while (retries < 20) { + ret = CTP_I2C_READ(ts->client, (str[0] & 0x7F), &str[2], str[1]); + if (ret == 2) + break; + else + NVT_ERR("error, retries=%d, ret=%d\n", retries, ret); + + retries++; + } + + // copy buff to user if i2c transfer + if (retries < 20) { + if (copy_to_user(buff, str, count)) + return -EFAULT; + } + + if (unlikely(retries == 20)) { + NVT_ERR("error, ret = %d\n", ret); + return -EIO; + } + + return ret; + } else { + NVT_ERR("Call error, str[0]=%d\n", str[0]); + return -EFAULT; + } +} + +/******************************************************* + * Description: + * Novatek touchscreen /proc/NVTflash open function. + * + * return: + * Executive outcomes. 0---succeed. -12---failed. + *******************************************************/ +static int32_t nvt_flash_open(struct inode *inode, struct file *file) +{ + struct nvt_flash_data *dev; + + dev = kmalloc(sizeof(struct nvt_flash_data), GFP_KERNEL); + if (dev == NULL) { + NVT_ERR("Failed to allocate memory for nvt flash data\n"); + return -ENOMEM; + } + + rwlock_init(&dev->lock); + file->private_data = dev; + + return 0; +} + +/******************************************************* + * Description: + * Novatek touchscreen /proc/NVTflash close function. + * + * return: + * Executive outcomes. 0---succeed. + *******************************************************/ +static int32_t nvt_flash_close(struct inode *inode, struct file *file) +{ + struct nvt_flash_data *dev = file->private_data; + + kfree(dev); + + return 0; +} + +static const struct proc_ops nvt_flash_fops = { + .proc_open = nvt_flash_open, + .proc_release = nvt_flash_close, + .proc_read = nvt_flash_read, +}; + +/******************************************************* + * Description: + * Novatek touchscreen /proc/NVTflash initial function. + * + * return: + * Executive outcomes. 0---succeed. -12---failed. + *******************************************************/ +static int32_t nvt_flash_proc_init(void) +{ + NVT_proc_entry = proc_create(DEVICE_NAME, 0444, NULL,&nvt_flash_fops); + if (NVT_proc_entry == NULL) { + NVT_ERR("Failed!\n"); + return -ENOMEM; + } else { + NVT_LOG("Succeeded!\n"); + } + + NVT_LOG("==========================================================\n"); + NVT_LOG("Create /proc/%s\n", DEVICE_NAME); + NVT_LOG("==========================================================\n"); + + return 0; +} + +/******************************************************* + * Description: + * Novatek touchscreen /proc/NVTflash deinitial function. + * + * return: + * n.a. + *******************************************************/ +static void nvt_flash_proc_deinit(void) +{ + if (NVT_proc_entry != NULL) { + remove_proc_entry(DEVICE_NAME, NULL); + NVT_proc_entry = NULL; + NVT_LOG("Removed /proc/%s\n", DEVICE_NAME); + } +} +#endif + +#if WAKEUP_GESTURE +#define GESTURE_WORD_C 12 +#define GESTURE_WORD_W 13 +#define GESTURE_WORD_V 14 +#define GESTURE_DOUBLE_CLICK 15 +#define GESTURE_WORD_Z 16 +#define GESTURE_WORD_M 17 +#define GESTURE_WORD_O 18 +#define GESTURE_WORD_e 19 +#define GESTURE_WORD_S 20 +#define GESTURE_SLIDE_UP 21 +#define GESTURE_SLIDE_DOWN 22 +#define GESTURE_SLIDE_LEFT 23 +#define GESTURE_SLIDE_RIGHT 24 +/* customized gesture id */ +#define DATA_PROTOCOL 30 + +/* function page definition */ +#define FUNCPAGE_GESTURE 1 + +/******************************************************* + * Description: + * Novatek touchscreen wake up gesture key report function. + * + * return: + * n.a. + *******************************************************/ +void nvt_ts_wakeup_gesture_report(uint8_t gesture_id, uint8_t *data) +{ + uint32_t keycode = 0; + uint8_t func_type = data[2]; + uint8_t func_id = data[3]; + + /* support fw specifal data protocol */ + if ((gesture_id == DATA_PROTOCOL) && (func_type == FUNCPAGE_GESTURE)) { + gesture_id = func_id; + } else if (gesture_id > DATA_PROTOCOL) { + NVT_ERR("gesture_id %d is invalid, func_type=%d, func_id=%d\n", gesture_id, func_type, func_id); + return; + } + + NVT_LOG("gesture_id = %d\n", gesture_id); + + switch (gesture_id) { + case GESTURE_WORD_C: + NVT_LOG("Gesture : Word-C.\n"); + keycode = gesture_key_array[0]; + break; + case GESTURE_WORD_W: + NVT_LOG("Gesture : Word-W.\n"); + keycode = gesture_key_array[1]; + break; + case GESTURE_WORD_V: + NVT_LOG("Gesture : Word-V.\n"); + keycode = gesture_key_array[2]; + break; + case GESTURE_DOUBLE_CLICK: + NVT_LOG("Gesture : Double Click.\n"); + keycode = gesture_key_array[3]; + break; + case GESTURE_WORD_Z: + NVT_LOG("Gesture : Word-Z.\n"); + keycode = gesture_key_array[4]; + break; + case GESTURE_WORD_M: + NVT_LOG("Gesture : Word-M.\n"); + keycode = gesture_key_array[5]; + break; + case GESTURE_WORD_O: + NVT_LOG("Gesture : Word-O.\n"); + keycode = gesture_key_array[6]; + break; + case GESTURE_WORD_e: + NVT_LOG("Gesture : Word-e.\n"); + keycode = gesture_key_array[7]; + break; + case GESTURE_WORD_S: + NVT_LOG("Gesture : Word-S.\n"); + keycode = gesture_key_array[8]; + break; + case GESTURE_SLIDE_UP: + NVT_LOG("Gesture : Slide UP.\n"); + keycode = gesture_key_array[9]; + break; + case GESTURE_SLIDE_DOWN: + NVT_LOG("Gesture : Slide DOWN.\n"); + keycode = gesture_key_array[10]; + break; + case GESTURE_SLIDE_LEFT: + NVT_LOG("Gesture : Slide LEFT.\n"); + keycode = gesture_key_array[11]; + break; + case GESTURE_SLIDE_RIGHT: + NVT_LOG("Gesture : Slide RIGHT.\n"); + keycode = gesture_key_array[12]; + break; + default: + break; + } + + if (keycode > 0) { + input_report_key(ts->input_dev, keycode, 1); + input_sync(ts->input_dev); + input_report_key(ts->input_dev, keycode, 0); + input_sync(ts->input_dev); + } +} +#endif + +/******************************************************* + * Description: + * Novatek touchscreen parse device tree function. + * + * return: + * n.a. + *******************************************************/ +#ifdef CONFIG_OF +static void nvt_parse_dt(struct device *dev) +{ + struct device_node *np = dev->of_node; + +#if NVT_TOUCH_SUPPORT_HW_RST + ts->reset_gpio = of_get_named_gpio_flags(np, "novatek,reset-gpio", 0, &ts->reset_flags); + NVT_LOG("novatek,reset-gpio=%d\n", ts->reset_gpio); +#endif + ts->irq_gpio = of_get_named_gpio_flags(np, "novatek,irq-gpio", 0, &ts->irq_flags); + NVT_LOG("novatek,irq-gpio=%d\n", ts->irq_gpio); + +} +#else +static void nvt_parse_dt(struct device *dev) +{ +#if NVT_TOUCH_SUPPORT_HW_RST + ts->reset_gpio = NVTTOUCH_RST_PIN; +#endif + ts->irq_gpio = NVTTOUCH_INT_PIN; +} +#endif + +/******************************************************* + * Description: + * Novatek touchscreen config and request gpio + * + * return: + * Executive outcomes. 0---succeed. not 0---failed. + *******************************************************/ +static int nvt_gpio_config(struct nvt_ts_data *ts) +{ + int32_t ret = 0; + +#if NVT_TOUCH_SUPPORT_HW_RST + /* request RST-pin (Output/High) */ + if (gpio_is_valid(ts->reset_gpio)) { + ret = gpio_request_one(ts->reset_gpio, GPIOF_OUT_INIT_HIGH, "NVT-tp-rst"); + if (ret) { + NVT_ERR("Failed to request NVT-tp-rst GPIO\n"); + goto err_request_reset_gpio; + } + } +#endif + + /* request INT-pin (Input) */ + if (gpio_is_valid(ts->irq_gpio)) { + ret = gpio_request_one(ts->irq_gpio, GPIOF_IN, "NVT-int"); + if (ret) { + NVT_ERR("Failed to request NVT-int GPIO\n"); + goto err_request_irq_gpio; + } + } + + return ret; + +err_request_irq_gpio: +#if NVT_TOUCH_SUPPORT_HW_RST + gpio_free(ts->reset_gpio); +err_request_reset_gpio: +#endif + return ret; +} + +/******************************************************* + * Description: + * Novatek touchscreen deconfig gpio + * + * return: + * n.a. + *******************************************************/ +static void nvt_gpio_deconfig(struct nvt_ts_data *ts) +{ + if (gpio_is_valid(ts->irq_gpio)) + gpio_free(ts->irq_gpio); +#if NVT_TOUCH_SUPPORT_HW_RST + if (gpio_is_valid(ts->reset_gpio)) + gpio_free(ts->reset_gpio); +#endif +} + +static uint8_t nvt_fw_recovery(uint8_t *point_data) +{ + uint8_t i = 0; + uint8_t detected = true; + + /* check pattern */ + for (i=1 ; i<7 ; i++) { + if (point_data[i] != 0x77) { + detected = false; + break; + } + } + + return detected; +} + +#if NVT_TOUCH_ESD_PROTECT +void nvt_esd_check_enable(uint8_t enable) +{ + /* update interrupt timer */ + irq_timer = jiffies; + /* clear esd_retry counter, if protect function is enabled */ + esd_retry = enable ? 0 : esd_retry; + /* enable/disable esd check flag */ + esd_check = enable; +} + +static void nvt_esd_check_func(struct work_struct *work) +{ + unsigned int timer = jiffies_to_msecs(jiffies - irq_timer); + + //NVT_ERR("esd_check = %d (retry %d)\n", esd_check, esd_retry); //DEBUG + + if ((timer > NVT_TOUCH_ESD_CHECK_PERIOD) && esd_check) { + mutex_lock(&ts->lock); + NVT_ERR("do ESD recovery, timer = %d, retry = %d\n", timer, esd_retry); + /* do esd recovery, bootloader reset */ + nvt_bootloader_reset(); + mutex_unlock(&ts->lock); + /* update interrupt timer */ + irq_timer = jiffies; + /* update esd_retry counter */ + esd_retry++; + } + + queue_delayed_work(nvt_esd_check_wq, &nvt_esd_check_work, + msecs_to_jiffies(NVT_TOUCH_ESD_CHECK_PERIOD)); +} +#endif /* #if NVT_TOUCH_ESD_PROTECT */ + +#define POINT_DATA_LEN 65 +/******************************************************* + * Description: + * Novatek touchscreen work function. + * + * return: + * n.a. + *******************************************************/ +static irqreturn_t nvt_ts_work_func(int irq, void *data) +{ + int32_t ret = -1; + uint8_t point_data[POINT_DATA_LEN + 1] = {0}; + uint32_t position = 0; + uint32_t input_x = 0; + uint32_t input_y = 0; + uint32_t input_w = 0; + uint32_t input_p = 0; + uint8_t input_id = 0; +#if MT_PROTOCOL_B + uint8_t press_id[TOUCH_MAX_FINGER_NUM] = {0}; +#endif /* MT_PROTOCOL_B */ + int32_t i = 0; + int32_t finger_cnt = 0; + +#if WAKEUP_GESTURE + if (bTouchIsAwake == 0) { + pm_wakeup_event(&ts->input_dev->dev, 5000); + } +#endif + + mutex_lock(&ts->lock); + + ret = CTP_I2C_READ(ts->client, I2C_FW_Address, point_data, POINT_DATA_LEN + 1); + if (ret < 0) { + NVT_ERR("CTP_I2C_READ failed.(%d)\n", ret); + goto XFER_ERROR; + } + + if (nvt_fw_recovery(point_data)) { +#if NVT_TOUCH_ESD_PROTECT + nvt_esd_check_enable(true); +#endif /* #if NVT_TOUCH_ESD_PROTECT */ + goto XFER_ERROR; + } + +#if WAKEUP_GESTURE + if (bTouchIsAwake == 0) { + input_id = (uint8_t)(point_data[1] >> 3); + nvt_ts_wakeup_gesture_report(input_id, point_data); + mutex_unlock(&ts->lock); + return IRQ_HANDLED; + } +#endif + + finger_cnt = 0; + + for (i = 0; i < ts->max_touch_num; i++) { + position = 1 + 6 * i; + input_id = (uint8_t)(point_data[position + 0] >> 3); + if ((input_id == 0) || (input_id > ts->max_touch_num)) + continue; + + if (((point_data[position] & 0x07) == 0x01) || ((point_data[position] & 0x07) == 0x02)) { //finger down (enter & moving) +#if NVT_TOUCH_ESD_PROTECT + /* update interrupt timer */ + irq_timer = jiffies; +#endif /* #if NVT_TOUCH_ESD_PROTECT */ + input_x = (uint32_t)(point_data[position + 1] << 4) + (uint32_t) (point_data[position + 3] >> 4); + input_y = (uint32_t)(point_data[position + 2] << 4) + (uint32_t) (point_data[position + 3] & 0x0F); + if ((input_x < 0) || (input_y < 0)) + continue; + if ((input_x > ts->abs_x_max) || (input_y > ts->abs_y_max)) + continue; + input_w = (uint32_t)(point_data[position + 4]); + if (input_w == 0) + input_w = 1; + if (i < 2) { + input_p = (uint32_t)(point_data[position + 5]) + (uint32_t)(point_data[i + 63] << 8); + if (input_p > TOUCH_FORCE_NUM) + input_p = TOUCH_FORCE_NUM; + } else { + input_p = (uint32_t)(point_data[position + 5]); + } + if (input_p == 0) + input_p = 1; + +#if MT_PROTOCOL_B + press_id[input_id - 1] = 1; + input_mt_slot(ts->input_dev, input_id - 1); + input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, true); +#else /* MT_PROTOCOL_B */ + input_report_abs(ts->input_dev, ABS_MT_TRACKING_ID, input_id - 1); + input_report_key(ts->input_dev, BTN_TOUCH, 1); +#endif /* MT_PROTOCOL_B */ + + input_report_abs(ts->input_dev, ABS_MT_POSITION_X, input_x); + input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, input_y); + input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, input_w); + input_report_abs(ts->input_dev, ABS_MT_PRESSURE, input_p); + +#if MT_PROTOCOL_B +#else /* MT_PROTOCOL_B */ + input_mt_sync(ts->input_dev); +#endif /* MT_PROTOCOL_B */ + + finger_cnt++; + } + } + +#if MT_PROTOCOL_B + for (i = 0; i < ts->max_touch_num; i++) { + if (press_id[i] != 1) { + input_mt_slot(ts->input_dev, i); + input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0); + input_report_abs(ts->input_dev, ABS_MT_PRESSURE, 0); + input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, false); + } + } + + input_report_key(ts->input_dev, BTN_TOUCH, (finger_cnt > 0)); +#else /* MT_PROTOCOL_B */ + if (finger_cnt == 0) { + input_report_key(ts->input_dev, BTN_TOUCH, 0); + input_mt_sync(ts->input_dev); + } +#endif /* MT_PROTOCOL_B */ + +#if TOUCH_KEY_NUM > 0 + if (point_data[61] == 0xF8) { +#if NVT_TOUCH_ESD_PROTECT + /* update interrupt timer */ + irq_timer = jiffies; +#endif /* #if NVT_TOUCH_ESD_PROTECT */ + for (i = 0; i < ts->max_button_num; i++) { + input_report_key(ts->input_dev, touch_key_array[i], ((point_data[62] >> i) & 0x01)); + } + } else { + for (i = 0; i < ts->max_button_num; i++) { + input_report_key(ts->input_dev, touch_key_array[i], 0); + } + } +#endif + + input_sync(ts->input_dev); + +XFER_ERROR: + + mutex_unlock(&ts->lock); + + return IRQ_HANDLED; +} + +/******************************************************* + * Description: + * Novatek touchscreen check and stop crc reboot loop. + * + * return: + * n.a. +*******************************************************/ +void nvt_stop_crc_reboot(void) +{ + uint8_t buf[8] = {0}; + int32_t retry = 0; + + //read dummy buffer to check CRC fail reboot is happening or not + + //---change I2C index to prevent geting 0xFF, but not 0xFC--- + nvt_set_page(I2C_BLDR_Address, 0x1F64E); + + //---read to check if buf is 0xFC which means IC is in CRC reboot --- + buf[0] = 0x4E; + CTP_I2C_READ(ts->client, I2C_BLDR_Address, buf, 4); + + if ((buf[1] == 0xFC) || + ((buf[1] == 0xFF) && (buf[2] == 0xFF) && (buf[3] == 0xFF))) { + + //IC is in CRC fail reboot loop, needs to be stopped! + for (retry = 5; retry > 0; retry--) { + + //---write i2c cmds to reset idle : 1st--- + buf[0]=0x00; + buf[1]=0xA5; + CTP_I2C_WRITE(ts->client, I2C_HW_Address, buf, 2); + + //---write i2c cmds to reset idle : 2rd--- + buf[0]=0x00; + buf[1]=0xA5; + CTP_I2C_WRITE(ts->client, I2C_HW_Address, buf, 2); + msleep(1); + + //---clear CRC_ERR_FLAG--- + nvt_set_page(I2C_BLDR_Address, 0x3F135); + + buf[0] = 0x35; + buf[1] = 0xA5; + CTP_I2C_WRITE(ts->client, I2C_BLDR_Address, buf, 2); + + //---check CRC_ERR_FLAG--- + nvt_set_page(I2C_BLDR_Address, 0x3F135); + + buf[0] = 0x35; + buf[1] = 0x00; + CTP_I2C_READ(ts->client, I2C_BLDR_Address, buf, 2); + + if (buf[1] == 0xA5) + break; + } + if (retry == 0) + NVT_ERR("CRC auto reboot is not able to be stopped!\n"); + } + + return; +} + +/******************************************************* + * Description: + * Novatek touchscreen check chip version trim function. + * + * return: + * Executive outcomes. 0---NVT IC. -1---not NVT IC. +*******************************************************/ +static int8_t nvt_ts_check_chip_ver_trim(uint32_t chip_ver_trim_addr) +{ + uint8_t buf[8] = {0}; + int32_t retry = 0; + int32_t list = 0; + int32_t i = 0; + int32_t found_nvt_chip = 0; + int32_t ret = -1; + + nvt_bootloader_reset(); // NOT in retry loop + + //---Check for 5 times--- + for (retry = 5; retry > 0; retry--) { + nvt_sw_reset_idle(); + + buf[0] = 0x00; + buf[1] = 0x35; + CTP_I2C_WRITE(ts->client, I2C_HW_Address, buf, 2); + msleep(10); + + nvt_set_page(I2C_BLDR_Address, chip_ver_trim_addr); + + buf[0] = chip_ver_trim_addr & 0xFF; + buf[1] = 0x00; + buf[2] = 0x00; + buf[3] = 0x00; + buf[4] = 0x00; + buf[5] = 0x00; + buf[6] = 0x00; + CTP_I2C_READ(ts->client, I2C_BLDR_Address, buf, 7); + NVT_LOG("buf[1]=0x%02X, buf[2]=0x%02X, buf[3]=0x%02X, buf[4]=0x%02X, buf[5]=0x%02X, buf[6]=0x%02X\n", + buf[1], buf[2], buf[3], buf[4], buf[5], buf[6]); + + //---Stop CRC check to prevent IC auto reboot--- + if ((buf[1] == 0xFC) || + ((buf[1] == 0xFF) && (buf[2] == 0xFF) && (buf[3] == 0xFF))) { + nvt_stop_crc_reboot(); + continue; + } + + // compare read chip id on supported list + for (list = 0; list < (sizeof(trim_id_table) / sizeof(struct nvt_ts_trim_id_table)); list++) { + found_nvt_chip = 0; + + // compare each byte + for (i = 0; i < NVT_ID_BYTE_MAX; i++) { + if (trim_id_table[list].mask[i]) { + if (buf[i + 1] != trim_id_table[list].id[i]) + break; + } + } + + if (i == NVT_ID_BYTE_MAX) { + found_nvt_chip = 1; + } + + if (found_nvt_chip) { + NVT_LOG("This is NVT touch IC\n"); + ts->mmap = trim_id_table[list].mmap; + ts->carrier_system = trim_id_table[list].hwinfo->carrier_system; + ret = 0; + goto out; + } else { + ts->mmap = NULL; + ret = -1; + } + } + + msleep(10); + } + +out: + return ret; +} + +#if defined(CONFIG_DRM) +static int nvt_ts_check_dt(struct device_node *np) +{ + int i; + int count; + struct device_node *node; + struct drm_panel *panel; + + count = of_count_phandle_with_args(np, "panel", NULL); + if (count <= 0) + return 0; + + for (i = 0; i < count; i++) { + node = of_parse_phandle(np, "panel", i); + panel = of_drm_find_panel(node); + of_node_put(node); + if (!IS_ERR(panel)) { + active_panel = panel; + NVT_LOG(" %s:find\n", __func__); + return 0; + } + } + + NVT_ERR(" %s: not find\n", __func__); + return -ENODEV; +} + +static int nvt_ts_check_default_tp(struct device_node *dt, const char *prop) +{ + const char **active_tp = NULL; + int count, tmp, score = 0; + const char *active; + int ret, i; + + count = of_property_count_strings(dt->parent, prop); + if (count <= 0 || count > 3) + return -ENODEV; + + active_tp = kcalloc(count, sizeof(char *), GFP_KERNEL); + if (!active_tp) { + NVT_ERR("FTS alloc failed\n"); + return -ENOMEM; + } + + ret = of_property_read_string_array(dt->parent, prop, + active_tp, count); + if (ret < 0) { + NVT_ERR("fail to read %s %d\n", prop, ret); + ret = -ENODEV; + goto out; + } + + for (i = 0; i < count; i++) { + active = active_tp[i]; + if (active != NULL) { + tmp = of_device_is_compatible(dt, active); + if (tmp > 0) + score++; + } + } + + if (score <= 0) { + NVT_ERR("not match this driver\n"); + ret = -ENODEV; + goto out; + } + ret = 0; +out: + kfree(active_tp); + return ret; +} +#endif + +/******************************************************* + * Description: + * Novatek touchscreen driver probe function. + * + * return: + * Executive outcomes. 0---succeed. negative---failed + *******************************************************/ +static int32_t nvt_ts_late_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int32_t ret = 0; +#if ((TOUCH_KEY_NUM > 0) || WAKEUP_GESTURE) + int32_t retry = 0; +#endif + //---request and config GPIOs--- + ret = nvt_gpio_config(ts); + if (ret) { + NVT_ERR("gpio config error!\n"); + goto err_gpio_config_failed; + } + + //---check chip version trim--- + ret = nvt_ts_check_chip_ver_trim(CHIP_VER_TRIM_ADDR); + if (ret) { + NVT_LOG("try to check from old chip ver trim address\n"); + ret = nvt_ts_check_chip_ver_trim(CHIP_VER_TRIM_OLD_ADDR); + if (ret) { + NVT_ERR("chip is not identified\n"); + ret = -EINVAL; + goto err_chipvertrim_failed; + } + } + + nvt_bootloader_reset(); + nvt_check_fw_reset_state(RESET_STATE_INIT); + nvt_get_fw_info(); + + //---allocate input device--- + ts->input_dev = input_allocate_device(); + if (ts->input_dev == NULL) { + NVT_ERR("allocate input device failed\n"); + ret = -ENOMEM; + goto err_input_dev_alloc_failed; + } + + ts->max_touch_num = TOUCH_MAX_FINGER_NUM; + +#if TOUCH_KEY_NUM > 0 + ts->max_button_num = TOUCH_KEY_NUM; +#endif + + ts->int_trigger_type = INT_TRIGGER_TYPE; + + + //---set input device info.--- + ts->input_dev->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS) ; + ts->input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + ts->input_dev->propbit[0] = BIT(INPUT_PROP_DIRECT); + +#if MT_PROTOCOL_B + input_mt_init_slots(ts->input_dev, ts->max_touch_num, 0); +#endif + + input_set_abs_params(ts->input_dev, ABS_MT_PRESSURE, 0, TOUCH_FORCE_NUM, 0, 0); //pressure = TOUCH_FORCE_NUM + +#if TOUCH_MAX_FINGER_NUM > 1 + input_set_abs_params(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0); //area = 255 + + input_set_abs_params(ts->input_dev, ABS_MT_POSITION_X, 0, ts->abs_x_max, 0, 0); + input_set_abs_params(ts->input_dev, ABS_MT_POSITION_Y, 0, ts->abs_y_max, 0, 0); +#if MT_PROTOCOL_B + // no need to set ABS_MT_TRACKING_ID, input_mt_init_slots() already set it +#else + input_set_abs_params(ts->input_dev, ABS_MT_TRACKING_ID, 0, ts->max_touch_num, 0, 0); +#endif //MT_PROTOCOL_B +#endif //TOUCH_MAX_FINGER_NUM > 1 + +#if TOUCH_KEY_NUM > 0 + for (retry = 0; retry < ts->max_button_num; retry++) { + input_set_capability(ts->input_dev, EV_KEY, touch_key_array[retry]); + } +#endif + +#if WAKEUP_GESTURE + for (retry = 0; retry < ARRAY_SIZE(gesture_key_array); retry++) { + input_set_capability(ts->input_dev, EV_KEY, gesture_key_array[retry]); + } +#endif + + snprintf(ts->phys, sizeof(ts->phys), "input/ts"); + ts->input_dev->name = NVT_TS_NAME; + ts->input_dev->phys = ts->phys; + ts->input_dev->id.bustype = BUS_I2C; + + //---register input device--- + ret = input_register_device(ts->input_dev); + if (ret) { + NVT_ERR("register input device (%s) failed. ret=%d\n", ts->input_dev->name, ret); + goto err_input_register_device_failed; + } + + //---set int-pin & request irq--- + client->irq = gpio_to_irq(ts->irq_gpio); + if (client->irq) { + NVT_LOG("int_trigger_type=%d\n", ts->int_trigger_type); + ts->irq_enabled = true; + ret = request_threaded_irq(client->irq, NULL, nvt_ts_work_func, + ts->int_trigger_type | IRQF_ONESHOT, NVT_I2C_NAME, ts); + if (ret != 0) { + NVT_ERR("request irq failed. ret=%d\n", ret); + goto err_int_request_failed; + } else { + nvt_irq_enable(false); + NVT_LOG("request irq %d succeed\n", client->irq); + } + } + +#if WAKEUP_GESTURE + device_init_wakeup(&ts->input_dev->dev, 1); +#endif + +#if BOOT_UPDATE_FIRMWARE + nvt_fwu_wq = alloc_workqueue("nvt_fwu_wq", WQ_UNBOUND | WQ_MEM_RECLAIM, 1); + if (!nvt_fwu_wq) { + NVT_ERR("nvt_fwu_wq create workqueue failed\n"); + ret = -ENOMEM; + goto err_create_nvt_fwu_wq_failed; + } + INIT_DELAYED_WORK(&ts->nvt_fwu_work, Boot_Update_Firmware); + // please make sure boot update start after display reset(RESX) sequence + queue_delayed_work(nvt_fwu_wq, &ts->nvt_fwu_work, msecs_to_jiffies(14000)); +#endif + + NVT_LOG("NVT_TOUCH_ESD_PROTECT is %d\n", NVT_TOUCH_ESD_PROTECT); +#if NVT_TOUCH_ESD_PROTECT + INIT_DELAYED_WORK(&nvt_esd_check_work, nvt_esd_check_func); + nvt_esd_check_wq = alloc_workqueue("nvt_esd_check_wq", WQ_MEM_RECLAIM, 1); + if (!nvt_esd_check_wq) { + NVT_ERR("nvt_esd_check_wq create workqueue failed\n"); + ret = -ENOMEM; + goto err_create_nvt_esd_check_wq_failed; + } + queue_delayed_work(nvt_esd_check_wq, &nvt_esd_check_work, + msecs_to_jiffies(NVT_TOUCH_ESD_CHECK_PERIOD)); +#endif /* #if NVT_TOUCH_ESD_PROTECT */ + + //---set device node--- +#if NVT_TOUCH_PROC + ret = nvt_flash_proc_init(); + if (ret != 0) { + NVT_ERR("nvt flash proc init failed. ret=%d\n", ret); + goto err_flash_proc_init_failed; + } +#endif + +#if NVT_TOUCH_EXT_PROC + ret = nvt_extra_proc_init(); + if (ret != 0) { + NVT_ERR("nvt extra proc init failed. ret=%d\n", ret); + goto err_extra_proc_init_failed; + } +#endif + +#if NVT_TOUCH_MP + ret = nvt_mp_proc_init(); + if (ret != 0) { + NVT_ERR("nvt mp proc init failed. ret=%d\n", ret); + goto err_mp_proc_init_failed; + } +#endif + + bTouchIsAwake = 1; + NVT_LOG("end\n"); + + nvt_irq_enable(true); + + return 0; + +#if NVT_TOUCH_MP +nvt_mp_proc_deinit(); +err_mp_proc_init_failed: +#endif +#if NVT_TOUCH_EXT_PROC +nvt_extra_proc_deinit(); +err_extra_proc_init_failed: +#endif +#if NVT_TOUCH_PROC +nvt_flash_proc_deinit(); +err_flash_proc_init_failed: +#endif +#if NVT_TOUCH_ESD_PROTECT + if (nvt_esd_check_wq) { + cancel_delayed_work_sync(&nvt_esd_check_work); + destroy_workqueue(nvt_esd_check_wq); + nvt_esd_check_wq = NULL; + } +err_create_nvt_esd_check_wq_failed: +#endif +#if BOOT_UPDATE_FIRMWARE + if (nvt_fwu_wq) { + cancel_delayed_work_sync(&ts->nvt_fwu_work); + destroy_workqueue(nvt_fwu_wq); + nvt_fwu_wq = NULL; + } +err_create_nvt_fwu_wq_failed: +#endif +#if WAKEUP_GESTURE + device_init_wakeup(&ts->input_dev->dev, 0); +#endif + free_irq(client->irq, ts); +err_int_request_failed: + input_unregister_device(ts->input_dev); + ts->input_dev = NULL; +err_input_register_device_failed: + if (ts->input_dev) { + input_free_device(ts->input_dev); + ts->input_dev = NULL; + } +err_input_dev_alloc_failed: +err_chipvertrim_failed: + nvt_gpio_deconfig(ts); +err_gpio_config_failed: + NVT_ERR("ret = %d\n", ret); + return ret; +} + +/******************************************************* + * Description: + * Novatek touchscreen driver probe function. + * + * return: + * Executive outcomes. 0---succeed. negative---failed + *******************************************************/ +static int32_t nvt_ts_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int32_t ret = 0; +#if defined(CONFIG_DRM) + struct device_node *dp = client->dev.of_node; +#endif + + NVT_LOG("start\n"); + +#if defined(CONFIG_DRM) + if (nvt_ts_check_dt(dp)) { + if (!nvt_ts_check_default_tp(dp, "qcom,i2c-touch-active")) + ret = -EPROBE_DEFER; + else + ret = -ENODEV; + + return ret; + } +#endif + + ts = devm_kzalloc(&client->dev, sizeof(struct nvt_ts_data), GFP_KERNEL); + if (ts == NULL) { + NVT_ERR("failed to allocated memory for nvt ts data\n"); + return -ENOMEM; + } + + ts->client = client; + i2c_set_clientdata(client, ts); + + //---parse dts--- + nvt_parse_dt(&client->dev); + + //---check i2c func.--- + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + NVT_ERR("i2c_check_functionality failed. (no I2C_FUNC_I2C)\n"); + return -ENODEV; + } + + mutex_init(&ts->lock); + mutex_init(&ts->xbuf_lock); + + ts->id = id; + +#if defined(CONFIG_DRM) + nvt_i2c_register_for_panel_events(client->dev.of_node, ts); +#elif defined(_MSM_DRM_NOTIFY_H_) + ts->drm_notif.notifier_call = nvt_drm_notifier_callback; + ret = msm_drm_register_client(&ts->drm_notif); + if (ret) { + NVT_ERR("register drm_notifier failed. ret=%d\n", ret); + goto err_register_drm_notif_failed; + } +#else + ts->fb_notif.notifier_call = nvt_fb_notifier_callback; + ts->fb_notif.notifier_call = nvt_fb_notifier_callback; + if (ret) { + NVT_ERR("register fb_notifier failed. ret=%d\n", ret); + goto err_register_fb_notif_failed; + } +#endif + NVT_LOG("end\n"); + return 0; +#if defined(CONFIG_DRM) + +#elif defined(_MSM_DRM_NOTIFY_H_) +err_register_drm_notif_failed: +#else +err_register_fb_notif_failed: +#endif + return -ENODEV; +} + +/******************************************************* + * Description: + * Novatek touchscreen driver release function. + * + * return: + * Executive outcomes. 0---succeed. + *******************************************************/ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0)) +static void nvt_ts_remove(struct i2c_client *client) +#else +static int32_t nvt_ts_remove(struct i2c_client *client) +#endif +{ + NVT_LOG("Removing driver...\n"); + +#if defined(CONFIG_DRM) + if (active_panel && ts->notifier_cookie) + panel_event_notifier_unregister(ts->notifier_cookie); +#elif defined(_MSM_DRM_NOTIFY_H_) + if (msm_drm_unregister_client(&ts->drm_notif)) + NVT_ERR("Error occurred while unregistering drm_notifier.\n"); +#else + if (fb_unregister_client(&ts->fb_notif)) + NVT_ERR("Error occurred while unregistering fb_notifier.\n"); +#endif + +#if NVT_TOUCH_MP + nvt_mp_proc_deinit(); +#endif +#if NVT_TOUCH_EXT_PROC + nvt_extra_proc_deinit(); +#endif +#if NVT_TOUCH_PROC + nvt_flash_proc_deinit(); +#endif + +#if NVT_TOUCH_ESD_PROTECT + if (nvt_esd_check_wq) { + cancel_delayed_work_sync(&nvt_esd_check_work); + nvt_esd_check_enable(false); + destroy_workqueue(nvt_esd_check_wq); + nvt_esd_check_wq = NULL; + } +#endif + +#if BOOT_UPDATE_FIRMWARE + if (nvt_fwu_wq) { + cancel_delayed_work_sync(&ts->nvt_fwu_work); + destroy_workqueue(nvt_fwu_wq); + nvt_fwu_wq = NULL; + } +#endif + +#if WAKEUP_GESTURE + if (ts->input_dev) + device_init_wakeup(&ts->input_dev->dev, 0); +#endif + + nvt_irq_enable(false); + free_irq(client->irq, ts); + + mutex_destroy(&ts->xbuf_lock); + mutex_destroy(&ts->lock); + + nvt_gpio_deconfig(ts); + + if (ts->input_dev) { + input_unregister_device(ts->input_dev); + ts->input_dev = NULL; + } + + i2c_set_clientdata(client, NULL); + + if (ts) { + kfree(ts); + ts = NULL; + } + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0)) + return 0; +#endif +} + +static void nvt_ts_shutdown(struct i2c_client *client) +{ + NVT_LOG("Shutdown driver...\n"); + + nvt_irq_enable(false); + +#if defined(CONFIG_DRM) + if (active_panel && ts->notifier_cookie) + panel_event_notifier_unregister(ts->notifier_cookie); +#elif defined(_MSM_DRM_NOTIFY_H_) + if (msm_drm_unregister_client(&ts->drm_notif)) + NVT_ERR("Error occurred while unregistering drm_notifier.\n"); +#else + if (fb_unregister_client(&ts->fb_notif)) + NVT_ERR("Error occurred while unregistering fb_notifier.\n"); +#endif + +#if NVT_TOUCH_MP + nvt_mp_proc_deinit(); +#endif +#if NVT_TOUCH_EXT_PROC + nvt_extra_proc_deinit(); +#endif +#if NVT_TOUCH_PROC + nvt_flash_proc_deinit(); +#endif + +#if NVT_TOUCH_ESD_PROTECT + if (nvt_esd_check_wq) { + cancel_delayed_work_sync(&nvt_esd_check_work); + nvt_esd_check_enable(false); + destroy_workqueue(nvt_esd_check_wq); + nvt_esd_check_wq = NULL; + } +#endif /* #if NVT_TOUCH_ESD_PROTECT */ + +#if BOOT_UPDATE_FIRMWARE + if (nvt_fwu_wq) { + cancel_delayed_work_sync(&ts->nvt_fwu_work); + destroy_workqueue(nvt_fwu_wq); + nvt_fwu_wq = NULL; + } +#endif + +#if WAKEUP_GESTURE + if (ts->input_dev) + device_init_wakeup(&ts->input_dev->dev, 0); +#endif +} + +/******************************************************* + * Description: + * Novatek touchscreen driver suspend function. + * + * return: + * Executive outcomes. 0---succeed. + *******************************************************/ +static int32_t nvt_ts_suspend(struct device *dev) +{ + uint8_t buf[4] = {0}; +#if MT_PROTOCOL_B + uint32_t i = 0; +#endif + + if (!bTouchIsAwake) { + NVT_LOG("Touch is already suspend\n"); + return 0; + } + +#if !WAKEUP_GESTURE + nvt_irq_enable(false); +#endif + +#if NVT_TOUCH_ESD_PROTECT + NVT_LOG("cancel delayed work sync\n"); + cancel_delayed_work_sync(&nvt_esd_check_work); + nvt_esd_check_enable(false); +#endif /* #if NVT_TOUCH_ESD_PROTECT */ + + mutex_lock(&ts->lock); + + NVT_LOG("start\n"); + + bTouchIsAwake = 0; + +#if WAKEUP_GESTURE + //---write command to enter "wakeup gesture mode"--- + buf[0] = EVENT_MAP_HOST_CMD; + buf[1] = 0x13; + CTP_I2C_WRITE(ts->client, I2C_FW_Address, buf, 2); + + enable_irq_wake(ts->client->irq); + + NVT_LOG("Enabled touch wakeup gesture\n"); + +#else // WAKEUP_GESTURE + //---write command to enter "deep sleep mode"--- + buf[0] = EVENT_MAP_HOST_CMD; + buf[1] = 0x11; + CTP_I2C_WRITE(ts->client, I2C_FW_Address, buf, 2); +#endif // WAKEUP_GESTURE + + mutex_unlock(&ts->lock); + + /* release all touches */ +#if MT_PROTOCOL_B + for (i = 0; i < ts->max_touch_num; i++) { + input_mt_slot(ts->input_dev, i); + input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0); + input_report_abs(ts->input_dev, ABS_MT_PRESSURE, 0); + input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, 0); + } +#endif + input_report_key(ts->input_dev, BTN_TOUCH, 0); +#if !MT_PROTOCOL_B + input_mt_sync(ts->input_dev); +#endif + input_sync(ts->input_dev); + + msleep(50); + + NVT_LOG("end\n"); + + return 0; +} + +/******************************************************* + * Description: + * Novatek touchscreen driver resume function. + * + * return: + * Executive outcomes. 0---succeed. + *******************************************************/ +static int32_t nvt_ts_resume(struct device *dev) +{ + NVT_LOG("start\n"); + + if (bTouchIsAwake || ts->fw_ver == 0) { + nvt_ts_late_probe(ts->client, ts->id); + NVT_LOG("nvt_ts_late_probe\n"); + return 0; + } + + mutex_lock(&ts->lock); + + // make sure display reset(RESX) sequence and dsi cmds sent before this +#if NVT_TOUCH_SUPPORT_HW_RST + gpio_set_value(ts->reset_gpio, 1); +#endif + + // NT36772 IC due to no boot-load when RESX/TP_RESX + // nvt_bootloader_reset(); + if (nvt_check_fw_reset_state(RESET_STATE_REK)) { + NVT_ERR("FW is not ready! Try to bootloader reset...\n"); + nvt_bootloader_reset(); + nvt_check_fw_reset_state(RESET_STATE_REK); + } + +#if !WAKEUP_GESTURE + nvt_irq_enable(true); +#endif + +#if NVT_TOUCH_ESD_PROTECT + nvt_esd_check_enable(false); + queue_delayed_work(nvt_esd_check_wq, &nvt_esd_check_work, + msecs_to_jiffies(NVT_TOUCH_ESD_CHECK_PERIOD)); +#endif /* #if NVT_TOUCH_ESD_PROTECT */ + + bTouchIsAwake = 1; + + mutex_unlock(&ts->lock); + + NVT_LOG("end\n"); + + return 0; +} + +#if defined(CONFIG_DRM) + +static void nvt_i2c_panel_notifier_callback(enum panel_event_notifier_tag tag, + struct panel_event_notification *notification, void *client_data) +{ + struct nvt_ts_data *ts = client_data; + + if (!notification) { + pr_err("Invalid notification\n"); + return; + } + + NVT_LOG("Notification type:%d, early_trigger:%d", + notification->notif_type, + notification->notif_data.early_trigger); + + switch (notification->notif_type) { + case DRM_PANEL_EVENT_UNBLANK: + if (notification->notif_data.early_trigger) + NVT_LOG("resume notification pre commit\n"); + else + nvt_ts_resume(&ts->client->dev); + break; + + case DRM_PANEL_EVENT_BLANK: + if (notification->notif_data.early_trigger) + nvt_ts_suspend(&ts->client->dev); + else + NVT_LOG("suspend notification post commit\n"); + break; + + case DRM_PANEL_EVENT_BLANK_LP: + NVT_LOG("received lp event\n"); + break; + + case DRM_PANEL_EVENT_FPS_CHANGE: + NVT_LOG("Received fps change old fps:%d new fps:%d\n", + notification->notif_data.old_fps, + notification->notif_data.new_fps); + break; + default: + NVT_LOG("notification serviced :%d\n", + notification->notif_type); + break; + } +} + +#elif defined(_MSM_DRM_NOTIFY_H_) +static int nvt_drm_notifier_callback(struct notifier_block *self, unsigned long event, void *data) +{ + struct msm_drm_notifier *evdata = data; + int *blank; + struct nvt_ts_data *ts = + container_of(self, struct nvt_ts_data, drm_notif); + + if (!evdata || (evdata->id != 0)) + return 0; + + if (evdata->data && ts) { + blank = evdata->data; + if (event == MSM_DRM_EARLY_EVENT_BLANK) { + if (*blank == MSM_DRM_BLANK_POWERDOWN) { + NVT_LOG("event=%lu, *blank=%d\n", event, *blank); + nvt_ts_suspend(&ts->client->dev); + } + } else if (event == MSM_DRM_EVENT_BLANK) { + if (*blank == MSM_DRM_BLANK_UNBLANK) { + NVT_LOG("event=%lu, *blank=%d\n", event, *blank); + nvt_ts_resume(&ts->client->dev); + } + } + } + + return 0; +} + +#else +static int nvt_fb_notifier_callback(struct notifier_block *self, unsigned long event, void *data) +{ + struct fb_event *evdata = data; + int *blank; + struct nvt_ts_data *ts = + container_of(self, struct nvt_ts_data, fb_notif); + + if (evdata && evdata->data && event == FB_EARLY_EVENT_BLANK) { + blank = evdata->data; + if (*blank == FB_BLANK_POWERDOWN) { + NVT_LOG("event=%lu, *blank=%d\n", event, *blank); + nvt_ts_suspend(&ts->client->dev); + } + } else if (evdata && evdata->data && event == FB_EVENT_BLANK) { + blank = evdata->data; + if (*blank == FB_BLANK_UNBLANK) { + NVT_LOG("event=%lu, *blank=%d\n", event, *blank); + nvt_ts_resume(&ts->client->dev); + } + } + + return 0; +} +#endif + +static const struct i2c_device_id nvt_ts_id[] = { + { NVT_I2C_NAME, 0 }, + { } +}; + +#ifdef CONFIG_OF +static struct of_device_id nvt_match_table[] = { + { .compatible = "novatek,NVT-ts",}, + { }, +}; +#endif + +static struct i2c_driver nvt_i2c_driver = { + .probe = nvt_ts_probe, + .remove = nvt_ts_remove, + .shutdown = nvt_ts_shutdown, + .id_table = nvt_ts_id, + .driver = { + .name = NVT_I2C_NAME, +#ifdef CONFIG_OF + .of_match_table = nvt_match_table, +#endif + }, +}; + +/******************************************************* + * Description: + * Driver Install function. + * + * return: + * Executive Outcomes. 0---succeed. not 0---failed. + ********************************************************/ +static int32_t __init nvt_driver_init(void) +{ + int32_t ret = 0; + + NVT_LOG("start\n"); + //---add i2c driver--- + ret = i2c_add_driver(&nvt_i2c_driver); + if (ret) { + NVT_ERR("failed to add i2c driver"); + goto err_driver; + } + + NVT_LOG("finished\n"); + +err_driver: + return ret; +} + +/******************************************************* + * Description: + * Driver uninstall function. + * + * return: + * n.a. + ********************************************************/ +static void __exit nvt_driver_exit(void) +{ + i2c_del_driver(&nvt_i2c_driver); +} + +//late_initcall(nvt_driver_init); +module_init(nvt_driver_init); +module_exit(nvt_driver_exit); + +MODULE_DESCRIPTION("Novatek Touchscreen Driver"); +MODULE_LICENSE("GPL v2"); + +#else /* NT36XXX_SPI */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(CONFIG_DRM) +#include +#endif + +#include "nt36xxx.h" +#if NVT_SPI_TOUCH_ESD_PROTECT +#include +#endif /* #if NVT_SPI_TOUCH_ESD_PROTECT */ + +#if NVT_SPI_TOUCH_ESD_PROTECT +static struct delayed_work nvt_spi_esd_check_work; +static struct workqueue_struct *nvt_spi_esd_check_wq; +static unsigned long nvt_spi_irq_timer; +uint8_t nvt_spi_esd_check; +uint8_t nvt_spi_esd_retry; +#endif /* #if NVT_SPI_TOUCH_ESD_PROTECT */ + +struct nvt_spi_data_t *nvt_spi_data; + +#if NVT_SPI_BOOT_UPDATE_FIRMWARE +static struct workqueue_struct *nvt_spi_fwu_wq; +#endif + +#if defined(CONFIG_DRM) +static struct drm_panel *active_spi_panel; +static void nvt_spi_panel_notifier_callback(enum panel_event_notifier_tag tag, + struct panel_event_notification *event, void *client_data); + +#elif defined(_MSM_DRM_NOTIFY_H_) +static int nvt_drm_notifier_callback( + struct notifier_block *self, unsigned long event, void *data); +#else +static int nvt_fb_notifier_callback( + struct notifier_block *self, unsigned long event, void *data); +#endif + +static uint32_t NVT_SPI_ENG_RST_ADDR = 0x7FFF80; +uint32_t NVT_SPI_SWRST_N8_ADDR; //read from dtsi +uint32_t NVT_SPI_RD_FAST_ADDR; //read from dtsi + +#if NVT_SPI_TOUCH_KEY_NUM > 0 +const uint16_t nvt_spi_touch_key_array[NVT_SPI_TOUCH_KEY_NUM] = { + KEY_BACK, + KEY_HOME, + KEY_MENU +}; +#endif + +#if NVT_SPI_WAKEUP_GESTURE +const uint16_t nvt_spi_gesture_key_array[] = { + KEY_POWER, //GESTURE_WORD_C + KEY_POWER, //GESTURE_WORD_W + KEY_POWER, //GESTURE_WORD_V + KEY_POWER, //GESTURE_DOUBLE_CLICK + KEY_POWER, //GESTURE_WORD_Z + KEY_POWER, //GESTURE_WORD_M + KEY_POWER, //GESTURE_WORD_O + KEY_POWER, //GESTURE_WORD_e + KEY_POWER, //GESTURE_WORD_S + KEY_POWER, //GESTURE_SLIDE_UP + KEY_POWER, //GESTURE_SLIDE_DOWN + KEY_POWER, //GESTURE_SLIDE_LEFT + KEY_POWER, //GESTURE_SLIDE_RIGHT +}; +#endif + +static uint8_t bTouchIsAwake; + +#if defined(CONFIG_DRM) +static void nvt_spi_register_for_panel_events(struct device_node *dp, + struct nvt_spi_data_t *ts) +{ + void *cookie = NULL; + + cookie = panel_event_notifier_register(PANEL_EVENT_NOTIFICATION_PRIMARY, + PANEL_EVENT_NOTIFIER_CLIENT_PRIMARY_TOUCH, active_spi_panel, + &nvt_spi_panel_notifier_callback, ts); + if (!cookie) { + pr_err("Failed to register for panel events\n"); + return; + } + + ts->notifier_cookie = cookie; +} +#endif + +/* + ******************************************************* + * Description: + * Novatek touchscreen irq enable/disable function. + * + * return: + * n.a. + ****************************************************** + */ +static void nvt_spi_irq_enable(bool enable) +{ + struct irq_desc *desc; + struct nvt_spi_data_t *ts = nvt_spi_data; + + if (enable) { + if (!ts->irq_enabled) { + enable_irq(ts->client->irq); + ts->irq_enabled = true; + } + } else { + if (ts->irq_enabled) { + disable_irq(ts->client->irq); + ts->irq_enabled = false; + } + } + + desc = irq_to_desc(ts->client->irq); + NVT_LOG("enable=%d, desc->depth=%d\n", enable, desc->depth); +} + +/* + ******************************************************* + * Description: + * Novatek touchscreen spi read/write core function. + * + * return: + * Executive outcomes. 0---succeed. + ****************************************************** + */ +static inline int32_t nvt_spi_read_write( + struct spi_device *client, uint8_t *buf, size_t len, enum NVT_SPI_RW rw) +{ + struct spi_message m; + struct spi_transfer t = { + .len = len, + }; + struct nvt_spi_data_t *ts = nvt_spi_data; + + memset(ts->xbuf, 0, len + NVT_SPI_DUMMY_BYTES); + memcpy(ts->xbuf, buf, len); + + switch (rw) { + case NVT_SPI_READ: + t.tx_buf = ts->xbuf; + t.rx_buf = ts->rbuf; + t.len = (len + NVT_SPI_DUMMY_BYTES); + break; + + case NVT_SPI_WRITE: + t.tx_buf = ts->xbuf; + break; + } + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + return spi_sync(client, &m); +} + +/* + ****************************************************** + * Description: + * Novatek touchscreen spi read function. + * + * return: + * Executive outcomes. 2---succeed. -5---I/O error + ****************************************************** + */ +int32_t nvt_spi_read(uint8_t *buf, uint16_t len) +{ + int32_t ret = -1; + int32_t retries = 0; + struct nvt_spi_data_t *ts = nvt_spi_data; + + mutex_lock(&ts->xbuf_lock); + + buf[0] = NVT_SPI_READ_MASK(buf[0]); + + while (retries < 5) { + ret = nvt_spi_read_write(ts->client, buf, len, NVT_SPI_READ); + if (ret == 0) + break; + retries++; + } + + if (unlikely(retries == 5)) { + NVT_ERR("read error, ret=%d\n", ret); + ret = -EIO; + } else + memcpy((buf+1), (ts->rbuf+2), (len-1)); + + mutex_unlock(&ts->xbuf_lock); + + return ret; +} + +/* + ****************************************************** + * Description: + * Novatek touchscreen spi write function. + * + * return: + * Executive outcomes. 1---succeed. -5---I/O error + ****************************************************** + */ +int32_t nvt_spi_write(uint8_t *buf, uint16_t len) +{ + int32_t ret = -1; + int32_t retries = 0; + struct nvt_spi_data_t *ts = nvt_spi_data; + + mutex_lock(&ts->xbuf_lock); + + buf[0] = NVT_SPI_WRITE_MASK(buf[0]); + + while (retries < 5) { + ret = nvt_spi_read_write(ts->client, buf, len, NVT_SPI_WRITE); + if (ret == 0) + break; + retries++; + } + + if (unlikely(retries == 5)) { + NVT_ERR("error, ret=%d\n", ret); + ret = -EIO; + } + + mutex_unlock(&ts->xbuf_lock); + + return ret; +} + +/* + ****************************************************** + * Description: + * Novatek touchscreen set index/page/addr address. + * + * return: + * Executive outcomes. 0---succeed. -5---access fail. + ****************************************************** + */ +int32_t nvt_spi_set_page(uint32_t addr) +{ + uint8_t buf[4] = {0}; + + buf[0] = 0xFF; //set index/page/addr command + buf[1] = (addr >> 15) & 0xFF; + buf[2] = (addr >> 7) & 0xFF; + + return nvt_spi_write(buf, 3); +} + +/* + ******************************************************* + * Description: + * Novatek touchscreen write data to specify address. + * + * return: + * Executive outcomes. 0---succeed. -5---access fail. + ******************************************************* + */ +int32_t nvt_spi_write_addr(uint32_t addr, uint8_t data) +{ + int32_t ret = 0; + uint8_t buf[4] = {0}; + + //---set xdata index--- + buf[0] = 0xFF; //set index/page/addr command + buf[1] = (addr >> 15) & 0xFF; + buf[2] = (addr >> 7) & 0xFF; + ret = nvt_spi_write(buf, 3); + if (ret) { + NVT_ERR("set page 0x%06X failed, ret = %d\n", addr, ret); + return ret; + } + + //---write data to index--- + buf[0] = addr & (0x7F); + buf[1] = data; + ret = nvt_spi_write(buf, 2); + if (ret) { + NVT_ERR("write data to 0x%06X failed, ret = %d\n", addr, ret); + return ret; + } + + return ret; +} + +/* + ******************************************************* + * Description: + * Novatek touchscreen enable hw bld crc function. + * + * return: + * N/A. + ****************************************************** + */ +void nvt_spi_bld_crc_enable(void) +{ + uint8_t buf[4] = {0}; + struct nvt_spi_data_t *ts = nvt_spi_data; + + //---set xdata index to BLD_CRC_EN_ADDR--- + nvt_spi_set_page(ts->mmap->BLD_CRC_EN_ADDR); + + //---read data from index--- + buf[0] = ts->mmap->BLD_CRC_EN_ADDR & (0x7F); + buf[1] = 0xFF; + nvt_spi_read(buf, 2); + + //---write data to index--- + buf[0] = ts->mmap->BLD_CRC_EN_ADDR & (0x7F); + buf[1] = buf[1] | (0x01 << 7); + nvt_spi_write(buf, 2); +} + +/* + ******************************************************* + * Description: + * Novatek touchscreen clear status & enable fw crc function. + * + * return: + * N/A. + ****************************************************** + */ +void nvt_spi_fw_crc_enable(void) +{ + uint8_t buf[4] = {0}; + struct nvt_spi_data_t *ts = nvt_spi_data; + + //---set xdata index to EVENT BUF ADDR--- + nvt_spi_set_page(ts->mmap->EVENT_BUF_ADDR); + + //---clear fw reset status--- + buf[0] = NVT_SPI_EVENT_MAP_RESET_COMPLETE & (0x7F); + buf[1] = 0x00; + nvt_spi_write(buf, 2); + + //---enable fw crc--- + buf[0] = NVT_SPI_EVENT_MAP_HOST_CMD & (0x7F); + buf[1] = 0xAE; //enable fw crc command + nvt_spi_write(buf, 2); +} + +/* + ******************************************************* + * Description: + * Novatek touchscreen set boot ready function. + * + * return: + * N/A. + ****************************************************** + */ +void nvt_spi_boot_ready(void) +{ + struct nvt_spi_data_t *ts = nvt_spi_data; + + //---write BOOT_RDY status cmds--- + nvt_spi_write_addr(ts->mmap->BOOT_RDY_ADDR, 1); + + mdelay(5); + + if (!ts->hw_crc) { + //---write BOOT_RDY status cmds--- + nvt_spi_write_addr(ts->mmap->BOOT_RDY_ADDR, 0); + + //---write POR_CD cmds--- + nvt_spi_write_addr(ts->mmap->POR_CD_ADDR, 0xA0); + } +} + +/* + ****************************************************** + * Description: + * Novatek touchscreen enable auto copy mode function. + * + * return: + * N/A. + ****************************************************** + */ +void nvt_spi_tx_auto_copy_mode(void) +{ + struct nvt_spi_data_t *ts = nvt_spi_data; + + //---write TX_AUTO_COPY_EN cmds--- + nvt_spi_write_addr(ts->mmap->TX_AUTO_COPY_EN, 0x69); + + NVT_ERR("tx auto copy mode enable\n"); +} + +/* + ****************************************************** + * Description: + * Novatek touchscreen check spi dma tx info function. + * + * return: + * N/A. + ****************************************************** + */ +int32_t nvt_spi_check_spi_dma_tx_info(void) +{ + uint8_t buf[8] = {0}; + int32_t i = 0; + const int32_t retry = 200; + struct nvt_spi_data_t *ts = nvt_spi_data; + + for (i = 0; i < retry; i++) { + //---set xdata index to EVENT BUF ADDR--- + nvt_spi_set_page(ts->mmap->SPI_DMA_TX_INFO); + + //---read fw status--- + buf[0] = ts->mmap->SPI_DMA_TX_INFO & 0x7F; + buf[1] = 0xFF; + nvt_spi_read(buf, 2); + + if (buf[1] == 0x00) + break; + + usleep_range(1000, 1100); + } + + if (i >= retry) { + NVT_ERR("failed, i=%d, buf[1]=0x%02X\n", i, buf[1]); + return -EIO; + } + + return 0; +} + +/* + ****************************************************** + * Description: + * Novatek touchscreen eng reset cmd function. + * + * return: + * n.a. + ****************************************************** + */ +void nvt_spi_eng_reset(void) +{ + //---eng reset cmds to ENG_RST_ADDR--- + nvt_spi_write_addr(NVT_SPI_ENG_RST_ADDR, 0x5A); + + mdelay(1); //wait tMCU_Idle2TP_REX_Hi after TP_RST +} + +/* + ******************************************************* + * Description: + * Novatek touchscreen reset MCU function. + * + * return: + * n.a. + ****************************************************** + */ +void nvt_spi_sw_reset(void) +{ + //---software reset cmds to SWRST_N8_ADDR--- + nvt_spi_write_addr(NVT_SPI_SWRST_N8_ADDR, 0x55); + + msleep(20); +} + +/* + ******************************************************* + * Description: + * Novatek touchscreen reset MCU then into idle mode + * function. + * + * return: + * n.a. + ****************************************************** + */ +void nvt_spi_sw_reset_idle(void) +{ + //---MCU idle cmds to SWRST_N8_ADDR--- + nvt_spi_write_addr(NVT_SPI_SWRST_N8_ADDR, 0xAA); + + msleep(20); +} + +/* + ****************************************************** + * Description: + * Novatek touchscreen reset MCU (boot) function. + * + * return: + * n.a. + ****************************************************** + */ +void nvt_spi_bootloader_reset(void) +{ + //---reset cmds to SWRST_N8_ADDR--- + nvt_spi_write_addr(NVT_SPI_SWRST_N8_ADDR, 0x69); + + mdelay(5); //wait tBRST2FR after Bootload RST + + if (NVT_SPI_RD_FAST_ADDR) { + /* disable SPI_RD_FAST */ + nvt_spi_write_addr(NVT_SPI_RD_FAST_ADDR, 0x00); + } + + NVT_LOG("end\n"); +} + +/* + ******************************************************* + * Description: + * Novatek touchscreen clear FW status function. + * + * return: + * Executive outcomes. 0---succeed. -1---fail. + ****************************************************** + */ +int32_t nvt_spi_clear_fw_status(void) +{ + uint32_t addr; + uint8_t buf[8] = {0}; + int32_t i = 0; + const int32_t retry = 20; + struct nvt_spi_data_t *ts = nvt_spi_data; + + for (i = 0; i < retry; i++) { + //---set xdata index to EVENT BUF ADDR--- + addr = ts->mmap->EVENT_BUF_ADDR; + nvt_spi_set_page(addr | NVT_SPI_EVENT_MAP_HANDSHAKING_or_SUB_CMD_BYTE); + + //---clear fw status--- + buf[0] = NVT_SPI_EVENT_MAP_HANDSHAKING_or_SUB_CMD_BYTE; + buf[1] = 0x00; + nvt_spi_write(buf, 2); + + //---read fw status--- + buf[0] = NVT_SPI_EVENT_MAP_HANDSHAKING_or_SUB_CMD_BYTE; + buf[1] = 0xFF; + nvt_spi_read(buf, 2); + + if (buf[1] == 0x00) + break; + + usleep_range(10000, 11000); + } + + if (i >= retry) { + NVT_ERR("failed, i=%d, buf[1]=0x%02X\n", i, buf[1]); + return -EIO; + } + + return 0; +} + +/* + ****************************************************** + * Description: + * Novatek touchscreen check FW status function. + + * return: + * Executive outcomes. 0---succeed. -1---failed. + ****************************************************** + */ +int32_t nvt_spi_check_fw_status(void) +{ + uint32_t addr; + uint8_t buf[8] = {0}; + int32_t i = 0; + const int32_t retry = 50; + struct nvt_spi_data_t *ts = nvt_spi_data; + + usleep_range(20000, 21000); + + for (i = 0; i < retry; i++) { + //---set xdata index to EVENT BUF ADDR--- + addr = ts->mmap->EVENT_BUF_ADDR; + nvt_spi_set_page(addr | NVT_SPI_EVENT_MAP_HANDSHAKING_or_SUB_CMD_BYTE); + + //---read fw status--- + buf[0] = NVT_SPI_EVENT_MAP_HANDSHAKING_or_SUB_CMD_BYTE; + buf[1] = 0x00; + nvt_spi_read(buf, 2); + + if ((buf[1] & 0xF0) == 0xA0) + break; + + usleep_range(10000, 11000); + } + + if (i >= retry) { + NVT_ERR("failed, i=%d, buf[1]=0x%02X\n", i, buf[1]); + return -EIO; + } + + return 0; +} + +/* + ****************************************************** + * Description: + * Novatek touchscreen check FW reset state function. + * + * return: + * Executive outcomes. 0---succeed. -1---failed. + ****************************************************** + */ +int32_t nvt_spi_check_fw_reset_state(enum NVT_SPI_RST_COMPLETE_STATE check_reset_state) +{ + uint8_t buf[8] = {0}; + int32_t ret = 0; + int32_t retry = 0; + int32_t retry_max = (check_reset_state == NVT_SPI_RESET_STATE_INIT) ? 10 : 50; + struct nvt_spi_data_t *ts = nvt_spi_data; + + //---set xdata index to EVENT BUF ADDR--- + nvt_spi_set_page(ts->mmap->EVENT_BUF_ADDR | NVT_SPI_EVENT_MAP_RESET_COMPLETE); + + while (1) { + //---read reset state--- + buf[0] = NVT_SPI_EVENT_MAP_RESET_COMPLETE; + buf[1] = 0x00; + nvt_spi_read(buf, 6); + + if ((buf[1] >= check_reset_state) && (buf[1] <= NVT_SPI_RESET_STATE_MAX)) { + ret = 0; + break; + } + + retry++; + if (unlikely(retry > retry_max)) { + NVT_ERR("error, retry=%d, buf[1]=0x%02X,0x%02X,0x%02X,0x%02X,0x%02X\n", + retry, buf[1], buf[2], buf[3], buf[4], buf[5]); + ret = -1; + break; + } + + usleep_range(10000, 11000); + } + + return ret; +} + +/* + ******************************************************* + * Description: + * Novatek touchscreen get firmware related information + * function. + * + * return: + * Executive outcomes. 0---success. -1---fail. + ****************************************************** + */ +int32_t nvt_spi_get_fw_info(void) +{ + uint8_t buf[64] = {0}; + uint32_t retry_count = 0; + int32_t ret = 0; + struct nvt_spi_data_t *ts = nvt_spi_data; + +info_retry: + //---set xdata index to EVENT BUF ADDR--- + nvt_spi_set_page(ts->mmap->EVENT_BUF_ADDR | NVT_SPI_EVENT_MAP_FWINFO); + + //---read fw info--- + buf[0] = NVT_SPI_EVENT_MAP_FWINFO; + nvt_spi_read(buf, 39); + if ((buf[1] + buf[2]) != 0xFF) { + NVT_ERR("FW info is broken! fw_ver=0x%02X, ~fw_ver=0x%02X\n", buf[1], buf[2]); + if (retry_count < 3) { + retry_count++; + NVT_ERR("retry_count=%d\n", retry_count); + goto info_retry; + } else { + ts->fw_ver = 0; + ts->abs_x_max = NVT_SPI_TOUCH_DEFAULT_MAX_WIDTH; + ts->abs_y_max = NVT_SPI_TOUCH_DEFAULT_MAX_HEIGHT; + ts->max_button_num = NVT_SPI_TOUCH_KEY_NUM; + NVT_ERR("Set default fw_ver=%d, abs_x_max=%d, ", + ts->fw_ver, ts->abs_x_max); + NVT_ERR("abs_y_max=%d, max_button_num=%d!\n", + ts->abs_y_max, ts->max_button_num); + ret = -1; + goto out; + } + } + ts->fw_ver = buf[1]; + ts->x_num = buf[3]; + ts->y_num = buf[4]; + ts->abs_x_max = (uint16_t)((buf[5] << 8) | buf[6]); + ts->abs_y_max = (uint16_t)((buf[7] << 8) | buf[8]); + ts->max_button_num = buf[11]; + ts->nvt_pid = (uint16_t)((buf[36] << 8) | buf[35]); + if (ts->pen_support) { + ts->x_gang_num = buf[37]; + ts->y_gang_num = buf[38]; + } + + NVT_ERR("fw_ver=0x%02X, fw_type=0x%02X, PID=0x%04X\n", ts->fw_ver, buf[14], ts->nvt_pid); + + ret = 0; +out: + + return ret; +} + +/* + ****************************************************** + * Create Device Node (Proc Entry) + ****************************************************** + */ +#if NVT_SPI_TOUCH_PROC +static struct proc_dir_entry *nvt_spi_proc_entry; +#define DEVICE_NAME "NVTSPI" + +/* + ****************************************************** + * Description: + * Novatek touchscreen /proc/NVTSPI read function. + * + * return: + * Executive outcomes. 2---succeed. -5,-14---failed. + ****************************************************** + */ +static ssize_t nvt_spi_flash_read( + struct file *file, char __user *buff, size_t count, loff_t *offp) +{ + uint8_t *str = NULL; + int32_t ret = 0; + int32_t retries = 0; + int8_t spi_wr = 0; + uint8_t *buf; + + if ((count > NVT_SPI_TRANSFER_LEN + 3) || (count < 3)) { + NVT_ERR("invalid transfer len!\n"); + return -EFAULT; + } + + /* allocate buffer for spi transfer */ + str = kzalloc((count), GFP_KERNEL); + if (str == NULL) { + NVT_ERR("kzalloc for buf failed!\n"); + ret = -ENOMEM; + goto kzalloc_failed; + } + + buf = kzalloc((count), GFP_KERNEL | GFP_DMA); + if (buf == NULL) { + NVT_ERR("kzalloc for buf failed!\n"); + ret = -ENOMEM; + kfree(str); + str = NULL; + goto kzalloc_failed; + } + + if (copy_from_user(str, buff, count)) { + NVT_ERR("copy from user error\n"); + ret = -EFAULT; + goto out; + } + +#if NVT_SPI_TOUCH_ESD_PROTECT + /* + * stop esd check work to avoid case that 0x77 report righ after here to enable esd + * check again finally lead to trigger esd recovery bootloader reset + */ + cancel_delayed_work_sync(&nvt_spi_esd_check_work); + nvt_spi_esd_check_enable(false); +#endif /* #if NVT_SPI_TOUCH_ESD_PROTECT */ + + spi_wr = str[0] >> 7; + memcpy(buf, str+2, ((str[0] & 0x7F) << 8) | str[1]); + + if (spi_wr == NVT_SPI_WRITE) { //SPI write + while (retries < 20) { + ret = nvt_spi_write(buf, ((str[0] & 0x7F) << 8) | str[1]); + if (!ret) + break; + NVT_ERR("error, retries=%d, ret=%d\n", retries, ret); + + retries++; + } + + if (unlikely(retries == 20)) { + NVT_ERR("error, ret = %d\n", ret); + ret = -EIO; + goto out; + } + } else if (spi_wr == NVT_SPI_READ) { //SPI read + while (retries < 20) { + ret = nvt_spi_read(buf, ((str[0] & 0x7F) << 8) | str[1]); + if (!ret) + break; + NVT_ERR("error, retries=%d, ret=%d\n", retries, ret); + + retries++; + } + + memcpy(str+2, buf, ((str[0] & 0x7F) << 8) | str[1]); + // copy buff to user if spi transfer + if (retries < 20) { + if (copy_to_user(buff, str, count)) { + ret = -EFAULT; + goto out; + } + } + + if (unlikely(retries == 20)) { + NVT_ERR("error, ret = %d\n", ret); + ret = -EIO; + goto out; + } + } else { + NVT_ERR("Call error, str[0]=%d\n", str[0]); + ret = -EFAULT; + goto out; + } + +out: + kfree(str); + kfree(buf); +kzalloc_failed: + return ret; +} + +/* + ****************************************************** + * Description: + * Novatek touchscreen /proc/NVTSPI open function. + * + * return: + * Executive outcomes. 0---succeed. -12---failed. + ****************************************************** + */ +static int32_t nvt_spi_flash_open(struct inode *inode, struct file *file) +{ + struct nvt_spi_flash_data *dev; + + dev = kmalloc(sizeof(struct nvt_spi_flash_data), GFP_KERNEL); + if (dev == NULL) { + NVT_ERR("Failed to allocate memory for nvt flash data\n"); + return -ENOMEM; + } + + rwlock_init(&dev->lock); + file->private_data = dev; + + return 0; +} + +/* + ****************************************************** + * Description: + * Novatek touchscreen /proc/NVTSPI close function. + * + * return: + * Executive outcomes. 0---succeed. + ****************************************************** + */ +static int32_t nvt_spi_flash_close(struct inode *inode, struct file *file) +{ + struct nvt_spi_flash_data *dev = file->private_data; + + kfree(dev); + + return 0; +} + +static const struct proc_ops nvt_flash_fops_spi = { + .proc_open = nvt_spi_flash_open, + .proc_release = nvt_spi_flash_close, + .proc_read = nvt_spi_flash_read, +}; + +/* + ****************************************************** + * Description: + * Novatek touchscreen /proc/NVTSPI initial function. + * + * return: + * Executive outcomes. 0---succeed. -12---failed. + ****************************************************** + */ +static int32_t nvt_spi_flash_proc_init(void) +{ + nvt_spi_proc_entry = proc_create(DEVICE_NAME, 0444, NULL, &nvt_flash_fops_spi); + if (nvt_spi_proc_entry == NULL) { + NVT_ERR("Failed!\n"); + return -ENOMEM; + } + + NVT_LOG("Succeeded!\n"); + + NVT_LOG("============================================================\n"); + NVT_LOG("Create /proc/%s\n", DEVICE_NAME); + NVT_LOG("============================================================\n"); + + return 0; +} + +/* + ****************************************************** + * Description: + * Novatek touchscreen /proc/NVTSPI deinitial function. + * + * return: + * n.a. + ****************************************************** + */ +static void nvt_spi_flash_proc_deinit(void) +{ + if (nvt_spi_proc_entry != NULL) { + remove_proc_entry(DEVICE_NAME, NULL); + nvt_spi_proc_entry = NULL; + NVT_LOG("Removed /proc/%s\n", DEVICE_NAME); + } +} +#endif + +#if NVT_SPI_WAKEUP_GESTURE +#define GESTURE_WORD_C 12 +#define GESTURE_WORD_W 13 +#define GESTURE_WORD_V 14 +#define GESTURE_DOUBLE_CLICK 15 +#define GESTURE_WORD_Z 16 +#define GESTURE_WORD_M 17 +#define GESTURE_WORD_O 18 +#define GESTURE_WORD_e 19 +#define GESTURE_WORD_S 20 +#define GESTURE_SLIDE_UP 21 +#define GESTURE_SLIDE_DOWN 22 +#define GESTURE_SLIDE_LEFT 23 +#define GESTURE_SLIDE_RIGHT 24 +/* customized gesture id */ +#define DATA_PROTOCOL 30 + +/* function page definition */ +#define FUNCPAGE_GESTURE 1 + +/* + ******************************************************* + * Description: + * Novatek touchscreen wake up gesture key report function. + * + * return: + * n.a. + ****************************************************** + */ +void nvt_spi_wakeup_gesture_report(uint8_t gesture_id, uint8_t *data) +{ + uint32_t keycode = 0; + uint8_t func_type = data[2]; + uint8_t func_id = data[3]; + struct nvt_spi_data_t *ts = nvt_spi_data; + + /* support fw specifal data protocol */ + if ((gesture_id == DATA_PROTOCOL) && (func_type == FUNCPAGE_GESTURE)) + gesture_id = func_id; + else if (gesture_id > DATA_PROTOCOL) { + NVT_ERR("gesture_id %d is invalid, func_type=%d, func_id=%d\n", + gesture_id, func_type, func_id); + return; + } + + NVT_LOG("gesture_id = %d\n", gesture_id); + + switch (gesture_id) { + case GESTURE_WORD_C: + NVT_LOG("Gesture : Word-C.\n"); + keycode = nvt_spi_gesture_key_array[0]; + break; + case GESTURE_WORD_W: + NVT_LOG("Gesture : Word-W.\n"); + keycode = nvt_spi_gesture_key_array[1]; + break; + case GESTURE_WORD_V: + NVT_LOG("Gesture : Word-V.\n"); + keycode = nvt_spi_gesture_key_array[2]; + break; + case GESTURE_DOUBLE_CLICK: + NVT_LOG("Gesture : Double Click.\n"); + keycode = nvt_spi_gesture_key_array[3]; + break; + case GESTURE_WORD_Z: + NVT_LOG("Gesture : Word-Z.\n"); + keycode = nvt_spi_gesture_key_array[4]; + break; + case GESTURE_WORD_M: + NVT_LOG("Gesture : Word-M.\n"); + keycode = nvt_spi_gesture_key_array[5]; + break; + case GESTURE_WORD_O: + NVT_LOG("Gesture : Word-O.\n"); + keycode = nvt_spi_gesture_key_array[6]; + break; + case GESTURE_WORD_e: + NVT_LOG("Gesture : Word-e.\n"); + keycode = nvt_spi_gesture_key_array[7]; + break; + case GESTURE_WORD_S: + NVT_LOG("Gesture : Word-S.\n"); + keycode = nvt_spi_gesture_key_array[8]; + break; + case GESTURE_SLIDE_UP: + NVT_LOG("Gesture : Slide UP.\n"); + keycode = nvt_spi_gesture_key_array[9]; + break; + case GESTURE_SLIDE_DOWN: + NVT_LOG("Gesture : Slide DOWN.\n"); + keycode = nvt_spi_gesture_key_array[10]; + break; + case GESTURE_SLIDE_LEFT: + NVT_LOG("Gesture : Slide LEFT.\n"); + keycode = nvt_spi_gesture_key_array[11]; + break; + case GESTURE_SLIDE_RIGHT: + NVT_LOG("Gesture : Slide RIGHT.\n"); + keycode = nvt_spi_gesture_key_array[12]; + break; + default: + break; + } + + if (keycode > 0) { + input_report_key(ts->input_dev, keycode, 1); + input_sync(ts->input_dev); + input_report_key(ts->input_dev, keycode, 0); + input_sync(ts->input_dev); + } +} +#endif + +/* + ****************************************************** + * Description: + * Novatek touchscreen parse device tree function. + * + * return: + * n.a. + ****************************************************** + */ +#ifdef CONFIG_OF +static int32_t nvt_spi_parse_dt(struct device *dev) +{ + struct device_node *np = dev->of_node; + int32_t ret = 0; + struct nvt_spi_data_t *ts = nvt_spi_data; + +#if NVT_SPI_TOUCH_SUPPORT_HW_RST + ts->reset_gpio = of_get_named_gpio_flags(np, "novatek,reset-gpio", 0, &ts->reset_flags); + NVT_LOG("novatek,reset-gpio=%d\n", ts->reset_gpio); +#endif + ts->irq_gpio = of_get_named_gpio_flags(np, "novatek,irq-gpio", 0, &ts->irq_flags); + NVT_LOG("novatek,irq-gpio=%d\n", ts->irq_gpio); + + ts->pen_support = of_property_read_bool(np, "novatek,pen-support"); + NVT_LOG("novatek,pen-support=%d\n", ts->pen_support); + + ts->wgp_stylus = of_property_read_bool(np, "novatek,wgp-stylus"); + NVT_LOG("novatek,wgp-stylus=%d\n", ts->wgp_stylus); + + ret = of_property_read_u32(np, "novatek,swrst-n8-addr", &NVT_SPI_SWRST_N8_ADDR); + if (ret) { + NVT_ERR("error reading novatek,swrst-n8-addr. ret=%d\n", ret); + return ret; + } + NVT_LOG("SWRST_N8_ADDR=0x%06X\n", NVT_SPI_SWRST_N8_ADDR); + + ret = of_property_read_u32(np, "novatek,spi-rd-fast-addr", &NVT_SPI_RD_FAST_ADDR); + if (ret) { + NVT_LOG("not support novatek,spi-rd-fast-addr\n"); + NVT_SPI_RD_FAST_ADDR = 0; + ret = 0; + } else + NVT_LOG("SPI_RD_FAST_ADDR=0x%06X\n", NVT_SPI_RD_FAST_ADDR); + + return ret; +} +#else +static int32_t nvt_spi_parse_dt(struct device *dev) +{ + struct nvt_spi_data_t *ts = nvt_spi_data; + +#if NVT_SPI_TOUCH_SUPPORT_HW_RST + ts->reset_gpio = NVTTOUCH_RST_PIN; +#endif + ts->irq_gpio = NVTTOUCH_INT_PIN; + return 0; +} +#endif + +/* + ****************************************************** + * Description: + * Novatek touchscreen config and request gpio + * + * return: + * Executive outcomes. 0---succeed. not 0---failed. + ****************************************************** + */ +static int nvt_spi_gpio_config(struct nvt_spi_data_t *ts) +{ + int32_t ret = 0; + +#if NVT_SPI_TOUCH_SUPPORT_HW_RST + /* request RST-pin (Output/High) */ + if (gpio_is_valid(ts->reset_gpio)) { + ret = gpio_request_one(ts->reset_gpio, GPIOF_OUT_INIT_LOW, "NVT-tp-rst"); + if (ret) { + NVT_ERR("Failed to request NVT-tp-rst GPIO\n"); + goto err_request_reset_gpio; + } + } +#endif + + /* request INT-pin (Input) */ + if (gpio_is_valid(ts->irq_gpio)) { + ret = gpio_request_one(ts->irq_gpio, GPIOF_IN, "NVT-int"); + if (ret) { + NVT_ERR("Failed to request NVT-int GPIO\n"); + goto err_request_irq_gpio; + } + } + + return ret; + +err_request_irq_gpio: +#if NVT_SPI_TOUCH_SUPPORT_HW_RST + gpio_free(ts->reset_gpio); +err_request_reset_gpio: +#endif + return ret; +} + +/* + ****************************************************** + * Description: + * Novatek touchscreen deconfig gpio + * + * return: + * n.a. + ****************************************************** + */ +static void nvt_spi_gpio_deconfig(struct nvt_spi_data_t *ts) +{ + if (gpio_is_valid(ts->irq_gpio)) + gpio_free(ts->irq_gpio); +#if NVT_SPI_TOUCH_SUPPORT_HW_RST + if (gpio_is_valid(ts->reset_gpio)) + gpio_free(ts->reset_gpio); +#endif +} + +static uint8_t nvt_spi_fw_recovery(uint8_t *point_data) +{ + uint8_t i = 0; + uint8_t detected = true; + + /* check pattern */ + for (i = 1; i < 7; i++) { + if (point_data[i] != 0x77) { + detected = false; + break; + } + } + + return detected; +} + +#if NVT_SPI_TOUCH_ESD_PROTECT +void nvt_spi_esd_check_enable(uint8_t enable) +{ + /* update interrupt timer */ + nvt_spi_irq_timer = jiffies; + /* clear esd_retry counter, if protect function is enabled */ + nvt_spi_esd_retry = enable ? 0 : nvt_spi_esd_retry; + /* enable/disable esd check flag */ + nvt_spi_esd_check = enable; +} + +static void nvt_spi_esd_check_func(struct work_struct *work) +{ + unsigned int timer = jiffies_to_msecs(jiffies - nvt_spi_irq_timer); + struct nvt_spi_data_t *ts = nvt_spi_data; + + //NVT_LOG("esd_check = %d (retry %d)\n", esd_check, esd_retry); + + if ((timer > NVT_SPI_TOUCH_ESD_CHECK_PERIOD) && esd_check) { + mutex_lock(&ts->lock); + NVT_ERR("do ESD recovery, timer = %d, retry = %d\n", timer, nvt_spi_esd_retry); + /* do esd recovery, reload fw */ + nvt_spi_update_firmware(NVT_SPI_BOOT_UPDATE_FIRMWARE_NAME); + mutex_unlock(&ts->lock); + /* update interrupt timer */ + nvt_spi_irq_timer = jiffies; + /* update esd_retry counter */ + nvt_spi_esd_retry++; + } + + queue_delayed_work(nvt_spi_esd_check_wq, &nvt_spi_esd_check_work, + msecs_to_jiffies(NVT_SPI_TOUCH_ESD_CHECK_PERIOD)); +} +#endif /* #if NVT_SPI_TOUCH_ESD_PROTECT */ + +#define NVT_SPI_PEN_DATA_LEN 14 +#if NVT_SPI_CHECK_PEN_DATA_CHECKSUM +static int32_t nvt_spi_pen_data_checksum(uint8_t *buf, uint8_t length) +{ + uint8_t checksum = 0; + int32_t i = 0; + + // Calculate checksum + for (i = 0; i < length - 1; i++) + checksum += buf[i]; + checksum = (~checksum + 1); + + // Compare ckecksum and dump fail data + if (checksum != buf[length - 1]) { + NVT_ERR("pen packet checksum not match. (buf[%d]=0x%02X, checksum=0x%02X)\n", + length - 1, buf[length - 1], checksum); + //--- dump pen buf --- + for (i = 0; i < length; i++) + NVT_ERR("%02X ", buf[i]); + + NVT_ERR("\n"); + + return -EIO; + } + + return 0; +} +#endif // #if NVT_SPI_CHECK_PEN_DATA_CHECKSUM + +#if NVT_SPI_TOUCH_WDT_RECOVERY +static uint8_t nvt_spi_recovery_cnt; +static uint8_t nvt_spi_wdt_fw_recovery(uint8_t *point_data) +{ + uint32_t recovery_cnt_max = 10; + uint8_t recovery_enable = false; + uint8_t i = 0; + + nvt_spi_recovery_cnt++; + + /* check pattern */ + for (i = 1 ; i < 7 ; i++) { + if ((point_data[i] != 0xFD) && (point_data[i] != 0xFE)) { + nvt_spi_recovery_cnt = 0; + break; + } + } + + if (nvt_spi_recovery_cnt > recovery_cnt_max) { + recovery_enable = true; + nvt_spi_recovery_cnt = 0; + } + + return recovery_enable; +} +#endif /* #if NVT_SPI_TOUCH_WDT_RECOVERY */ + +#if NVT_SPI_POINT_DATA_CHECKSUM +static int32_t nvt_spi_point_data_checksum(uint8_t *buf, uint8_t length) +{ + uint8_t checksum = 0; + int32_t i = 0; + + // Generate checksum + for (i = 0; i < length - 1; i++) + checksum += buf[i + 1]; + + checksum = (~checksum + 1); + + // Compare ckecksum and dump fail data + if (checksum != buf[length]) { + NVT_ERR("packet checksum not match. (point_data[%d]=0x%02X, checksum=0x%02X)\n", + length, buf[length], checksum); + + for (i = 0; i < 10; i++) + NVT_LOG("%02X %02X %02X %02X %02X %02X\n", + buf[1 + i * 6], buf[2 + i * 6], buf[3 + i * 6], + buf[4 + i * 6], buf[5 + i * 6], buf[6 + i * 6]); + + NVT_LOG("%02X %02X %02X %02X %02X\n", + buf[61], buf[62], buf[63], buf[64], buf[65]); + return -EIO; + } + + return 0; +} +#endif /* NVT_SPI_POINT_DATA_CHECKSUM */ + +/* + ****************************************************** + * Description: + * Novatek touchscreen work function. + * + * return: + * n.a. + ****************************************************** + */ +#define NVT_SPI_POINT_DATA_LEN 65 +#define NVT_SPI_POINT (NVT_SPI_POINT_DATA_LEN + NVT_SPI_PEN_DATA_LEN + 1 + NVT_SPI_DUMMY_BYTES) +static irqreturn_t nvt_spi_work_func(int irq, void *data) +{ + int32_t ret = -1; + uint8_t point_data[NVT_SPI_POINT] = {0}; + uint32_t position = 0; + uint32_t input_x = 0; + uint32_t input_y = 0; + uint32_t input_w = 0; + uint32_t input_p = 0; + uint8_t input_id = 0; +#if NVT_SPI_MT_PROTOCOL_B + uint8_t press_id[NVT_SPI_TOUCH_MAX_FINGER_NUM] = {0}; +#endif /* NVT_SPI_MT_PROTOCOL_B */ + int32_t i = 0; + int32_t finger_cnt = 0; + uint8_t pen_format_id = 0; + uint32_t pen_x = 0; + uint32_t pen_y = 0; + uint32_t pen_pressure = 0; + uint32_t pen_distance = 0; + int8_t pen_tilt_x = 0; + int8_t pen_tilt_y = 0; + uint32_t pen_btn1 = 0; + uint32_t pen_btn2 = 0; + uint32_t pen_battery = 0; + struct nvt_spi_data_t *ts = nvt_spi_data; + +#if NVT_SPI_WAKEUP_GESTURE + if (bTouchIsAwake == 0) + pm_wakeup_event(&ts->input_dev->dev, 5000); + +#endif + + mutex_lock(&ts->lock); + + if (ts->pen_support) + ret = nvt_spi_read(point_data, NVT_SPI_POINT_DATA_LEN + NVT_SPI_PEN_DATA_LEN + 1); + else + ret = nvt_spi_read(point_data, NVT_SPI_POINT_DATA_LEN + 1); + + if (ret < 0) { + NVT_ERR("nvt_spi_read failed.(%d)\n", ret); + goto XFER_ERROR; + } + + //--- dump SPI buf --- +/* + * for (i = 0; i < 10; i++) { + * printk("%02X %02X %02X %02X %02X %02X ", + * point_data[1+i*6], point_data[2+i*6], point_data[3+i*6], + * point_data[4+i*6], point_data[5+i*6], point_data[6+i*6]); + * } + * printk("\n"); + */ + +#if NVT_SPI_TOUCH_WDT_RECOVERY + /* ESD protect by WDT */ + if (nvt_spi_wdt_fw_recovery(point_data)) { + NVT_ERR("Recover for fw reset, %02X\n", point_data[1]); + nvt_spi_update_firmware(NVT_SPI_BOOT_UPDATE_FIRMWARE_NAME); + goto XFER_ERROR; + } +#endif /* #if NVT_SPI_TOUCH_WDT_RECOVERY */ + + /* ESD protect by FW handshake */ + if (nvt_spi_fw_recovery(point_data)) { +#if NVT_SPI_TOUCH_ESD_PROTECT + nvt_spi_esd_check_enable(true); +#endif /* #if NVT_SPI_TOUCH_ESD_PROTECT */ + goto XFER_ERROR; + } + +#if NVT_SPI_POINT_DATA_CHECKSUM + if (NVT_SPI_POINT_DATA_LEN >= NVT_SPI_POINT_DATA_CHECKSUM_LEN) { + ret = nvt_spi_point_data_checksum(point_data, NVT_SPI_POINT_DATA_CHECKSUM_LEN); + if (ret) + goto XFER_ERROR; + } +#endif /* NVT_SPI_POINT_DATA_CHECKSUM */ + +#if NVT_SPI_WAKEUP_GESTURE + if (bTouchIsAwake == 0) { + input_id = (uint8_t)(point_data[1] >> 3); + nvt_spi_wakeup_gesture_report(input_id, point_data); + mutex_unlock(&ts->lock); + return IRQ_HANDLED; + } +#endif + + finger_cnt = 0; + + for (i = 0; i < ts->max_touch_num; i++) { + position = 1 + 6 * i; + input_id = (uint8_t)(point_data[position + 0] >> 3); + if ((input_id == 0) || (input_id > ts->max_touch_num)) + continue; + + if (((point_data[position] & 0x07) == 0x01) + || ((point_data[position] & 0x07) == 0x02)) { + //finger down (enter & moving) +#if NVT_SPI_TOUCH_ESD_PROTECT + /* update interrupt timer */ + nvt_spi_irq_timer = jiffies; +#endif /* #if NVT_SPI_TOUCH_ESD_PROTECT */ + input_x = (uint32_t)(point_data[position + 1] << 4); + input_x += (uint32_t) (point_data[position + 3] >> 4); + + input_y = (uint32_t)(point_data[position + 2] << 4); + input_y += (uint32_t) (point_data[position + 3] & 0x0F); + if ((input_x < 0) || (input_y < 0)) + continue; + if ((input_x > ts->abs_x_max) || (input_y > ts->abs_y_max)) + continue; + input_w = (uint32_t)(point_data[position + 4]); + if (input_w == 0) + input_w = 1; + if (i < 2) { + input_p = (uint32_t)(point_data[position + 5]); + input_p += (uint32_t)(point_data[i + 63] << 8); + if (input_p > NVT_SPI_TOUCH_FORCE_NUM) + input_p = NVT_SPI_TOUCH_FORCE_NUM; + } else + input_p = (uint32_t)(point_data[position + 5]); + + if (input_p == 0) + input_p = 1; + +#if NVT_SPI_MT_PROTOCOL_B + press_id[input_id - 1] = 1; + input_mt_slot(ts->input_dev, input_id - 1); + input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, true); +#else /* NVT_SPI_MT_PROTOCOL_B */ + input_report_abs(ts->input_dev, ABS_MT_TRACKING_ID, input_id - 1); + input_report_key(ts->input_dev, BTN_TOUCH, 1); +#endif /* NVT_SPI_MT_PROTOCOL_B */ + + input_report_abs(ts->input_dev, ABS_MT_POSITION_X, input_x); + input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, input_y); + input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, input_w); + input_report_abs(ts->input_dev, ABS_MT_PRESSURE, input_p); + +#if NVT_SPI_MT_PROTOCOL_B +#else /* NVT_SPI_MT_PROTOCOL_B */ + input_mt_sync(ts->input_dev); +#endif /* NVT_SPI_MT_PROTOCOL_B */ + + finger_cnt++; + } + } + +#if NVT_SPI_MT_PROTOCOL_B + for (i = 0; i < ts->max_touch_num; i++) { + if (press_id[i] != 1) { + input_mt_slot(ts->input_dev, i); + input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0); + input_report_abs(ts->input_dev, ABS_MT_PRESSURE, 0); + input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, false); + } + } + + input_report_key(ts->input_dev, BTN_TOUCH, (finger_cnt > 0)); +#else /* NVT_SPI_MT_PROTOCOL_B */ + if (finger_cnt == 0) { + input_report_key(ts->input_dev, BTN_TOUCH, 0); + input_mt_sync(ts->input_dev); + } +#endif /* NVT_SPI_MT_PROTOCOL_B */ + +#if NVT_SPI_TOUCH_KEY_NUM > 0 + if (point_data[61] == 0xF8) { +#if NVT_SPI_TOUCH_ESD_PROTECT + /* update interrupt timer */ + nvt_spi_irq_timer = jiffies; +#endif /* #if NVT_SPI_TOUCH_ESD_PROTECT */ + for (i = 0; i < ts->max_button_num; i++) + input_report_key(ts->input_dev, nvt_spi_touch_key_array[i], + ((point_data[62] >> i) & 0x01)); + + } else { + for (i = 0; i < ts->max_button_num; i++) + input_report_key(ts->input_dev, nvt_spi_touch_key_array[i], 0); + } +#endif + + input_sync(ts->input_dev); + + if (ts->pen_support) { + + //--- dump pen buf --- +/* + * printk("%02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n", + * point_data[66], point_data[67], point_data[68], point_data[69], + * point_data[70], point_data[71], point_data[72], point_data[73], + * point_data[74], point_data[75], point_data[76], point_data[77], + * point_data[78], point_data[79]); + */ +#if NVT_SPI_CHECK_PEN_DATA_CHECKSUM + if (nvt_spi_pen_data_checksum(&point_data[66], NVT_SPI_PEN_DATA_LEN)) { + // pen data packet checksum not match, skip it + goto XFER_ERROR; + } +#endif // #if NVT_SPI_CHECK_PEN_DATA_CHECKSUM + + // parse and handle pen report + pen_format_id = point_data[66]; + if (pen_format_id != 0xFF) { + if (pen_format_id == 0x01) { + // report pen data + pen_x = (uint32_t)(point_data[67] << 8); + pen_x += (uint32_t)(point_data[68]); + + pen_y = (uint32_t)(point_data[69] << 8); + pen_y += (uint32_t)(point_data[70]); + + pen_pressure = (uint32_t)(point_data[71] << 8); + pen_pressure += (uint32_t)(point_data[72]); + + pen_tilt_x = (int32_t)point_data[73]; + pen_tilt_y = (int32_t)point_data[74]; + + pen_distance = (uint32_t)(point_data[75] << 8); + pen_distance += (uint32_t)(point_data[76]); + + pen_btn1 = (uint32_t)(point_data[77] & 0x01); + pen_btn2 = (uint32_t)((point_data[77] >> 1) & 0x01); + pen_battery = (uint32_t)point_data[78]; +// printk("x=%d,y=%d,p=%d,tx=%d,ty=%d,d=%d,b1=%d,b2=%d,bat=%d\n", +// pen_x, pen_y, pen_pressure, pen_tilt_x, pen_tilt_y, +// pen_distance, pen_btn1, pen_btn2, pen_battery); + + input_report_abs(ts->pen_input_dev, ABS_X, pen_x); + input_report_abs(ts->pen_input_dev, ABS_Y, pen_y); + input_report_abs(ts->pen_input_dev, ABS_PRESSURE, pen_pressure); + input_report_key(ts->pen_input_dev, BTN_TOUCH, !!pen_pressure); + input_report_abs(ts->pen_input_dev, ABS_TILT_X, pen_tilt_x); + input_report_abs(ts->pen_input_dev, ABS_TILT_Y, pen_tilt_y); + input_report_abs(ts->pen_input_dev, ABS_DISTANCE, pen_distance); + input_report_key(ts->pen_input_dev, BTN_TOOL_PEN, + !!pen_distance || !!pen_pressure); + input_report_key(ts->pen_input_dev, BTN_STYLUS, pen_btn1); + input_report_key(ts->pen_input_dev, BTN_STYLUS2, pen_btn2); + // TBD: pen battery event report + // NVT_LOG("pen_battery=%d\n", pen_battery); + } else if (pen_format_id == 0xF0) { + // report Pen ID + } else { + NVT_ERR("Unknown pen format id!\n"); + goto XFER_ERROR; + } + } else { // pen_format_id = 0xFF, i.e. no pen present + input_report_abs(ts->pen_input_dev, ABS_X, 0); + input_report_abs(ts->pen_input_dev, ABS_Y, 0); + input_report_abs(ts->pen_input_dev, ABS_PRESSURE, 0); + input_report_abs(ts->pen_input_dev, ABS_TILT_X, 0); + input_report_abs(ts->pen_input_dev, ABS_TILT_Y, 0); + input_report_abs(ts->pen_input_dev, ABS_DISTANCE, 0); + input_report_key(ts->pen_input_dev, BTN_TOUCH, 0); + input_report_key(ts->pen_input_dev, BTN_TOOL_PEN, 0); + input_report_key(ts->pen_input_dev, BTN_STYLUS, 0); + input_report_key(ts->pen_input_dev, BTN_STYLUS2, 0); + } + + input_sync(ts->pen_input_dev); + } /* if (ts->pen_support) */ + +XFER_ERROR: + + mutex_unlock(&ts->lock); + + return IRQ_HANDLED; +} + + +/* + ******************************************************* + * Description: + * Novatek touchscreen check chip version trim function. + * + * return: + * Executive outcomes. 0---NVT IC. -1---not NVT IC. + ****************************************************** + */ +static int8_t nvt_spi_check_chip_ver_trim(uint32_t chip_ver_trim_addr) +{ + uint8_t buf[8] = {0}; + int32_t retry = 0; + int32_t list = 0; + int32_t i = 0; + int32_t found_nvt_chip = 0; + int32_t ret = -1; + struct nvt_spi_data_t *ts = nvt_spi_data; + + //---Check for 5 times--- + for (retry = 5; retry > 0; retry--) { + + nvt_spi_bootloader_reset(); + + nvt_spi_set_page(chip_ver_trim_addr); + + buf[0] = chip_ver_trim_addr & 0x7F; + buf[1] = 0x00; + buf[2] = 0x00; + buf[3] = 0x00; + buf[4] = 0x00; + buf[5] = 0x00; + buf[6] = 0x00; + nvt_spi_read(buf, 7); + NVT_LOG("buf[1]=0x%02X,[2]=0x%02X,[3]=0x%02X,[4]=0x%02X,[5]=0x%02X,[6]=0x%02X\n", + buf[1], buf[2], buf[3], buf[4], buf[5], buf[6]); + + // compare read chip id on supported list + for (list = 0; list < ARRAY_SIZE(nvt_spi_trim_id_table); list++) { + found_nvt_chip = 0; + + // compare each byte + for (i = 0; i < NVT_SPI_ID_BYTE_MAX; i++) { + if (nvt_spi_trim_id_table[list].mask[i]) { + if (buf[i + 1] != nvt_spi_trim_id_table[list].id[i]) + break; + } + } + + if (i == NVT_SPI_ID_BYTE_MAX) + found_nvt_chip = 1; + + if (found_nvt_chip) { + NVT_LOG("This is NVT touch IC\n"); + ts->mmap = nvt_spi_trim_id_table[list].mmap; + ts->hw_crc = nvt_spi_trim_id_table[list].hwinfo->hw_crc; + ret = 0; + goto out; + } else { + ts->mmap = NULL; + ret = -1; + } + } + + msleep(20); + } + +out: + return ret; +} + +#if defined(CONFIG_DRM) +static int nvt_spi_check_dt(struct device_node *np) +{ + int i; + int count; + struct device_node *node; + struct drm_panel *panel; + + count = of_count_phandle_with_args(np, "panel", NULL); + if (count <= 0) + return 0; + + for (i = 0; i < count; i++) { + node = of_parse_phandle(np, "panel", i); + panel = of_drm_find_panel(node); + of_node_put(node); + if (!IS_ERR(panel)) { + active_spi_panel = panel; + return 0; + } + } + + return PTR_ERR(panel); +} +#endif + +static int32_t nvt_spi_late_probe(struct spi_device *client) +{ + int32_t ret = 0; + + //---check chip version trim--- + ret = nvt_spi_check_chip_ver_trim(NVT_SPI_CHIP_VER_TRIM_ADDR); + if (ret) { + NVT_LOG("try to check from old chip ver trim address\n"); + ret = nvt_spi_check_chip_ver_trim(NVT_SPI_CHIP_VER_TRIM_OLD_ADDR); + if (ret) { + NVT_ERR("chip is not identified\n"); + ret = -EINVAL; + return ret; + } + } + + if (nvt_spi_update_firmware(NVT_SPI_BOOT_UPDATE_FIRMWARE_NAME)) + NVT_ERR("download firmware failed, ignore check fw state\n"); + else + nvt_spi_check_fw_reset_state(NVT_SPI_RESET_STATE_REK); + + //---set device node--- +#if NVT_SPI_TOUCH_PROC + ret = nvt_spi_flash_proc_init(); + if (ret != 0) { + NVT_ERR("nvt flash proc init failed. ret=%d\n", ret); + goto err_flash_proc_init_failed; + } +#endif + +#if NVT_SPI_TOUCH_EXT_PROC + ret = nvt_spi_extra_proc_init(); + if (ret != 0) { + NVT_ERR("nvt extra proc init failed. ret=%d\n", ret); + goto err_extra_proc_init_failed; + } +#endif + +#if NVT_SPI_TOUCH_MP + ret = nvt_spi_mp_proc_init(); + if (ret != 0) { + NVT_ERR("nvt mp proc init failed. ret=%d\n", ret); + goto err_mp_proc_init_failed; + } +#endif + + bTouchIsAwake = 1; + NVT_LOG("end\n"); + + nvt_spi_irq_enable(true); + return 0; + +#if NVT_SPI_TOUCH_MP + nvt_spi_mp_proc_deinit(); +err_mp_proc_init_failed: +#endif +#if NVT_SPI_TOUCH_EXT_PROC + nvt_spi_extra_proc_deinit(); +err_extra_proc_init_failed: +#endif +#if NVT_SPI_TOUCH_PROC + nvt_spi_flash_proc_deinit(); +err_flash_proc_init_failed: +#endif + return 0; +} + +/* + ******************************************************* + * Description: + * Novatek touchscreen driver probe function. + * + * return: + * Executive outcomes. 0---succeed. negative---failed + ****************************************************** + */ +static int32_t nvt_spi_probe(struct spi_device *client) +{ + int32_t ret = 0; +#if defined(CONFIG_DRM) + struct device_node *dp = NULL; +#endif +#if ((NVT_SPI_TOUCH_KEY_NUM > 0) || NVT_SPI_WAKEUP_GESTURE) + int32_t retry = 0; +#endif + struct nvt_spi_data_t *ts; + +#if defined(CONFIG_DRM) + dp = client->dev.of_node; + + ret = nvt_spi_check_dt(dp); + if (ret == -EPROBE_DEFER) + return ret; + + if (ret) { + ret = -ENODEV; + return ret; + } +#endif + + NVT_LOG("start\n"); + + ts = kzalloc(sizeof(struct nvt_spi_data_t), GFP_KERNEL); + if (ts == NULL) { + NVT_ERR("failed to allocated memory for nvt ts data\n"); + return -ENOMEM; + } + nvt_spi_data = ts; + + ts->xbuf = kzalloc(NVT_SPI_XBUF_LEN, GFP_KERNEL); + if (ts->xbuf == NULL) { + NVT_ERR("kzalloc for xbuf failed!\n"); + ret = -ENOMEM; + goto err_malloc_xbuf; + } + + ts->rbuf = kzalloc(NVT_SPI_READ_LEN, GFP_KERNEL); + if (ts->rbuf == NULL) { + NVT_ERR("kzalloc for rbuf failed!\n"); + ret = -ENOMEM; + goto err_malloc_rbuf; + } + + ts->client = client; + spi_set_drvdata(client, ts); + + //---prepare for spi parameter--- + if (ts->client->master->flags & SPI_MASTER_HALF_DUPLEX) { + NVT_ERR("Full duplex not supported by master\n"); + ret = -EIO; + goto err_ckeck_full_duplex; + } + ts->client->bits_per_word = 8; + ts->client->mode = SPI_MODE_0; + + ret = spi_setup(ts->client); + if (ret < 0) { + NVT_ERR("Failed to perform SPI setup\n"); + goto err_spi_setup; + } + + NVT_LOG("mode=%d, max_speed_hz=%d\n", ts->client->mode, ts->client->max_speed_hz); + + //---parse dts--- + ret = nvt_spi_parse_dt(&client->dev); + if (ret) { + NVT_ERR("parse dt error\n"); + goto err_spi_setup; + } + + //---request and config GPIOs--- + ret = nvt_spi_gpio_config(ts); + if (ret) { + NVT_ERR("gpio config error!\n"); + goto err_gpio_config_failed; + } + + mutex_init(&ts->lock); + mutex_init(&ts->xbuf_lock); + + //---eng reset before TP_RESX high + nvt_spi_eng_reset(); + +#if NVT_SPI_TOUCH_SUPPORT_HW_RST + gpio_set_value(ts->reset_gpio, 1); +#endif + + // need 10ms delay after POR(power on reset) + msleep(20); + + ts->abs_x_max = NVT_SPI_TOUCH_DEFAULT_MAX_WIDTH; + ts->abs_y_max = NVT_SPI_TOUCH_DEFAULT_MAX_HEIGHT; + + //---allocate input device--- + ts->input_dev = input_allocate_device(); + if (ts->input_dev == NULL) { + NVT_ERR("allocate input device failed\n"); + ret = -ENOMEM; + goto err_input_dev_alloc_failed; + } + + ts->max_touch_num = NVT_SPI_TOUCH_MAX_FINGER_NUM; + +#if NVT_SPI_TOUCH_KEY_NUM > 0 + ts->max_button_num = NVT_SPI_TOUCH_KEY_NUM; +#endif + + ts->int_trigger_type = NVT_SPI_INT_TRIGGER_TYPE; + + + //---set input device info.--- + ts->input_dev->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + ts->input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + ts->input_dev->propbit[0] = BIT(INPUT_PROP_DIRECT); + +#if NVT_SPI_MT_PROTOCOL_B + input_mt_init_slots(ts->input_dev, ts->max_touch_num, 0); +#endif + + // pressure = NVT_SPI_TOUCH_FORCE_NUM + input_set_abs_params(ts->input_dev, ABS_MT_PRESSURE, 0, NVT_SPI_TOUCH_FORCE_NUM, 0, 0); + +#if NVT_SPI_TOUCH_MAX_FINGER_NUM > 1 + // area = 255 + input_set_abs_params(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0); + + input_set_abs_params(ts->input_dev, ABS_MT_POSITION_X, 0, ts->abs_x_max, 0, 0); + input_set_abs_params(ts->input_dev, ABS_MT_POSITION_Y, 0, ts->abs_y_max, 0, 0); +#if NVT_SPI_MT_PROTOCOL_B + // no need to set ABS_MT_TRACKING_ID, input_mt_init_slots() already set it +#else + input_set_abs_params(ts->input_dev, ABS_MT_TRACKING_ID, 0, ts->max_touch_num, 0, 0); +#endif //NVT_SPI_MT_PROTOCOL_B +#endif //NVT_SPI_TOUCH_MAX_FINGER_NUM > 1 + +#if NVT_SPI_TOUCH_KEY_NUM > 0 + for (retry = 0; retry < ts->max_button_num; retry++) + input_set_capability(ts->input_dev, EV_KEY, nvt_spi_touch_key_array[retry]); +#endif + +#if NVT_SPI_WAKEUP_GESTURE + for (retry = 0; retry < ARRAY_SIZE(nvt_spi_gesture_key_array); retry++) + input_set_capability(ts->input_dev, EV_KEY, nvt_spi_gesture_key_array[retry]); +#endif + + snprintf(ts->phys, sizeof(ts->phys), "input/ts"); + ts->input_dev->name = NVT_SPI_TS_NAME; + ts->input_dev->phys = ts->phys; + ts->input_dev->id.bustype = BUS_SPI; + + //---register input device--- + ret = input_register_device(ts->input_dev); + if (ret) { + NVT_ERR("register input device (%s) failed. ret=%d\n", ts->input_dev->name, ret); + goto err_input_register_device_failed; + } + + if (ts->pen_support) { + //---allocate pen input device--- + ts->pen_input_dev = input_allocate_device(); + if (ts->pen_input_dev == NULL) { + NVT_ERR("allocate pen input device failed\n"); + ret = -ENOMEM; + goto err_pen_input_dev_alloc_failed; + } + + //---set pen input device info.--- + ts->pen_input_dev->evbit[0] = + BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + ts->pen_input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + ts->pen_input_dev->keybit[BIT_WORD(BTN_TOOL_PEN)] |= BIT_MASK(BTN_TOOL_PEN); + //ts->pen_input_dev->keybit[BIT_WORD(BTN_TOOL_RUBBER)] + // |= BIT_MASK(BTN_TOOL_RUBBER); + ts->pen_input_dev->keybit[BIT_WORD(BTN_STYLUS)] |= BIT_MASK(BTN_STYLUS); + ts->pen_input_dev->keybit[BIT_WORD(BTN_STYLUS2)] |= BIT_MASK(BTN_STYLUS2); + ts->pen_input_dev->propbit[0] = BIT(INPUT_PROP_DIRECT); + + if (ts->wgp_stylus) { + input_set_abs_params(ts->pen_input_dev, ABS_X, 0, + ts->abs_x_max * 2, 0, 0); + input_set_abs_params(ts->pen_input_dev, ABS_Y, 0, + ts->abs_y_max * 2, 0, 0); + } else { + input_set_abs_params(ts->pen_input_dev, ABS_X, 0, + ts->abs_x_max, 0, 0); + + input_set_abs_params(ts->pen_input_dev, ABS_Y, 0, + ts->abs_y_max, 0, 0); + } + input_set_abs_params(ts->pen_input_dev, ABS_PRESSURE, 0, + NVT_SPI_PEN_PRESSURE_MAX, 0, 0); + input_set_abs_params(ts->pen_input_dev, ABS_DISTANCE, 0, + NVT_SPI_PEN_DISTANCE_MAX, 0, 0); + input_set_abs_params(ts->pen_input_dev, ABS_TILT_X, + NVT_SPI_PEN_TILT_MIN, NVT_SPI_PEN_TILT_MAX, 0, 0); + input_set_abs_params(ts->pen_input_dev, ABS_TILT_Y, + NVT_SPI_PEN_TILT_MIN, NVT_SPI_PEN_TILT_MAX, 0, 0); + + snprintf(ts->pen_phys, sizeof(ts->pen_phys), "input/pen"); + ts->pen_input_dev->name = NVT_SPI_PEN_NAME; + ts->pen_input_dev->phys = ts->pen_phys; + ts->pen_input_dev->id.bustype = BUS_SPI; + + //---register pen input device--- + ret = input_register_device(ts->pen_input_dev); + if (ret) { + NVT_ERR("register pen input device (%s) failed. ret=%d\n", + ts->pen_input_dev->name, ret); + goto err_pen_input_register_device_failed; + } + } /* if (ts->pen_support) */ + + //---set int-pin & request irq--- + client->irq = gpio_to_irq(ts->irq_gpio); + if (client->irq) { + NVT_LOG("int_trigger_type=%d\n", ts->int_trigger_type); + ts->irq_enabled = true; + ret = request_threaded_irq(client->irq, NULL, nvt_spi_work_func, + ts->int_trigger_type | IRQF_ONESHOT, NVT_SPI_NAME, ts); + if (ret != 0) { + NVT_ERR("request irq failed. ret=%d\n", ret); + goto err_int_request_failed; + } else { + nvt_spi_irq_enable(false); + NVT_LOG("request irq %d succeed\n", client->irq); + } + } + +#if NVT_SPI_WAKEUP_GESTURE + device_init_wakeup(&ts->input_dev->dev, 1); +#endif + +#if NVT_SPI_BOOT_UPDATE_FIRMWARE + nvt_spi_fwu_wq = alloc_workqueue("nvt_spi_fwu_wq", WQ_UNBOUND | WQ_MEM_RECLAIM, 1); + if (!nvt_spi_fwu_wq) { + NVT_ERR("nvt_spi_fwu_wq create workqueue failed\n"); + ret = -ENOMEM; + goto err_create_nvt_spi_fwu_wq_failed; + } + INIT_DELAYED_WORK(&ts->nvt_fwu_work, nvt_spi_update_firmware_work); + + // please make sure boot update start after display reset(RESX) sequence + //queue_delayed_work(nvt_spi_fwu_wq, &ts->nvt_fwu_work, msecs_to_jiffies(14000)); +#endif + + NVT_LOG("NVT_SPI_TOUCH_ESD_PROTECT is %d\n", NVT_SPI_TOUCH_ESD_PROTECT); +#if NVT_SPI_TOUCH_ESD_PROTECT + INIT_DELAYED_WORK(&nvt_spi_esd_check_work, nvt_spi_esd_check_func); + nvt_spi_esd_check_wq = alloc_workqueue("nvt_spi_esd_check_wq", WQ_MEM_RECLAIM, 1); + if (!nvt_spi_esd_check_wq) { + NVT_ERR("nvt_spi_esd_check_wq create workqueue failed\n"); + ret = -ENOMEM; + goto err_create_nvt_spi_esd_check_wq_failed; + } + queue_delayed_work(nvt_spi_esd_check_wq, &nvt_spi_esd_check_work, + msecs_to_jiffies(NVT_SPI_TOUCH_ESD_CHECK_PERIOD)); +#endif /* #if NVT_SPI_TOUCH_ESD_PROTECT */ + + +#if defined(CONFIG_DRM) + //if (!strcmp(ts->touch_environment, "pvm")) + nvt_spi_register_for_panel_events(client->dev.of_node, ts); +#elif defined(_MSM_DRM_NOTIFY_H_) + ts->drm_notif.notifier_call = nvt_drm_notifier_callback; + ret = msm_drm_register_client(&ts->drm_notif); + if (ret) { + NVT_ERR("register drm_notifier failed. ret=%d\n", ret); + goto err_register_drm_notif_failed; + } +#else + ts->fb_notif.notifier_call = nvt_fb_notifier_callback; + ret = fb_register_client(&ts->fb_notif); + if (ret) { + NVT_ERR("register fb_notifier failed. ret=%d\n", ret); + goto err_register_fb_notif_failed; + } +#endif + + NVT_LOG("end\n"); + return 0; + +#if defined(CONFIG_DRM) + +#elif defined(_MSM_DRM_NOTIFY_H_) +err_register_drm_notif_failed: +#else +err_register_fb_notif_failed: +#endif + +#if NVT_SPI_TOUCH_MP + nvt_spi_mp_proc_deinit(); +#endif +#if NVT_SPI_TOUCH_EXT_PROC + nvt_spi_extra_proc_deinit(); + +#endif +#if NVT_SPI_TOUCH_PROC + nvt_spi_flash_proc_deinit(); + +#endif +#if NVT_SPI_TOUCH_ESD_PROTECT + if (nvt_spi_esd_check_wq) { + cancel_delayed_work_sync(&nvt_spi_esd_check_work); + destroy_workqueue(nvt_spi_esd_check_wq); + nvt_spi_esd_check_wq = NULL; + } +err_create_nvt_spi_esd_check_wq_failed: +#endif +#if NVT_SPI_BOOT_UPDATE_FIRMWARE + if (nvt_spi_fwu_wq) { + cancel_delayed_work_sync(&ts->nvt_fwu_work); + destroy_workqueue(nvt_spi_fwu_wq); + nvt_spi_fwu_wq = NULL; + } +err_create_nvt_spi_fwu_wq_failed: +#endif +#if NVT_SPI_WAKEUP_GESTURE + device_init_wakeup(&ts->input_dev->dev, 0); +#endif + free_irq(client->irq, ts); +err_int_request_failed: + if (ts->pen_support) { + input_unregister_device(ts->pen_input_dev); + ts->pen_input_dev = NULL; + } +err_pen_input_register_device_failed: + if (ts->pen_support) { + if (ts->pen_input_dev) { + input_free_device(ts->pen_input_dev); + ts->pen_input_dev = NULL; + } + } +err_pen_input_dev_alloc_failed: + input_unregister_device(ts->input_dev); + ts->input_dev = NULL; +err_input_register_device_failed: + if (ts->input_dev) { + input_free_device(ts->input_dev); + ts->input_dev = NULL; + } +err_input_dev_alloc_failed: + mutex_destroy(&ts->xbuf_lock); + mutex_destroy(&ts->lock); + nvt_spi_gpio_deconfig(ts); +err_gpio_config_failed: +err_spi_setup: +err_ckeck_full_duplex: + spi_set_drvdata(client, NULL); + kfree(ts->rbuf); + ts->rbuf = NULL; +err_malloc_rbuf: + kfree(ts->xbuf); + ts->xbuf = NULL; +err_malloc_xbuf: + kfree(ts); + ts = NULL; + + return ret; +} + +/* + ******************************************************* + * Description: + * Novatek touchscreen driver release function. + * + * return: + * Executive outcomes. 0---succeed. + ****************************************************** + */ +static int32_t nvt_spi_remove(struct spi_device *client) +{ + struct nvt_spi_data_t *ts = nvt_spi_data; + + NVT_LOG("Removing driver...\n"); + +#if defined(CONFIG_DRM) + if (active_spi_panel && ts->notifier_cookie) + panel_event_notifier_unregister(ts->notifier_cookie); +#elif defined(_MSM_DRM_NOTIFY_H_) + if (msm_drm_unregister_client(&ts->drm_notif)) + NVT_ERR("Error occurred while unregistering drm_notifier.\n"); +#else + if (fb_unregister_client(&ts->fb_notif)) + NVT_ERR("Error occurred while unregistering fb_notifier.\n"); +#endif + +#if NVT_SPI_TOUCH_MP + nvt_spi_mp_proc_deinit(); +#endif +#if NVT_SPI_TOUCH_EXT_PROC + nvt_spi_extra_proc_deinit(); +#endif +#if NVT_SPI_TOUCH_PROC + nvt_spi_flash_proc_deinit(); +#endif + +#if NVT_SPI_TOUCH_ESD_PROTECT + if (nvt_spi_esd_check_wq) { + cancel_delayed_work_sync(&nvt_spi_esd_check_work); + nvt_spi_esd_check_enable(false); + destroy_workqueue(nvt_spi_esd_check_wq); + nvt_spi_esd_check_wq = NULL; + } +#endif + +#if NVT_SPI_BOOT_UPDATE_FIRMWARE + if (nvt_spi_fwu_wq) { + cancel_delayed_work_sync(&ts->nvt_fwu_work); + destroy_workqueue(nvt_spi_fwu_wq); + nvt_spi_fwu_wq = NULL; + } +#endif + +#if NVT_SPI_WAKEUP_GESTURE + device_init_wakeup(&ts->input_dev->dev, 0); +#endif + + nvt_spi_irq_enable(false); + free_irq(client->irq, ts); + + mutex_destroy(&ts->xbuf_lock); + mutex_destroy(&ts->lock); + + nvt_spi_gpio_deconfig(ts); + + if (ts->pen_support) { + if (ts->pen_input_dev) { + input_unregister_device(ts->pen_input_dev); + ts->pen_input_dev = NULL; + } + } + + if (ts->input_dev) { + input_unregister_device(ts->input_dev); + ts->input_dev = NULL; + } + + spi_set_drvdata(client, NULL); + + kfree(ts->xbuf); + ts->xbuf = NULL; + + kfree(ts); + ts = NULL; + + return 0; +} + +static void nvt_spi_shutdown(struct spi_device *client) +{ + struct nvt_spi_data_t *ts = nvt_spi_data; + + NVT_LOG("Shutdown driver...\n"); + + nvt_spi_irq_enable(false); + +#if defined(CONFIG_DRM) + if (active_spi_panel && ts->notifier_cookie) + panel_event_notifier_unregister(ts->notifier_cookie); +#elif defined(_MSM_DRM_NOTIFY_H_) + if (msm_drm_unregister_client(&ts->drm_notif)) + NVT_ERR("Error occurred while unregistering drm_notifier.\n"); +#else + if (fb_unregister_client(&ts->fb_notif)) + NVT_ERR("Error occurred while unregistering fb_notifier.\n"); +#endif + +#if NVT_SPI_TOUCH_MP + nvt_spi_mp_proc_deinit(); +#endif +#if NVT_SPI_TOUCH_EXT_PROC + nvt_spi_extra_proc_deinit(); +#endif +#if NVT_SPI_TOUCH_PROC + nvt_spi_flash_proc_deinit(); +#endif + +#if NVT_SPI_TOUCH_ESD_PROTECT + if (nvt_spi_esd_check_wq) { + cancel_delayed_work_sync(&nvt_spi_esd_check_work); + nvt_spi_esd_check_enable(false); + destroy_workqueue(nvt_spi_esd_check_wq); + nvt_spi_esd_check_wq = NULL; + } +#endif /* #if NVT_SPI_TOUCH_ESD_PROTECT */ + +#if NVT_SPI_BOOT_UPDATE_FIRMWARE + if (nvt_spi_fwu_wq) { + cancel_delayed_work_sync(&ts->nvt_fwu_work); + destroy_workqueue(nvt_spi_fwu_wq); + nvt_spi_fwu_wq = NULL; + } +#endif + +#if NVT_SPI_WAKEUP_GESTURE + device_init_wakeup(&ts->input_dev->dev, 0); +#endif +} + +/* + ****************************************************** + * Description: + * Novatek touchscreen driver suspend function. + * + * return: + * Executive outcomes. 0---succeed. + ****************************************************** + */ +static int32_t nvt_spi_suspend(struct device *dev) +{ + uint8_t buf[4] = {0}; +#if NVT_SPI_MT_PROTOCOL_B + uint32_t i = 0; +#endif + struct nvt_spi_data_t *ts = nvt_spi_data; + + if (!bTouchIsAwake) { + NVT_LOG("Touch is already suspend\n"); + return 0; + } + +#if !NVT_SPI_WAKEUP_GESTURE + nvt_spi_irq_enable(false); +#endif + +#if NVT_SPI_TOUCH_ESD_PROTECT + NVT_LOG("cancel delayed work sync\n"); + cancel_delayed_work_sync(&nvt_spi_esd_check_work); + nvt_spi_esd_check_enable(false); +#endif /* #if NVT_SPI_TOUCH_ESD_PROTECT */ + + mutex_lock(&ts->lock); + + NVT_LOG("start\n"); + + bTouchIsAwake = 0; + +#if NVT_SPI_WAKEUP_GESTURE + //---write command to enter "wakeup gesture mode"--- + buf[0] = NVT_SPI_EVENT_MAP_HOST_CMD; + buf[1] = 0x13; + nvt_spi_write(buf, 2); + + enable_irq_wake(ts->client->irq); + + NVT_LOG("Enabled touch wakeup gesture\n"); + +#else // NVT_SPI_WAKEUP_GESTURE + //---write command to enter "deep sleep mode"--- + buf[0] = NVT_SPI_EVENT_MAP_HOST_CMD; + buf[1] = 0x11; + nvt_spi_write(buf, 2); +#endif // NVT_SPI_WAKEUP_GESTURE + + mutex_unlock(&ts->lock); + + /* release all touches */ +#if NVT_SPI_MT_PROTOCOL_B + for (i = 0; i < ts->max_touch_num; i++) { + input_mt_slot(ts->input_dev, i); + input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0); + input_report_abs(ts->input_dev, ABS_MT_PRESSURE, 0); + input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, 0); + } +#endif + input_report_key(ts->input_dev, BTN_TOUCH, 0); +#if !NVT_SPI_MT_PROTOCOL_B + input_mt_sync(ts->input_dev); +#endif + input_sync(ts->input_dev); + + /* release pen event */ + if (ts->pen_support) { + input_report_abs(ts->pen_input_dev, ABS_X, 0); + input_report_abs(ts->pen_input_dev, ABS_Y, 0); + input_report_abs(ts->pen_input_dev, ABS_PRESSURE, 0); + input_report_abs(ts->pen_input_dev, ABS_TILT_X, 0); + input_report_abs(ts->pen_input_dev, ABS_TILT_Y, 0); + input_report_abs(ts->pen_input_dev, ABS_DISTANCE, 0); + input_report_key(ts->pen_input_dev, BTN_TOUCH, 0); + input_report_key(ts->pen_input_dev, BTN_TOOL_PEN, 0); + input_report_key(ts->pen_input_dev, BTN_STYLUS, 0); + input_report_key(ts->pen_input_dev, BTN_STYLUS2, 0); + input_sync(ts->pen_input_dev); + } + + msleep(50); + + NVT_LOG("end\n"); + + return 0; +} + +/* + ****************************************************** + * Description: + * Novatek touchscreen driver resume function. + * + * return: + * Executive outcomes. 0---succeed. + ****************************************************** + */ +static int32_t nvt_spi_resume(struct device *dev) +{ + struct nvt_spi_data_t *ts = nvt_spi_data; + + if (bTouchIsAwake || ts->fw_ver == 0) { + nvt_spi_late_probe(ts->client); + NVT_LOG("nvt_spi_late_probe\n"); + return 0; + } + + mutex_lock(&ts->lock); + + NVT_LOG("start\n"); + + // please make sure display reset(RESX) sequence and mipi dsi cmds sent before this +#if NVT_SPI_TOUCH_SUPPORT_HW_RST + gpio_set_value(ts->reset_gpio, 1); +#endif + + if (nvt_spi_update_firmware(NVT_SPI_BOOT_UPDATE_FIRMWARE_NAME)) + NVT_ERR("download firmware failed, ignore check fw state\n"); + else + nvt_spi_check_fw_reset_state(NVT_SPI_RESET_STATE_REK); + +#if !NVT_SPI_WAKEUP_GESTURE + nvt_spi_irq_enable(true); +#endif + +#if NVT_SPI_TOUCH_ESD_PROTECT + nvt_spi_esd_check_enable(false); + queue_delayed_work(nvt_spi_esd_check_wq, &nvt_spi_esd_check_work, + msecs_to_jiffies(NVT_SPI_TOUCH_ESD_CHECK_PERIOD)); +#endif /* #if NVT_SPI_TOUCH_ESD_PROTECT */ + + bTouchIsAwake = 1; + + mutex_unlock(&ts->lock); + + NVT_LOG("end\n"); + + return 0; +} + +#if defined(CONFIG_DRM) + +static void nvt_spi_panel_notifier_callback(enum panel_event_notifier_tag tag, + struct panel_event_notification *notification, void *client_data) +{ + struct nvt_spi_data_t *ts = client_data; + + if (!notification) { + pr_err("Invalid notification\n"); + return; + } + + NVT_LOG("Notification type:%d, early_trigger:%d", + notification->notif_type, + notification->notif_data.early_trigger); + + switch (notification->notif_type) { + case DRM_PANEL_EVENT_UNBLANK: + if (notification->notif_data.early_trigger) + NVT_LOG("resume notification pre commit\n"); + else + nvt_spi_resume(&ts->client->dev); + break; + + case DRM_PANEL_EVENT_BLANK: + if (notification->notif_data.early_trigger) + nvt_spi_suspend(&ts->client->dev); + else + NVT_LOG("suspend notification post commit\n"); + break; + + case DRM_PANEL_EVENT_BLANK_LP: + NVT_LOG("received lp event\n"); + break; + + case DRM_PANEL_EVENT_FPS_CHANGE: + NVT_LOG("shashank:Received fps change old fps:%d new fps:%d\n", + notification->notif_data.old_fps, + notification->notif_data.new_fps); + break; + default: + NVT_LOG("notification serviced :%d\n", + notification->notif_type); + break; + } +} + +#elif defined(_MSM_DRM_NOTIFY_H_) +static int nvt_drm_notifier_callback(struct notifier_block *self, unsigned long event, void *data) +{ + struct msm_drm_notifier *evdata = data; + int *blank; + struct nvt_spi_data_t *ts = + container_of(self, struct nvt_spi_data_t, drm_notif); + + if (!evdata || (evdata->id != 0)) + return 0; + + if (evdata->data && ts) { + blank = evdata->data; + if (event == MSM_DRM_EARLY_EVENT_BLANK) { + if (*blank == MSM_DRM_BLANK_POWERDOWN) { + NVT_LOG("event=%lu, *blank=%d\n", event, *blank); + nvt_spi_suspend(&ts->client->dev); + } + } else if (event == MSM_DRM_EVENT_BLANK) { + if (*blank == MSM_DRM_BLANK_UNBLANK) { + NVT_LOG("event=%lu, *blank=%d\n", event, *blank); + nvt_ts_resume(&ts->client->dev); + } + } + } + + return 0; +} +#else +static int nvt_fb_notifier_callback(struct notifier_block *self, unsigned long event, void *data) +{ + struct fb_event *evdata = data; + int *blank; + struct nvt_spi_data_t *ts = + container_of(self, struct nvt_spi_data_t, fb_notif); + + if (evdata && evdata->data && event == FB_EARLY_EVENT_BLANK) { + blank = evdata->data; + if (*blank == FB_BLANK_POWERDOWN) { + NVT_LOG("event=%lu, *blank=%d\n", event, *blank); + nvt_spi_suspend(&ts->client->dev); + } + } else if (evdata && evdata->data && event == FB_EVENT_BLANK) { + blank = evdata->data; + if (*blank == FB_BLANK_UNBLANK) { + NVT_LOG("event=%lu, *blank=%d\n", event, *blank); + nvt_ts_resume(&ts->client->dev); + } + } + + return 0; +} +#endif + +static const struct spi_device_id nvt_spi_id[] = { + { NVT_SPI_NAME, 0 }, + { } +}; + +#ifdef CONFIG_OF +static const struct of_device_id nvt_spi_match_table[] = { + { .compatible = "novatek,NVT-ts",}, + { }, +}; +#endif + +static struct spi_driver nvt_spi_driver = { + .probe = nvt_spi_probe, + .remove = nvt_spi_remove, + .shutdown = nvt_spi_shutdown, + .id_table = nvt_spi_id, + .driver = { + .name = NVT_SPI_NAME, + .owner = THIS_MODULE, +#ifdef CONFIG_OF + .of_match_table = nvt_spi_match_table, +#endif + }, +}; + +/* + ****************************************************** + * Description: + * Driver Install function. + * + * return: + * Executive Outcomes. 0---succeed. not 0---failed. + ******************************************************* + */ +static int32_t __init nvt_spi_driver_init(void) +{ + int32_t ret = 0; + + NVT_LOG("start\n"); + + //---add spi driver--- + ret = spi_register_driver(&nvt_spi_driver); + if (ret) { + NVT_ERR("failed to add spi driver"); + goto err_driver; + } + + NVT_LOG("finished\n"); + +err_driver: + return ret; +} + +/* + ****************************************************** + * Description: + * Driver uninstall function. + * + * return: + * n.a. + ******************************************************* + */ +static void __exit nvt_spi_driver_exit(void) +{ + spi_unregister_driver(&nvt_spi_driver); +} + +module_init(nvt_spi_driver_init); +module_exit(nvt_spi_driver_exit); + +MODULE_DESCRIPTION("Novatek Touchscreen Driver"); +MODULE_LICENSE("GPL v2"); + +#endif diff --git a/qcom/opensource/touch-drivers/nt36xxx/nt36xxx.h b/qcom/opensource/touch-drivers/nt36xxx/nt36xxx.h new file mode 100644 index 0000000000..1a298efb6a --- /dev/null +++ b/qcom/opensource/touch-drivers/nt36xxx/nt36xxx.h @@ -0,0 +1,386 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2010 - 2018 Novatek, Inc. + * + * $Revision: 47247 $ + * $Date: 2019-07-10 10:41:36 +0800 (Wed, 10 Jul 2019) $ + * + * 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. + * + */ +#ifndef _LINUX_NVT_TOUCH_H +#define _LINUX_NVT_TOUCH_H + +#if !defined(NVT_NT36XXX_SPI) /* NT36XXX I2C */ + +#include +#include +#include +#include + +#ifdef CONFIG_HAS_EARLYSUSPEND +#include +#endif + +#include "nt36xxx_mem_map.h" + +#define NVT_DEBUG 1 + +//---GPIO number--- +#define NVTTOUCH_RST_PIN 980 +#define NVTTOUCH_INT_PIN 943 + + +//---INT trigger mode--- +//#define IRQ_TYPE_EDGE_RISING 1 +//#define IRQ_TYPE_EDGE_FALLING 2 +#define INT_TRIGGER_TYPE IRQ_TYPE_EDGE_RISING + + +//---I2C driver info.--- +#define NVT_I2C_NAME "NVT-ts" +#define I2C_BLDR_Address 0x01 +#define I2C_FW_Address 0x01 +#define I2C_HW_Address 0x62 + +#if NVT_DEBUG +#define NVT_LOG(fmt, args...) pr_err("[%s] %s %d: " fmt, NVT_I2C_NAME, __func__, __LINE__, ##args) +#else +#define NVT_LOG(fmt, args...) pr_info("[%s] %s %d: " fmt, NVT_I2C_NAME, __func__, __LINE__, ##args) +#endif +#define NVT_ERR(fmt, args...) pr_err("[%s] %s %d: " fmt, NVT_I2C_NAME, __func__, __LINE__, ##args) + +//---Input device info.--- +#define NVT_TS_NAME "NVTCapacitiveTouchScreen" + + +//---Touch info.--- +#define TOUCH_DEFAULT_MAX_WIDTH 1080 +#define TOUCH_DEFAULT_MAX_HEIGHT 2408 +#define TOUCH_MAX_FINGER_NUM 10 +#define TOUCH_KEY_NUM 0 +#if TOUCH_KEY_NUM > 0 +extern const uint16_t touch_key_array[TOUCH_KEY_NUM]; +#endif +#define TOUCH_FORCE_NUM 1000 + +/* Enable only when module have tp reset pin and connected to host */ +#define NVT_TOUCH_SUPPORT_HW_RST 1 + +//---Customerized func.--- +#define NVT_TOUCH_PROC 1 +#define NVT_TOUCH_EXT_PROC 1 +#define NVT_TOUCH_MP 1 +#define MT_PROTOCOL_B 1 +#define WAKEUP_GESTURE 0 +#if WAKEUP_GESTURE +extern const uint16_t gesture_key_array[]; +#endif +#define BOOT_UPDATE_FIRMWARE 1 +#define BOOT_UPDATE_FIRMWARE_NAME "novatek_ts_fw.bin" + +//---ESD Protect.--- +#define NVT_TOUCH_ESD_PROTECT 0 +#define NVT_TOUCH_ESD_CHECK_PERIOD 1500 /* ms */ + +struct nvt_ts_data { + struct i2c_client *client; + struct input_dev *input_dev; + struct delayed_work nvt_fwu_work; + uint16_t addr; + int8_t phys[32]; + const struct i2c_device_id *id; +#if defined(CONFIG_DRM) + struct notifier_block drm_panel_notif; +#elif defined(_MSM_DRM_NOTIFY_H_) + struct notifier_block drm_notif; +#else + struct notifier_block fb_notif; + +#endif + uint8_t fw_ver; + uint8_t x_num; + uint8_t y_num; + uint16_t abs_x_max; + uint16_t abs_y_max; + uint8_t max_touch_num; + uint8_t max_button_num; + uint32_t int_trigger_type; + int32_t irq_gpio; + uint32_t irq_flags; + int32_t reset_gpio; + uint32_t reset_flags; + struct mutex lock; + const struct nvt_ts_mem_map *mmap; + uint8_t carrier_system; + uint16_t nvt_pid; + uint8_t xbuf[1025]; + struct mutex xbuf_lock; + bool irq_enabled; + void *notifier_cookie; +}; + +#if NVT_TOUCH_PROC +struct nvt_flash_data{ + rwlock_t lock; + struct i2c_client *client; +}; +#endif + +typedef enum { + RESET_STATE_INIT = 0xA0,// IC reset + RESET_STATE_REK, // ReK baseline + RESET_STATE_REK_FINISH, // baseline is ready + RESET_STATE_NORMAL_RUN, // normal run + RESET_STATE_MAX = 0xAF +} RST_COMPLETE_STATE; + +typedef enum { + EVENT_MAP_HOST_CMD = 0x50, + EVENT_MAP_HANDSHAKING_or_SUB_CMD_BYTE = 0x51, + EVENT_MAP_RESET_COMPLETE = 0x60, + EVENT_MAP_FWINFO = 0x78, + EVENT_MAP_PROJECTID = 0x9A, +} I2C_EVENT_MAP; + +//---extern structures--- +extern struct nvt_ts_data *ts; + +//---extern functions--- +extern int32_t CTP_I2C_READ(struct i2c_client *client, uint16_t address, uint8_t *buf, uint16_t len); +extern int32_t CTP_I2C_WRITE(struct i2c_client *client, uint16_t address, uint8_t *buf, uint16_t len); +extern void nvt_bootloader_reset(void); +extern void nvt_sw_reset_idle(void); +extern int32_t nvt_check_fw_reset_state(RST_COMPLETE_STATE check_reset_state); +extern int32_t nvt_get_fw_info(void); +extern int32_t nvt_clear_fw_status(void); +extern int32_t nvt_check_fw_status(void); +extern int32_t nvt_set_page(uint16_t i2c_addr, uint32_t addr); +#if NVT_TOUCH_ESD_PROTECT +extern void nvt_esd_check_enable(uint8_t enable); +#endif /* #if NVT_TOUCH_ESD_PROTECT */ +extern void nvt_stop_crc_reboot(void); + +#else /* NT36XXX_SPI */ + +#include +#include +#include +#include +#include + +#include "nt36xxx_mem_map.h" + +#define NVT_SPI_DEBUG 0 + +//---GPIO number--- +#define NVTTOUCH_RST_PIN 980 +#define NVTTOUCH_INT_PIN 943 + +//---INT trigger mode--- +//#define NVT_SPI_IRQ_TYPE_EDGE_RISING 1 +//#define NVT_SPI_IRQ_TYPE_EDGE_FALLING 2 +#define NVT_SPI_INT_TRIGGER_TYPE IRQ_TYPE_EDGE_RISING + +//---SPI driver info.--- +#define NVT_SPI_NAME "NVT-SPI" + +#if NVT_SPI_DEBUG +#define NVT_LOG(fmt, args...) pr_err("[%s] %s %d: " fmt, "NVT-SPI", __func__, __LINE__, ##args) +#else +#define NVT_LOG(fmt, args...) pr_debug("[%s] %s %d: " fmt, "NVT-SPI", __func__, __LINE__, ##args) +#endif +#define NVT_ERR(fmt, args...) pr_err("[%s] %s %d: " fmt, "NVT-SPI", __func__, __LINE__, ##args) + +//---Input device info.--- +#define NVT_SPI_TS_NAME "NVTCapacitiveTouchScreen" +#define NVT_SPI_PEN_NAME "NVTCapacitivePen" + +//---Touch info.--- +#define NVT_SPI_TOUCH_DEFAULT_MAX_WIDTH 1080 +#define NVT_SPI_TOUCH_DEFAULT_MAX_HEIGHT 2400 +#define NVT_SPI_TOUCH_MAX_FINGER_NUM 10 +#define NVT_SPI_TOUCH_KEY_NUM 0 +#if NVT_SPI_TOUCH_KEY_NUM > 0 +extern const uint16_t nvt_spi_touch_key_array[NVT_SPI_TOUCH_KEY_NUM]; +#endif +#define NVT_SPI_TOUCH_FORCE_NUM 1000 +//---for Pen--- +#define NVT_SPI_PEN_PRESSURE_MAX (4095) +#define NVT_SPI_PEN_DISTANCE_MAX (1) +#define NVT_SPI_PEN_TILT_MIN (-60) +#define NVT_SPI_PEN_TILT_MAX (60) + +/* Enable only when module have tp reset pin and connected to host */ +#define NVT_SPI_TOUCH_SUPPORT_HW_RST 0 + +//---Customerized func.--- +#define NVT_SPI_TOUCH_PROC 1 +#define NVT_SPI_TOUCH_EXT_PROC 1 +#define NVT_SPI_TOUCH_MP 0 +#define NVT_SPI_MT_PROTOCOL_B 1 +#define NVT_SPI_WAKEUP_GESTURE 0 + +#if NVT_SPI_WAKEUP_GESTURE +extern const uint16_t nvt_spi_gesture_key_array[]; +#endif +#define NVT_SPI_BOOT_UPDATE_FIRMWARE 1 +#define NVT_SPI_BOOT_UPDATE_FIRMWARE_NAME "novatek_spi_fw.bin" +#define NVT_SPI_MP_UPDATE_FIRMWARE_NAME "novatek_ts_mp.bin" +#define NVT_SPI_POINT_DATA_CHECKSUM 1 +#define NVT_SPI_POINT_DATA_CHECKSUM_LEN 65 + +//---ESD Protect.--- +#define NVT_SPI_TOUCH_ESD_PROTECT 0 +#define NVT_SPI_TOUCH_ESD_CHECK_PERIOD 1500 /* ms */ +#define NVT_SPI_TOUCH_WDT_RECOVERY 1 + +#define NVT_SPI_CHECK_PEN_DATA_CHECKSUM 0 + +struct nvt_spi_data_t { + struct spi_device *client; + struct input_dev *input_dev; + struct delayed_work nvt_fwu_work; + uint16_t addr; + int8_t phys[32]; + +#if defined(CONFIG_DRM) + struct notifier_block drm_panel_notif; +#elif defined(_MSM_DRM_NOTIFY_H_) + struct notifier_block drm_notif; +#else + struct notifier_block fb_notif; +#endif + + uint8_t fw_ver; + uint8_t x_num; + uint8_t y_num; + uint16_t abs_x_max; + uint16_t abs_y_max; + uint8_t max_touch_num; + uint8_t max_button_num; + uint32_t int_trigger_type; + int32_t irq_gpio; + uint32_t irq_flags; + int32_t reset_gpio; + uint32_t reset_flags; + struct mutex lock; + const struct nvt_spi_mem_map *mmap; + uint8_t hw_crc; + uint16_t nvt_pid; + uint8_t *rbuf; + uint8_t *xbuf; + struct mutex xbuf_lock; + bool irq_enabled; + bool pen_support; + bool wgp_stylus; + uint8_t x_gang_num; + uint8_t y_gang_num; + struct input_dev *pen_input_dev; + int8_t pen_phys[32]; + + void *notifier_cookie; + const char *touch_environment; + +#ifdef CONFIG_NOVATEK_SPI_TRUSTED_TOUCH + struct trusted_touch_vm_info *vm_info; + struct mutex fts_clk_io_ctrl_mutex; + struct completion trusted_touch_powerdown; + struct clk *core_clk; + struct clk *iface_clk; + atomic_t trusted_touch_initialized; + atomic_t trusted_touch_enabled; + atomic_t trusted_touch_underway; + atomic_t trusted_touch_event; + atomic_t trusted_touch_abort_status; + atomic_t delayed_vm_probe_pending; + atomic_t trusted_touch_mode; +#endif +}; + +#if NVT_SPI_TOUCH_PROC +struct nvt_spi_flash_data { + rwlock_t lock; +}; +#endif + +enum NVT_SPI_RST_COMPLETE_STATE { + NVT_SPI_RESET_STATE_INIT = 0xA0,// IC reset + NVT_SPI_RESET_STATE_REK, // ReK baseline + NVT_SPI_RESET_STATE_REK_FINISH, // baseline is ready + NVT_SPI_RESET_STATE_NORMAL_RUN, // normal run + NVT_SPI_RESET_STATE_MAX = 0xAF +}; + +enum NVT_SPI_EVENT_MAP { + NVT_SPI_EVENT_MAP_HOST_CMD = 0x50, + NVT_SPI_EVENT_MAP_HANDSHAKING_or_SUB_CMD_BYTE = 0x51, + NVT_SPI_EVENT_MAP_RESET_COMPLETE = 0x60, + NVT_SPI_EVENT_MAP_FWINFO = 0x78, + NVT_SPI_EVENT_MAP_PROJECTID = 0x9A, +}; + +//---SPI READ/WRITE--- +#define NVT_SPI_WRITE_MASK(a) (a | 0x80) +#define NVT_SPI_READ_MASK(a) (a & 0x7F) + +#define NVT_SPI_DUMMY_BYTES (1) +#define NVT_SPI_TRANSFER_LEN (63*1024) +#define NVT_SPI_READ_LEN (2*1024) +#define NVT_SPI_XBUF_LEN (NVT_SPI_TRANSFER_LEN+1+NVT_SPI_DUMMY_BYTES) + +enum NVT_SPI_RW { + NVT_SPI_WRITE = 0, + NVT_SPI_READ = 1 +}; + +//---extern structures--- +extern struct nvt_spi_data_t *nvt_spi_data; + +//---extern functions--- +int32_t nvt_spi_read(uint8_t *buf, uint16_t len); +int32_t nvt_spi_write(uint8_t *buf, uint16_t len); +void nvt_spi_bootloader_reset(void); +void nvt_spi_eng_reset(void); +void nvt_spi_sw_reset(void); +void nvt_spi_sw_reset_idle(void); +void nvt_spi_boot_ready(void); +void nvt_spi_bld_crc_enable(void); +void nvt_spi_fw_crc_enable(void); +void nvt_spi_tx_auto_copy_mode(void); +int32_t nvt_spi_update_firmware(char *firmware_name); +void nvt_spi_update_firmware_work(struct work_struct *work); +int32_t nvt_spi_check_fw_reset_state(enum NVT_SPI_RST_COMPLETE_STATE reset_state); +int32_t nvt_spi_get_fw_info(void); +int32_t nvt_spi_clear_fw_status(void); +int32_t nvt_spi_check_fw_status(void); +int32_t nvt_spi_check_spi_dma_tx_info(void); +int32_t nvt_spi_set_page(uint32_t addr); +int32_t nvt_spi_write_addr(uint32_t addr, uint8_t data); + +#if NVT_SPI_TOUCH_EXT_PROC +int32_t nvt_spi_extra_proc_init(void); +void nvt_spi_extra_proc_deinit(void); +#endif + +#if NVT_SPI_TOUCH_MP +extern int32_t nvt_spi_mp_proc_init(void); +extern void nvt_spi_mp_proc_deinit(void); +#endif + +#if NVT_SPI_TOUCH_ESD_PROTECT +extern void nvt_spi_esd_check_enable(uint8_t enable); +#endif /* #if NVT_SPI_TOUCH_ESD_PROTECT */ + + +#endif + +#endif /* _LINUX_NVT_TOUCH_H */ diff --git a/qcom/opensource/touch-drivers/nt36xxx/nt36xxx_ext_proc.c b/qcom/opensource/touch-drivers/nt36xxx/nt36xxx_ext_proc.c new file mode 100644 index 0000000000..d08d8b3162 --- /dev/null +++ b/qcom/opensource/touch-drivers/nt36xxx/nt36xxx_ext_proc.c @@ -0,0 +1,1537 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2010 - 2018 Novatek, Inc. + * + * $Revision: 47247 $ + * $Date: 2019-07-10 10:41:36 +0800 (Wed, 10 Jul 2019) $ + * + * 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. + * + */ + +#if !defined(NVT_NT36XXX_SPI) /* TOUCHSCREEN_NT36XXX I2C */ + +#include +#include + +#include "nt36xxx.h" + +#if NVT_TOUCH_EXT_PROC +#define NVT_FW_VERSION "nvt_fw_version" +#define NVT_BASELINE "nvt_baseline" +#define NVT_RAW "nvt_raw" +#define NVT_DIFF "nvt_diff" + +#define BUS_TRANSFER_LENGTH 64 + +#define NORMAL_MODE 0x00 +#define TEST_MODE_1 0x21 +#define TEST_MODE_2 0x22 +#define HANDSHAKING_HOST_READY 0xBB + +#define XDATA_SECTOR_SIZE 256 + +static uint8_t xdata_tmp[2048] = {0}; +static int32_t xdata[2048] = {0}; + +static struct proc_dir_entry *NVT_proc_fw_version_entry; +static struct proc_dir_entry *NVT_proc_baseline_entry; +static struct proc_dir_entry *NVT_proc_raw_entry; +static struct proc_dir_entry *NVT_proc_diff_entry; + +/******************************************************* +Description: + Novatek touchscreen change mode function. + +return: + n.a. +*******************************************************/ +void nvt_change_mode(uint8_t mode) +{ + uint8_t buf[8] = {0}; + + //---set xdata index to EVENT BUF ADDR--- + nvt_set_page(I2C_FW_Address, ts->mmap->EVENT_BUF_ADDR | EVENT_MAP_HOST_CMD); + + //---set mode--- + buf[0] = EVENT_MAP_HOST_CMD; + buf[1] = mode; + CTP_I2C_WRITE(ts->client, I2C_FW_Address, buf, 2); + + if (mode == NORMAL_MODE) { + buf[0] = EVENT_MAP_HANDSHAKING_or_SUB_CMD_BYTE; + buf[1] = HANDSHAKING_HOST_READY; + CTP_I2C_WRITE(ts->client, I2C_FW_Address, buf, 2); + msleep(20); + } +} + +/******************************************************* +Description: + Novatek touchscreen get firmware pipe function. + +return: + Executive outcomes. 0---pipe 0. 1---pipe 1. +*******************************************************/ +uint8_t nvt_get_fw_pipe(void) +{ + uint8_t buf[8]= {0}; + + //---set xdata index to EVENT BUF ADDR--- + nvt_set_page(I2C_FW_Address, ts->mmap->EVENT_BUF_ADDR | EVENT_MAP_HANDSHAKING_or_SUB_CMD_BYTE); + + //---read fw status--- + buf[0] = EVENT_MAP_HANDSHAKING_or_SUB_CMD_BYTE; + buf[1] = 0x00; + CTP_I2C_READ(ts->client, I2C_FW_Address, buf, 2); + + //NVT_LOG("FW pipe=%d, buf[1]=0x%02X\n", (buf[1]&0x01), buf[1]); + + return (buf[1] & 0x01); +} + +/******************************************************* +Description: + Novatek touchscreen read meta data function. + +return: + n.a. +*******************************************************/ +void nvt_read_mdata(uint32_t xdata_addr, uint32_t xdata_btn_addr) +{ + int32_t i = 0; + int32_t j = 0; + int32_t k = 0; + uint8_t buf[BUS_TRANSFER_LENGTH + 1] = {0}; + uint32_t head_addr = 0; + int32_t dummy_len = 0; + int32_t data_len = 0; + int32_t residual_len = 0; + + //---set xdata sector address & length--- + head_addr = xdata_addr - (xdata_addr % XDATA_SECTOR_SIZE); + dummy_len = xdata_addr - head_addr; + data_len = ts->x_num * ts->y_num * 2; + residual_len = (head_addr + dummy_len + data_len) % XDATA_SECTOR_SIZE; + + //printk("head_addr=0x%05X, dummy_len=0x%05X, data_len=0x%05X, residual_len=0x%05X\n", head_addr, dummy_len, data_len, residual_len); + + //read xdata : step 1 + for (i = 0; i < ((dummy_len + data_len) / XDATA_SECTOR_SIZE); i++) { + //---change xdata index--- + nvt_set_page(I2C_FW_Address, head_addr + XDATA_SECTOR_SIZE * i); + + //---read xdata by BUS_TRANSFER_LENGTH + for (j = 0; j < (XDATA_SECTOR_SIZE / BUS_TRANSFER_LENGTH); j++) { + //---read data--- + buf[0] = BUS_TRANSFER_LENGTH * j; + CTP_I2C_READ(ts->client, I2C_FW_Address, buf, BUS_TRANSFER_LENGTH + 1); + + //---copy buf to xdata_tmp--- + for (k = 0; k < BUS_TRANSFER_LENGTH; k++) { + xdata_tmp[XDATA_SECTOR_SIZE * i + BUS_TRANSFER_LENGTH * j + k] = buf[k + 1]; + //printk("0x%02X, 0x%04X\n", buf[k+1], (XDATA_SECTOR_SIZE*i + BUS_TRANSFER_LENGTH*j + k)); + } + } + //printk("addr=0x%05X\n", (head_addr+XDATA_SECTOR_SIZE*i)); + } + + //read xdata : step2 + if (residual_len != 0) { + //---change xdata index--- + nvt_set_page(I2C_FW_Address, xdata_addr + data_len - residual_len); + + //---read xdata by BUS_TRANSFER_LENGTH + for (j = 0; j < (residual_len / BUS_TRANSFER_LENGTH + 1); j++) { + //---read data--- + buf[0] = BUS_TRANSFER_LENGTH * j; + CTP_I2C_READ(ts->client, I2C_FW_Address, buf, BUS_TRANSFER_LENGTH + 1); + + //---copy buf to xdata_tmp--- + for (k = 0; k < BUS_TRANSFER_LENGTH; k++) { + xdata_tmp[(dummy_len + data_len - residual_len) + BUS_TRANSFER_LENGTH * j + k] = buf[k + 1]; + //printk("0x%02X, 0x%04x\n", buf[k+1], ((dummy_len+data_len-residual_len) + BUS_TRANSFER_LENGTH*j + k)); + } + } + //printk("addr=0x%05X\n", (xdata_addr+data_len-residual_len)); + } + + //---remove dummy data and 2bytes-to-1data--- + for (i = 0; i < (data_len / 2); i++) { + xdata[i] = (int16_t)(xdata_tmp[dummy_len + i * 2] + 256 * xdata_tmp[dummy_len + i * 2 + 1]); + } + +#if TOUCH_KEY_NUM > 0 + //read button xdata : step3 + //---change xdata index--- + nvt_set_page(I2C_FW_Address, xdata_btn_addr); + + //---read data--- + buf[0] = (xdata_btn_addr & 0xFF); + CTP_I2C_READ(ts->client, I2C_FW_Address, buf, (TOUCH_KEY_NUM * 2 + 1)); + + //---2bytes-to-1data--- + for (i = 0; i < TOUCH_KEY_NUM; i++) { + xdata[ts->x_num * ts->y_num + i] = (int16_t)(buf[1 + i * 2] + 256 * buf[1 + i * 2 + 1]); + } +#endif + + //---set xdata index to EVENT BUF ADDR--- + nvt_set_page(I2C_FW_Address, ts->mmap->EVENT_BUF_ADDR); +} + +/******************************************************* +Description: + Novatek touchscreen get meta data function. + +return: + n.a. +*******************************************************/ +void nvt_get_mdata(int32_t *buf, uint8_t *m_x_num, uint8_t *m_y_num) +{ + *m_x_num = ts->x_num; + *m_y_num = ts->y_num; + memcpy(buf, xdata, ((ts->x_num * ts->y_num + TOUCH_KEY_NUM) * sizeof(int32_t))); +} + +/******************************************************* +Description: + Novatek touchscreen firmware version show function. + +return: + Executive outcomes. 0---succeed. +*******************************************************/ +static int32_t c_fw_version_show(struct seq_file *m, void *v) +{ + seq_printf(m, "fw_ver=%d, x_num=%d, y_num=%d, button_num=%d\n", ts->fw_ver, ts->x_num, ts->y_num, ts->max_button_num); + return 0; +} + +/******************************************************* +Description: + Novatek touchscreen xdata sequence print show + function. + +return: + Executive outcomes. 0---succeed. +*******************************************************/ +static int32_t c_show(struct seq_file *m, void *v) +{ + int32_t i = 0; + int32_t j = 0; + + for (i = 0; i < ts->y_num; i++) { + for (j = 0; j < ts->x_num; j++) { + seq_printf(m, "%5d, ", xdata[i * ts->x_num + j]); + } + seq_puts(m, "\n"); + } + +#if TOUCH_KEY_NUM > 0 + for (i = 0; i < TOUCH_KEY_NUM; i++) { + seq_printf(m, "%5d, ", xdata[ts->x_num * ts->y_num + i]); + } + seq_puts(m, "\n"); +#endif + + seq_printf(m, "\n\n"); + return 0; +} + +/******************************************************* +Description: + Novatek touchscreen xdata sequence print start + function. + +return: + Executive outcomes. 1---call next function. + NULL---not call next function and sequence loop + stop. +*******************************************************/ +static void *c_start(struct seq_file *m, loff_t *pos) +{ + return *pos < 1 ? (void *)1 : NULL; +} + +/******************************************************* +Description: + Novatek touchscreen xdata sequence print next + function. + +return: + Executive outcomes. NULL---no next and call sequence + stop function. +*******************************************************/ +static void *c_next(struct seq_file *m, void *v, loff_t *pos) +{ + ++*pos; + return NULL; +} + +/******************************************************* +Description: + Novatek touchscreen xdata sequence print stop + function. + +return: + n.a. +*******************************************************/ +static void c_stop(struct seq_file *m, void *v) +{ + return; +} + +const struct seq_operations nvt_fw_version_seq_ops = { + .start = c_start, + .next = c_next, + .stop = c_stop, + .show = c_fw_version_show +}; + +const struct seq_operations nvt_seq_ops = { + .start = c_start, + .next = c_next, + .stop = c_stop, + .show = c_show +}; + +/******************************************************* +Description: + Novatek touchscreen /proc/nvt_fw_version open + function. + +return: + n.a. +*******************************************************/ +static int32_t nvt_fw_version_open(struct inode *inode, struct file *file) +{ + if (mutex_lock_interruptible(&ts->lock)) { + return -ERESTARTSYS; + } + + NVT_LOG("++\n"); + +#if NVT_TOUCH_ESD_PROTECT + nvt_esd_check_enable(false); +#endif /* #if NVT_TOUCH_ESD_PROTECT */ + + if (nvt_get_fw_info()) { + mutex_unlock(&ts->lock); + return -EAGAIN; + } + + mutex_unlock(&ts->lock); + + NVT_LOG("--\n"); + + return seq_open(file, &nvt_fw_version_seq_ops); +} + +static const struct proc_ops nvt_fw_version_fops = { + .proc_open = nvt_fw_version_open, + .proc_read = seq_read, + .proc_lseek = seq_lseek, + .proc_release = seq_release, +}; + +/******************************************************* +Description: + Novatek touchscreen /proc/nvt_baseline open function. + +return: + Executive outcomes. 0---succeed. +*******************************************************/ +static int32_t nvt_baseline_open(struct inode *inode, struct file *file) +{ + if (mutex_lock_interruptible(&ts->lock)) { + return -ERESTARTSYS; + } + + NVT_LOG("++\n"); + +#if NVT_TOUCH_ESD_PROTECT + nvt_esd_check_enable(false); +#endif /* #if NVT_TOUCH_ESD_PROTECT */ + + if (nvt_clear_fw_status()) { + mutex_unlock(&ts->lock); + return -EAGAIN; + } + + nvt_change_mode(TEST_MODE_2); + + if (nvt_check_fw_status()) { + mutex_unlock(&ts->lock); + return -EAGAIN; + } + + if (nvt_get_fw_info()) { + mutex_unlock(&ts->lock); + return -EAGAIN; + } + + nvt_read_mdata(ts->mmap->BASELINE_ADDR, ts->mmap->BASELINE_BTN_ADDR); + + nvt_change_mode(NORMAL_MODE); + + mutex_unlock(&ts->lock); + + NVT_LOG("--\n"); + + return seq_open(file, &nvt_seq_ops); +} + +static const struct proc_ops nvt_baseline_fops = { + .proc_open = nvt_baseline_open, + .proc_read = seq_read, + .proc_lseek = seq_lseek, + .proc_release = seq_release, +}; + +/******************************************************* +Description: + Novatek touchscreen /proc/nvt_raw open function. + +return: + Executive outcomes. 0---succeed. +*******************************************************/ +static int32_t nvt_raw_open(struct inode *inode, struct file *file) +{ + if (mutex_lock_interruptible(&ts->lock)) { + return -ERESTARTSYS; + } + + NVT_LOG("++\n"); + +#if NVT_TOUCH_ESD_PROTECT + nvt_esd_check_enable(false); +#endif /* #if NVT_TOUCH_ESD_PROTECT */ + + if (nvt_clear_fw_status()) { + mutex_unlock(&ts->lock); + return -EAGAIN; + } + + nvt_change_mode(TEST_MODE_2); + + if (nvt_check_fw_status()) { + mutex_unlock(&ts->lock); + return -EAGAIN; + } + + if (nvt_get_fw_info()) { + mutex_unlock(&ts->lock); + return -EAGAIN; + } + + if (nvt_get_fw_pipe() == 0) + nvt_read_mdata(ts->mmap->RAW_PIPE0_ADDR, ts->mmap->RAW_BTN_PIPE0_ADDR); + else + nvt_read_mdata(ts->mmap->RAW_PIPE1_ADDR, ts->mmap->RAW_BTN_PIPE1_ADDR); + + nvt_change_mode(NORMAL_MODE); + + mutex_unlock(&ts->lock); + + NVT_LOG("--\n"); + + return seq_open(file, &nvt_seq_ops); +} + +static const struct proc_ops nvt_raw_fops = { + .proc_open = nvt_raw_open, + .proc_read = seq_read, + .proc_lseek = seq_lseek, + .proc_release = seq_release, +}; + +/******************************************************* +Description: + Novatek touchscreen /proc/nvt_diff open function. + +return: + Executive outcomes. 0---succeed. negative---failed. +*******************************************************/ +static int32_t nvt_diff_open(struct inode *inode, struct file *file) +{ + if (mutex_lock_interruptible(&ts->lock)) { + return -ERESTARTSYS; + } + + NVT_LOG("++\n"); + +#if NVT_TOUCH_ESD_PROTECT + nvt_esd_check_enable(false); +#endif /* #if NVT_TOUCH_ESD_PROTECT */ + + if (nvt_clear_fw_status()) { + mutex_unlock(&ts->lock); + return -EAGAIN; + } + + nvt_change_mode(TEST_MODE_2); + + if (nvt_check_fw_status()) { + mutex_unlock(&ts->lock); + return -EAGAIN; + } + + if (nvt_get_fw_info()) { + mutex_unlock(&ts->lock); + return -EAGAIN; + } + + if (nvt_get_fw_pipe() == 0) + nvt_read_mdata(ts->mmap->DIFF_PIPE0_ADDR, ts->mmap->DIFF_BTN_PIPE0_ADDR); + else + nvt_read_mdata(ts->mmap->DIFF_PIPE1_ADDR, ts->mmap->DIFF_BTN_PIPE1_ADDR); + + nvt_change_mode(NORMAL_MODE); + + mutex_unlock(&ts->lock); + + NVT_LOG("--\n"); + + return seq_open(file, &nvt_seq_ops); +} + +static const struct proc_ops nvt_diff_fops = { + .proc_open = nvt_diff_open, + .proc_read = seq_read, + .proc_lseek = seq_lseek, + .proc_release = seq_release, +}; + +/******************************************************* +Description: + Novatek touchscreen extra function proc. file node + initial function. + +return: + Executive outcomes. 0---succeed. -12---failed. +*******************************************************/ +int32_t nvt_extra_proc_init(void) +{ + NVT_proc_fw_version_entry = proc_create(NVT_FW_VERSION, 0444, NULL,&nvt_fw_version_fops); + if (NVT_proc_fw_version_entry == NULL) { + NVT_ERR("create proc/%s Failed!\n", NVT_FW_VERSION); + return -ENOMEM; + } else { + NVT_LOG("create proc/%s Succeeded!\n", NVT_FW_VERSION); + } + + NVT_proc_baseline_entry = proc_create(NVT_BASELINE, 0444, NULL,&nvt_baseline_fops); + if (NVT_proc_baseline_entry == NULL) { + NVT_ERR("create proc/%s Failed!\n", NVT_BASELINE); + return -ENOMEM; + } else { + NVT_LOG("create proc/%s Succeeded!\n", NVT_BASELINE); + } + + NVT_proc_raw_entry = proc_create(NVT_RAW, 0444, NULL,&nvt_raw_fops); + if (NVT_proc_raw_entry == NULL) { + NVT_ERR("create proc/%s Failed!\n", NVT_RAW); + return -ENOMEM; + } else { + NVT_LOG("create proc/%s Succeeded!\n", NVT_RAW); + } + + NVT_proc_diff_entry = proc_create(NVT_DIFF, 0444, NULL,&nvt_diff_fops); + if (NVT_proc_diff_entry == NULL) { + NVT_ERR("create proc/%s Failed!\n", NVT_DIFF); + return -ENOMEM; + } else { + NVT_LOG("create proc/%s Succeeded!\n", NVT_DIFF); + } + + return 0; +} + +/******************************************************* +Description: + Novatek touchscreen extra function proc. file node + deinitial function. + +return: + n.a. +*******************************************************/ +void nvt_extra_proc_deinit(void) +{ + if (NVT_proc_fw_version_entry != NULL) { + remove_proc_entry(NVT_FW_VERSION, NULL); + NVT_proc_fw_version_entry = NULL; + NVT_LOG("Removed /proc/%s\n", NVT_FW_VERSION); + } + + if (NVT_proc_baseline_entry != NULL) { + remove_proc_entry(NVT_BASELINE, NULL); + NVT_proc_baseline_entry = NULL; + NVT_LOG("Removed /proc/%s\n", NVT_BASELINE); + } + + if (NVT_proc_raw_entry != NULL) { + remove_proc_entry(NVT_RAW, NULL); + NVT_proc_raw_entry = NULL; + NVT_LOG("Removed /proc/%s\n", NVT_RAW); + } + + if (NVT_proc_diff_entry != NULL) { + remove_proc_entry(NVT_DIFF, NULL); + NVT_proc_diff_entry = NULL; + NVT_LOG("Removed /proc/%s\n", NVT_DIFF); + } +} +#endif + +#else /* NT36XXX_SPI */ + +#include +#include + +#include "nt36xxx.h" + +#if NVT_SPI_TOUCH_EXT_PROC +#define NVT_SPI_FW_VERSION "nvt_fw_version" +#define NVT_SPI_BASELINE "nvt_baseline" +#define NVT_SPI_RAW "nvt_raw" +#define NVT_SPI_DIFF "nvt_diff" +#define NVT_SPI_PEN_DIFF "nvt_pen_diff" + +#define NVT_SPI_BUS_TRANSFER_LENGTH 256 + +#define NVT_SPI_NORMAL_MODE 0x00 +#define NVT_SPI_TEST_MODE_2 0x22 +#define NVT_SPI_HANDSHAKING_HOST_READY 0xBB + +#define NVT_SPI_XDATA_SECTOR_SIZE 256 + +static uint8_t nvt_spi_xdata_tmp[5000] = {0}; +static int32_t nvt_spi_xdata[2500] = {0}; +static int32_t nvt_spi_xdata_pen_tip_x[256] = {0}; +static int32_t nvt_spi_xdata_pen_tip_y[256] = {0}; +static int32_t nvt_spi_xdata_pen_ring_x[256] = {0}; +static int32_t nvt_spi_xdata_pen_ring_y[256] = {0}; + +static struct proc_dir_entry *nvt_spi_proc_fw_version_entry; +static struct proc_dir_entry *nvt_spi_proc_baseline_entry; +static struct proc_dir_entry *nvt_spi_proc_raw_entry; +static struct proc_dir_entry *nvt_spi_proc_diff_entry; +static struct proc_dir_entry *nvt_spi_proc_pen_diff_entry; + +/* + ******************************************************* + * Description: + * Novatek touchscreen change mode function. + * + * return: + * n.a. + ****************************************************** + */ +void nvt_spi_change_mode(uint8_t mode) +{ + uint8_t buf[8] = {0}; + struct nvt_spi_data_t *ts = nvt_spi_data; + + //---set xdata index to EVENT BUF ADDR--- + nvt_spi_set_page(ts->mmap->EVENT_BUF_ADDR | NVT_SPI_EVENT_MAP_HOST_CMD); + + //---set mode--- + buf[0] = NVT_SPI_EVENT_MAP_HOST_CMD; + buf[1] = mode; + nvt_spi_write(buf, 2); + + if (mode == NVT_SPI_NORMAL_MODE) { + usleep_range(20000, 21000); + buf[0] = NVT_SPI_EVENT_MAP_HANDSHAKING_or_SUB_CMD_BYTE; + buf[1] = NVT_SPI_HANDSHAKING_HOST_READY; + nvt_spi_write(buf, 2); + usleep_range(20000, 21000); + } +} + +static int32_t nvt_set_pen_inband_mode_1_spi(uint8_t freq_idx, uint8_t x_term) +{ + uint8_t buf[8] = {0}; + int32_t i = 0; + const int32_t retry = 5; + struct nvt_spi_data_t *ts = nvt_spi_data; + + //---set xdata index to EVENT BUF ADDR--- + nvt_spi_set_page(ts->mmap->EVENT_BUF_ADDR | NVT_SPI_EVENT_MAP_HOST_CMD); + + //---set mode--- + buf[0] = NVT_SPI_EVENT_MAP_HOST_CMD; + buf[1] = 0xC1; + buf[2] = 0x02; + buf[3] = freq_idx; + buf[4] = x_term; + nvt_spi_write(buf, 5); + + for (i = 0; i < retry; i++) { + buf[0] = NVT_SPI_EVENT_MAP_HOST_CMD; + buf[1] = 0xFF; + nvt_spi_read(buf, 2); + + if (buf[1] == 0x00) + break; + + usleep_range(10000, 11000); + } + + if (i >= retry) { + NVT_ERR("failed, i=%d, buf[1]=0x%02X\n", i, buf[1]); + return -EIO; + } + + return 0; +} + +static int32_t nvt_spi_set_pen_normal_mode(void) +{ + uint8_t buf[8] = {0}; + int32_t i = 0; + const int32_t retry = 5; + struct nvt_spi_data_t *ts = nvt_spi_data; + + //---set xdata index to EVENT BUF ADDR--- + nvt_spi_set_page(ts->mmap->EVENT_BUF_ADDR | NVT_SPI_EVENT_MAP_HOST_CMD); + + //---set mode--- + buf[0] = NVT_SPI_EVENT_MAP_HOST_CMD; + buf[1] = 0xC1; + buf[2] = 0x04; + nvt_spi_write(buf, 3); + + for (i = 0; i < retry; i++) { + buf[0] = NVT_SPI_EVENT_MAP_HOST_CMD; + buf[1] = 0xFF; + nvt_spi_read(buf, 2); + + if (buf[1] == 0x00) + break; + + usleep_range(10000, 11000); + } + + if (i >= retry) { + NVT_ERR("failed, i=%d, buf[1]=0x%02X\n", i, buf[1]); + return -EIO; + } + + return 0; +} + +/* + ******************************************************* + * Description: + * Novatek touchscreen get firmware pipe function. + * + * return: + * Executive outcomes. 0---pipe 0. 1---pipe 1. + ****************************************************** + */ +uint8_t nvt_spi_get_fw_pipe(void) +{ + uint32_t addr; + uint8_t buf[8] = {0}; + struct nvt_spi_data_t *ts = nvt_spi_data; + + //---set xdata index to EVENT BUF ADDR--- + addr = ts->mmap->EVENT_BUF_ADDR; + nvt_spi_set_page(addr | NVT_SPI_EVENT_MAP_HANDSHAKING_or_SUB_CMD_BYTE); + + //---read fw status--- + buf[0] = NVT_SPI_EVENT_MAP_HANDSHAKING_or_SUB_CMD_BYTE; + buf[1] = 0x00; + nvt_spi_read(buf, 2); + + //NVT_LOG("FW pipe=%d, buf[1]=0x%02X\n", (buf[1]&0x01), buf[1]); + + return (buf[1] & 0x01); +} + +/* + ******************************************************* + * Description: + * Novatek touchscreen read meta data function. + * + * return: + * n.a. + ****************************************************** + */ +static void nvt_spi_read_mdata(uint32_t xdata_addr, uint32_t xdata_btn_addr) +{ + int32_t i = 0; + int32_t j = 0; + int32_t k = 0; + uint8_t buf[NVT_SPI_BUS_TRANSFER_LENGTH + 2] = {0}; + uint32_t head_addr = 0; + int32_t dummy_len = 0; + int32_t data_len = 0; + int32_t residual_len = 0; + int32_t index, data; + struct nvt_spi_data_t *ts = nvt_spi_data; + + //---set xdata sector address & length--- + head_addr = xdata_addr - (xdata_addr % NVT_SPI_XDATA_SECTOR_SIZE); + dummy_len = xdata_addr - head_addr; + data_len = ts->x_num * ts->y_num * 2; + residual_len = (head_addr + dummy_len + data_len) % NVT_SPI_XDATA_SECTOR_SIZE; + + //printk("head_addr=0x%05X, dummy_len=0x%05X, data_len=0x%05X, residual_len=0x%05X\n", + // head_addr, dummy_len, data_len, residual_len); + + //read xdata : step 1 + for (i = 0; i < ((dummy_len + data_len) / NVT_SPI_XDATA_SECTOR_SIZE); i++) { + //---change xdata index--- + nvt_spi_set_page(head_addr + NVT_SPI_XDATA_SECTOR_SIZE * i); + + //---read xdata by NVT_SPI_BUS_TRANSFER_LENGTH + for (j = 0; j < (NVT_SPI_XDATA_SECTOR_SIZE / NVT_SPI_BUS_TRANSFER_LENGTH); j++) { + //---read data--- + buf[0] = NVT_SPI_BUS_TRANSFER_LENGTH * j; + nvt_spi_read(buf, NVT_SPI_BUS_TRANSFER_LENGTH + 1); + + //---copy buf to nvt_spi_xdata_tmp--- + for (k = 0; k < NVT_SPI_BUS_TRANSFER_LENGTH; k++) { + index = NVT_SPI_XDATA_SECTOR_SIZE * i; + index += NVT_SPI_BUS_TRANSFER_LENGTH * j + k; + nvt_spi_xdata_tmp[index] = buf[k + 1]; + //printk("0x%02X, 0x%04X\n", buf[k+1], index); + } + } + //printk("addr=0x%05X\n", (head_addr+NVT_SPI_XDATA_SECTOR_SIZE*i)); + } + + //read xdata : step2 + if (residual_len != 0) { + //---change xdata index--- + nvt_spi_set_page(xdata_addr + data_len - residual_len); + + //---read xdata by NVT_SPI_BUS_TRANSFER_LENGTH + for (j = 0; j < (residual_len / NVT_SPI_BUS_TRANSFER_LENGTH + 1); j++) { + //---read data--- + buf[0] = NVT_SPI_BUS_TRANSFER_LENGTH * j; + nvt_spi_read(buf, NVT_SPI_BUS_TRANSFER_LENGTH + 1); + + //---copy buf to nvt_spi_xdata_tmp--- + for (k = 0; k < NVT_SPI_BUS_TRANSFER_LENGTH; k++) { + index = (dummy_len + data_len - residual_len); + index += NVT_SPI_BUS_TRANSFER_LENGTH * j + k; + nvt_spi_xdata_tmp[index] = buf[k + 1]; + //printk("0x%02X, 0x%04x\n", buf[k+1], index); + + } + } + //printk("addr=0x%05X\n", (xdata_addr+data_len-residual_len)); + } + + //---remove dummy data and 2bytes-to-1data--- + for (i = 0; i < (data_len / 2); i++) { + data = nvt_spi_xdata_tmp[dummy_len + i * 2]; + data += 256 * nvt_spi_xdata_tmp[dummy_len + i * 2 + 1]; + nvt_spi_xdata[i] = (int16_t)data; + } + +#if NVT_SPI_TOUCH_KEY_NUM > 0 + //read button xdata : step3 + //---change xdata index--- + nvt_spi_set_page(xdata_btn_addr); + + //---read data--- + buf[0] = (xdata_btn_addr & 0xFF); + nvt_spi_read(buf, (NVT_SPI_TOUCH_KEY_NUM * 2 + 1)); + + //---2bytes-to-1data--- + for (i = 0; i < NVT_SPI_TOUCH_KEY_NUM; i++) + nvt_spi_xdata[ts->x_num * ts->y_num + i] = + (int16_t)(buf[1 + i * 2] + 256 * buf[1 + i * 2 + 1]); +#endif + + //---set xdata index to EVENT BUF ADDR--- + nvt_spi_set_page(ts->mmap->EVENT_BUF_ADDR); +} + +/* + ******************************************************* + * Description: + * Novatek touchscreen get meta data function. + * + * return: + * n.a. + ****************************************************** + */ +void nvt_spi_get_mdata(int32_t *buf, uint8_t *m_x_num, uint8_t *m_y_num) +{ + struct nvt_spi_data_t *ts = nvt_spi_data; + + *m_x_num = ts->x_num; + *m_y_num = ts->y_num; + memcpy(buf, nvt_spi_xdata, + ((ts->x_num * ts->y_num + NVT_SPI_TOUCH_KEY_NUM) * sizeof(int32_t))); +} + +/* + ******************************************************* + * Description: + * Novatek touchscreen read and get number of meta data function. + * + * return: + * n.a. + ******************************************************* + */ +void nvt_spi_read_get_num_mdata(uint32_t xdata_addr, int32_t *buffer, uint32_t num) +{ + int32_t i = 0; + int32_t j = 0; + int32_t k = 0; + uint8_t buf[NVT_SPI_BUS_TRANSFER_LENGTH + 2] = {0}; + uint32_t head_addr = 0; + int32_t dummy_len = 0; + int32_t data_len = 0; + int32_t residual_len = 0; + int32_t index, data; + struct nvt_spi_data_t *ts = nvt_spi_data; + + //---set xdata sector address & length--- + head_addr = xdata_addr - (xdata_addr % NVT_SPI_XDATA_SECTOR_SIZE); + dummy_len = xdata_addr - head_addr; + data_len = num * 2; + residual_len = (head_addr + dummy_len + data_len) % NVT_SPI_XDATA_SECTOR_SIZE; + + //printk("head_addr=0x%05X, dummy_len=0x%05X, data_len=0x%05X, residual_len=0x%05X\n", + // head_addr, dummy_len, data_len, residual_len); + + //read xdata : step 1 + for (i = 0; i < ((dummy_len + data_len) / NVT_SPI_XDATA_SECTOR_SIZE); i++) { + //---change xdata index--- + nvt_spi_set_page(head_addr + NVT_SPI_XDATA_SECTOR_SIZE * i); + + //---read xdata by NVT_SPI_BUS_TRANSFER_LENGTH + for (j = 0; j < (NVT_SPI_XDATA_SECTOR_SIZE / NVT_SPI_BUS_TRANSFER_LENGTH); j++) { + //---read data--- + buf[0] = NVT_SPI_BUS_TRANSFER_LENGTH * j; + nvt_spi_read(buf, NVT_SPI_BUS_TRANSFER_LENGTH + 1); + + //---copy buf to nvt_spi_xdata_tmp--- + for (k = 0; k < NVT_SPI_BUS_TRANSFER_LENGTH; k++) { + index = NVT_SPI_XDATA_SECTOR_SIZE * i; + index += NVT_SPI_BUS_TRANSFER_LENGTH * j + k; + nvt_spi_xdata_tmp[index] = buf[k + 1]; + } + } + //printk("addr=0x%05X\n", (head_addr+NVT_SPI_XDATA_SECTOR_SIZE*i)); + } + + //read xdata : step2 + if (residual_len != 0) { + //---change xdata index--- + nvt_spi_set_page(xdata_addr + data_len - residual_len); + + //---read xdata by NVT_SPI_BUS_TRANSFER_LENGTH + for (j = 0; j < (residual_len / NVT_SPI_BUS_TRANSFER_LENGTH + 1); j++) { + //---read data--- + buf[0] = NVT_SPI_BUS_TRANSFER_LENGTH * j; + nvt_spi_read(buf, NVT_SPI_BUS_TRANSFER_LENGTH + 1); + + //---copy buf to nvt_spi_xdata_tmp--- + for (k = 0; k < NVT_SPI_BUS_TRANSFER_LENGTH; k++) { + index = (dummy_len + data_len - residual_len); + index += NVT_SPI_BUS_TRANSFER_LENGTH * j + k; + nvt_spi_xdata_tmp[index] = buf[k + 1]; + //printk("0x%02X, 0x%04x\n", buf[k+1], index)); + + } + } + //printk("addr=0x%05X\n", (xdata_addr+data_len-residual_len)); + } + + //---remove dummy data and 2bytes-to-1data--- + for (i = 0; i < (data_len / 2); i++) { + data = nvt_spi_xdata_tmp[dummy_len + i * 2]; + data += 256 * nvt_spi_xdata_tmp[dummy_len + i * 2 + 1]; + buffer[i] = (int16_t)data; + } + + //---set xdata index to EVENT BUF ADDR--- + nvt_spi_set_page(ts->mmap->EVENT_BUF_ADDR); +} + +/* + ****************************************************** + * Description: + * Novatek touchscreen firmware version show function. + * + * return: + * Executive outcomes. 0---succeed. + ****************************************************** + */ +static int32_t c_fw_version_show(struct seq_file *m, void *v) +{ + struct nvt_spi_data_t *ts = nvt_spi_data; + + seq_printf(m, "fw_ver=%d, x_num=%d, y_num=%d, button_num=%d\n", + ts->fw_ver, ts->x_num, ts->y_num, ts->max_button_num); + return 0; +} + +/* + ******************************************************* + * Description: + * Novatek touchscreen xdata sequence print show + * function. + * + * return: + * Executive outcomes. 0---succeed. + ****************************************************** + */ +static int32_t c_show(struct seq_file *m, void *v) +{ + int32_t i = 0; + int32_t j = 0; + struct nvt_spi_data_t *ts = nvt_spi_data; + + for (i = 0; i < ts->y_num; i++) { + for (j = 0; j < ts->x_num; j++) + seq_printf(m, "%5d, ", nvt_spi_xdata[i * ts->x_num + j]); + + seq_puts(m, "\n"); + } + +#if NVT_SPI_TOUCH_KEY_NUM > 0 + for (i = 0; i < NVT_SPI_TOUCH_KEY_NUM; i++) + seq_printf(m, "%5d, ", nvt_spi_xdata[ts->x_num * ts->y_num + i]); + + seq_puts(m, "\n"); +#endif + + seq_puts(m, "\n\n"); + return 0; +} + +/* + ******************************************************* + * Description: + * Novatek pen 1D diff xdata sequence print show + * function. + * + * return: + * Executive outcomes. 0---succeed. + ****************************************************** + */ +static int32_t c_pen_1d_diff_show(struct seq_file *m, void *v) +{ + int32_t i = 0; + struct nvt_spi_data_t *ts = nvt_spi_data; + + seq_puts(m, "Tip X:\n"); + for (i = 0; i < ts->x_num; i++) + seq_printf(m, "%5d, ", nvt_spi_xdata_pen_tip_x[i]); + + seq_puts(m, "\n"); + seq_puts(m, "Tip Y:\n"); + for (i = 0; i < ts->y_num; i++) + seq_printf(m, "%5d, ", nvt_spi_xdata_pen_tip_y[i]); + + seq_puts(m, "\n"); + seq_puts(m, "Ring X:\n"); + for (i = 0; i < ts->x_num; i++) + seq_printf(m, "%5d, ", nvt_spi_xdata_pen_ring_x[i]); + + seq_puts(m, "\n"); + seq_puts(m, "Ring Y:\n"); + for (i = 0; i < ts->y_num; i++) + seq_printf(m, "%5d, ", nvt_spi_xdata_pen_ring_y[i]); + + seq_puts(m, "\n"); + + seq_puts(m, "\n\n"); + return 0; +} + +/* + ******************************************************* + * Description: + * Novatek touchscreen xdata sequence print start + * function. + * + * return: + * Executive outcomes. 1---call next function. + * NULL---not call next function and sequence loop + * stop. + ******************************************************* + */ +static void *c_start(struct seq_file *m, loff_t *pos) +{ + return *pos < 1 ? (void *)1 : NULL; +} + +/* + ******************************************************* + * Description: + * Novatek touchscreen xdata sequence print next + * function. + * + * return: + * Executive outcomes. NULL---no next and call sequence + * stop function. + ****************************************************** + */ +static void *c_next(struct seq_file *m, void *v, loff_t *pos) +{ + ++*pos; + return NULL; +} + +/* + ******************************************************* + * Description: + * Novatek touchscreen xdata sequence print stop + * function. + * + * return: + * n.a. + ****************************************************** + */ +static void c_stop(struct seq_file *m, void *v) +{ +} + +const struct seq_operations nvt_spi_fw_version_seq_ops = { + .start = c_start, + .next = c_next, + .stop = c_stop, + .show = c_fw_version_show +}; + +const struct seq_operations nvt_spi_seq_ops = { + .start = c_start, + .next = c_next, + .stop = c_stop, + .show = c_show +}; + +const struct seq_operations nvt_spi_pen_diff_seq_ops = { + .start = c_start, + .next = c_next, + .stop = c_stop, + .show = c_pen_1d_diff_show +}; + +/* + ******************************************************* + * Description: + * Novatek touchscreen /proc/nvt_fw_version open + * function. + * + * return: + * n.a. + ****************************************************** + */ +static int32_t nvt_spi_fw_version_open(struct inode *inode, struct file *file) +{ + struct nvt_spi_data_t *ts = nvt_spi_data; + + if (mutex_lock_interruptible(&ts->lock)) + return -ERESTARTSYS; + + NVT_LOG("++\n"); + +#if NVT_SPI_TOUCH_ESD_PROTECT + nvt_spi_esd_check_enable(false); +#endif /* #if NVT_SPI_TOUCH_ESD_PROTECT */ + + if (nvt_spi_get_fw_info()) { + mutex_unlock(&ts->lock); + return -EAGAIN; + } + + mutex_unlock(&ts->lock); + + NVT_LOG("--\n"); + + return seq_open(file, &nvt_spi_fw_version_seq_ops); +} + +static const struct proc_ops nvt_spi_fw_version_fops = { + .proc_open = nvt_spi_fw_version_open, + .proc_read = seq_read, + .proc_lseek = seq_lseek, + .proc_release = seq_release, +}; + +/* + ******************************************************* + * Description: + * Novatek touchscreen /proc/nvt_baseline open function. + * + * return: + * Executive outcomes. 0---succeed. + ****************************************************** + */ +static int32_t nvt_spi_baseline_open(struct inode *inode, struct file *file) +{ + struct nvt_spi_data_t *ts = nvt_spi_data; + + if (mutex_lock_interruptible(&ts->lock)) + return -ERESTARTSYS; + + NVT_LOG("++\n"); + +#if NVT_SPI_TOUCH_ESD_PROTECT + nvt_spi_esd_check_enable(false); +#endif /* #if NVT_SPI_TOUCH_ESD_PROTECT */ + + if (nvt_spi_clear_fw_status()) { + mutex_unlock(&ts->lock); + return -EAGAIN; + } + + nvt_spi_change_mode(NVT_SPI_TEST_MODE_2); + + if (nvt_spi_check_fw_status()) { + mutex_unlock(&ts->lock); + return -EAGAIN; + } + + if (nvt_spi_get_fw_info()) { + mutex_unlock(&ts->lock); + return -EAGAIN; + } + + nvt_spi_read_mdata(ts->mmap->BASELINE_ADDR, ts->mmap->BASELINE_BTN_ADDR); + + nvt_spi_change_mode(NVT_SPI_NORMAL_MODE); + + mutex_unlock(&ts->lock); + + NVT_LOG("--\n"); + + return seq_open(file, &nvt_spi_seq_ops); +} + +static const struct proc_ops nvt_spi_baseline_fops = { + .proc_open = nvt_spi_baseline_open, + .proc_read = seq_read, + .proc_lseek = seq_lseek, + .proc_release = seq_release, +}; + +/* + ******************************************************* + * Description: + * Novatek touchscreen /proc/nvt_raw open function. + * + * return: + * Executive outcomes. 0---succeed. + ****************************************************** + */ +static int32_t nvt_spi_raw_open(struct inode *inode, struct file *file) +{ + struct nvt_spi_data_t *ts = nvt_spi_data; + + if (mutex_lock_interruptible(&ts->lock)) + return -ERESTARTSYS; + + NVT_LOG("++\n"); + +#if NVT_SPI_TOUCH_ESD_PROTECT + nvt_spi_esd_check_enable(false); +#endif /* #if NVT_SPI_TOUCH_ESD_PROTECT */ + + if (nvt_spi_clear_fw_status()) { + mutex_unlock(&ts->lock); + return -EAGAIN; + } + + nvt_spi_change_mode(NVT_SPI_TEST_MODE_2); + + if (nvt_spi_check_fw_status()) { + mutex_unlock(&ts->lock); + return -EAGAIN; + } + + if (nvt_spi_get_fw_info()) { + mutex_unlock(&ts->lock); + return -EAGAIN; + } + + if (nvt_spi_get_fw_pipe() == 0) + nvt_spi_read_mdata(ts->mmap->RAW_PIPE0_ADDR, ts->mmap->RAW_BTN_PIPE0_ADDR); + else + nvt_spi_read_mdata(ts->mmap->RAW_PIPE1_ADDR, ts->mmap->RAW_BTN_PIPE1_ADDR); + + nvt_spi_change_mode(NVT_SPI_NORMAL_MODE); + + mutex_unlock(&ts->lock); + + NVT_LOG("--\n"); + + return seq_open(file, &nvt_spi_seq_ops); +} + +static const struct proc_ops nvt_spi_raw_fops = { + .proc_open = nvt_spi_raw_open, + .proc_read = seq_read, + .proc_lseek = seq_lseek, + .proc_release = seq_release, +}; + +/* + ****************************************************** + * Description: + * Novatek touchscreen /proc/nvt_diff open function. + * + * return: + * Executive outcomes. 0---succeed. negative---failed. + ****************************************************** + */ +static int32_t nvt_spi_diff_open(struct inode *inode, struct file *file) +{ + struct nvt_spi_data_t *ts = nvt_spi_data; + + if (mutex_lock_interruptible(&ts->lock)) + return -ERESTARTSYS; + + NVT_LOG("++\n"); + +#if NVT_SPI_TOUCH_ESD_PROTECT + nvt_spi_esd_check_enable(false); +#endif /* #if NVT_SPI_TOUCH_ESD_PROTECT */ + + if (nvt_spi_clear_fw_status()) { + mutex_unlock(&ts->lock); + return -EAGAIN; + } + + nvt_spi_change_mode(NVT_SPI_TEST_MODE_2); + + if (nvt_spi_check_fw_status()) { + mutex_unlock(&ts->lock); + return -EAGAIN; + } + + if (nvt_spi_get_fw_info()) { + mutex_unlock(&ts->lock); + return -EAGAIN; + } + + if (nvt_spi_get_fw_pipe() == 0) + nvt_spi_read_mdata(ts->mmap->DIFF_PIPE0_ADDR, ts->mmap->DIFF_BTN_PIPE0_ADDR); + else + nvt_spi_read_mdata(ts->mmap->DIFF_PIPE1_ADDR, ts->mmap->DIFF_BTN_PIPE1_ADDR); + + nvt_spi_change_mode(NVT_SPI_NORMAL_MODE); + + mutex_unlock(&ts->lock); + + NVT_LOG("--\n"); + + return seq_open(file, &nvt_spi_seq_ops); +} + +static const struct proc_ops nvt_spi_diff_fops = { + .proc_open = nvt_spi_diff_open, + .proc_read = seq_read, + .proc_lseek = seq_lseek, + .proc_release = seq_release, +}; + +/* + ******************************************************* + * Description: + * Novatek touchscreen /proc/nvt_pen_diff open function. + * + * return: + * Executive outcomes. 0---succeed. negative---failed. + ****************************************************** + */ +static int32_t nvt_spi_pen_diff_open(struct inode *inode, struct file *file) +{ + uint32_t addr; + struct nvt_spi_data_t *ts = nvt_spi_data; + + if (mutex_lock_interruptible(&ts->lock)) + return -ERESTARTSYS; + + NVT_LOG("++\n"); + +#if NVT_SPI_TOUCH_ESD_PROTECT + nvt_spi_esd_check_enable(false); +#endif /* #if NVT_SPI_TOUCH_ESD_PROTECT */ + + if (nvt_set_pen_inband_mode_1_spi(0xFF, 0x00)) { + mutex_unlock(&ts->lock); + return -EAGAIN; + } + + if (nvt_spi_check_fw_reset_state(NVT_SPI_RESET_STATE_NORMAL_RUN)) { + mutex_unlock(&ts->lock); + return -EAGAIN; + } + + if (nvt_spi_clear_fw_status()) { + mutex_unlock(&ts->lock); + return -EAGAIN; + } + + nvt_spi_change_mode(NVT_SPI_TEST_MODE_2); + + if (nvt_spi_check_fw_status()) { + mutex_unlock(&ts->lock); + return -EAGAIN; + } + + if (nvt_spi_get_fw_info()) { + mutex_unlock(&ts->lock); + return -EAGAIN; + } + + addr = ts->mmap->PEN_1D_DIFF_TIP_X_ADDR; + nvt_spi_read_get_num_mdata(addr, nvt_spi_xdata_pen_tip_x, ts->x_num); + + addr = ts->mmap->PEN_1D_DIFF_TIP_Y_ADDR; + nvt_spi_read_get_num_mdata(addr, nvt_spi_xdata_pen_tip_y, ts->y_num); + + addr = ts->mmap->PEN_1D_DIFF_RING_X_ADDR; + nvt_spi_read_get_num_mdata(addr, nvt_spi_xdata_pen_ring_x, ts->x_num); + + addr = ts->mmap->PEN_1D_DIFF_RING_Y_ADDR; + nvt_spi_read_get_num_mdata(addr, nvt_spi_xdata_pen_ring_y, ts->y_num); + + nvt_spi_change_mode(NVT_SPI_NORMAL_MODE); + + nvt_spi_set_pen_normal_mode(); + + nvt_spi_check_fw_reset_state(NVT_SPI_RESET_STATE_NORMAL_RUN); + + mutex_unlock(&ts->lock); + + NVT_LOG("--\n"); + + return seq_open(file, &nvt_spi_pen_diff_seq_ops); +} + +static const struct proc_ops nvt_spi_pen_diff_fops = { + .proc_open = nvt_spi_pen_diff_open, + .proc_read = seq_read, + .proc_lseek = seq_lseek, + .proc_release = seq_release, +}; + +/* + ******************************************************* + * Description: + * Novatek touchscreen extra function proc. file node + * initial function. + * + * return: + * Executive outcomes. 0---succeed. -12---failed. + ****************************************************** + */ +int32_t nvt_spi_extra_proc_init(void) +{ + struct nvt_spi_data_t *ts = nvt_spi_data; + + nvt_spi_proc_fw_version_entry = proc_create(NVT_SPI_FW_VERSION, 0444, NULL, + &nvt_spi_fw_version_fops); + if (nvt_spi_proc_fw_version_entry == NULL) { + NVT_ERR("create proc/%s Failed!\n", NVT_SPI_FW_VERSION); + return -ENOMEM; + } + NVT_LOG("create proc/%s Succeeded!\n", NVT_SPI_FW_VERSION); + + nvt_spi_proc_baseline_entry = proc_create(NVT_SPI_BASELINE, 0444, NULL, + &nvt_spi_baseline_fops); + if (nvt_spi_proc_baseline_entry == NULL) { + NVT_ERR("create proc/%s Failed!\n", NVT_SPI_BASELINE); + return -ENOMEM; + } + NVT_LOG("create proc/%s Succeeded!\n", NVT_SPI_BASELINE); + + nvt_spi_proc_raw_entry = proc_create(NVT_SPI_RAW, 0444, NULL, &nvt_spi_raw_fops); + if (nvt_spi_proc_raw_entry == NULL) { + NVT_ERR("create proc/%s Failed!\n", NVT_SPI_RAW); + return -ENOMEM; + } + NVT_LOG("create proc/%s Succeeded!\n", NVT_SPI_RAW); + + nvt_spi_proc_diff_entry = proc_create(NVT_SPI_DIFF, 0444, NULL, &nvt_spi_diff_fops); + if (nvt_spi_proc_diff_entry == NULL) { + NVT_ERR("create proc/%s Failed!\n", NVT_SPI_DIFF); + return -ENOMEM; + } + NVT_LOG("create proc/%s Succeeded!\n", NVT_SPI_DIFF); + + if (ts->pen_support) { + nvt_spi_proc_pen_diff_entry = proc_create(NVT_SPI_PEN_DIFF, 0444, NULL, + &nvt_spi_pen_diff_fops); + if (nvt_spi_proc_pen_diff_entry == NULL) { + NVT_ERR("create proc/%s Failed!\n", NVT_SPI_PEN_DIFF); + return -ENOMEM; + } + NVT_LOG("create proc/%s Succeeded!\n", NVT_SPI_PEN_DIFF); + } + + return 0; +} + +/* + ****************************************************** + * Description: + * Novatek touchscreen extra function proc. file node + * deinitial function. + * + * return: + * n.a. + ****************************************************** + */ +void nvt_spi_extra_proc_deinit(void) +{ + struct nvt_spi_data_t *ts = nvt_spi_data; + + if (nvt_spi_proc_fw_version_entry != NULL) { + remove_proc_entry(NVT_SPI_FW_VERSION, NULL); + nvt_spi_proc_fw_version_entry = NULL; + NVT_LOG("Removed /proc/%s\n", NVT_SPI_FW_VERSION); + } + + if (nvt_spi_proc_baseline_entry != NULL) { + remove_proc_entry(NVT_SPI_BASELINE, NULL); + nvt_spi_proc_baseline_entry = NULL; + NVT_LOG("Removed /proc/%s\n", NVT_SPI_BASELINE); + } + + if (nvt_spi_proc_raw_entry != NULL) { + remove_proc_entry(NVT_SPI_RAW, NULL); + nvt_spi_proc_raw_entry = NULL; + NVT_LOG("Removed /proc/%s\n", NVT_SPI_RAW); + } + + if (nvt_spi_proc_diff_entry != NULL) { + remove_proc_entry(NVT_SPI_DIFF, NULL); + nvt_spi_proc_diff_entry = NULL; + NVT_LOG("Removed /proc/%s\n", NVT_SPI_DIFF); + } + + if (ts->pen_support) { + if (nvt_spi_proc_pen_diff_entry != NULL) { + remove_proc_entry(NVT_SPI_PEN_DIFF, NULL); + nvt_spi_proc_pen_diff_entry = NULL; + NVT_LOG("Removed /proc/%s\n", NVT_SPI_PEN_DIFF); + } + } +} +#endif + + + +#endif diff --git a/qcom/opensource/touch-drivers/nt36xxx/nt36xxx_fw_update.c b/qcom/opensource/touch-drivers/nt36xxx/nt36xxx_fw_update.c new file mode 100644 index 0000000000..cbce144794 --- /dev/null +++ b/qcom/opensource/touch-drivers/nt36xxx/nt36xxx_fw_update.c @@ -0,0 +1,2002 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2010 - 2018 Novatek, Inc. + * + * $Revision: 47247 $ + * $Date: 2019-07-10 10:41:36 +0800 (Wed, 10 Jul 2019) $ + * + * 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. + * + */ + +#if !defined(NVT_NT36XXX_SPI) /* NT36XXX I2C */ + +#include + +#include "nt36xxx.h" + +#if BOOT_UPDATE_FIRMWARE + +#define SIZE_4KB 4096 +#define FLASH_SECTOR_SIZE SIZE_4KB +#define SIZE_64KB 65536 +#define BLOCK_64KB_NUM 4 +#define FW_BIN_VER_OFFSET (fw_need_write_size - SIZE_4KB) +#define FW_BIN_VER_BAR_OFFSET (FW_BIN_VER_OFFSET + 1) + +#define NVT_FLASH_END_FLAG_LEN 3 +#define NVT_FLASH_END_FLAG_ADDR (fw_need_write_size - NVT_FLASH_END_FLAG_LEN) + +const struct firmware *fw_entry = NULL; +static size_t fw_need_write_size = 0; + +static int32_t nvt_get_fw_need_write_size(const struct firmware *fw_entry) +{ + int32_t i = 0; + int32_t total_sectors_to_check = 0; + + total_sectors_to_check = fw_entry->size / FLASH_SECTOR_SIZE; + /* printk("total_sectors_to_check = %d\n", total_sectors_to_check); */ + + for (i = total_sectors_to_check; i > 0; i--) { + /* printk("current end flag address checked = 0x%X\n", i * FLASH_SECTOR_SIZE - NVT_FLASH_END_FLAG_LEN); */ + /* check if there is end flag "NVT" at the end of this sector */ + if ((memcmp((const char *)&fw_entry->data[i * FLASH_SECTOR_SIZE - + NVT_FLASH_END_FLAG_LEN], "NVT", NVT_FLASH_END_FLAG_LEN) == 0) || + (memcmp((const char *)&fw_entry->data[i * FLASH_SECTOR_SIZE - + NVT_FLASH_END_FLAG_LEN], "MOD", NVT_FLASH_END_FLAG_LEN) == 0)) { + fw_need_write_size = i * FLASH_SECTOR_SIZE; + NVT_LOG("fw_need_write_size = %zu(0x%zx)\n", fw_need_write_size, fw_need_write_size); + return 0; + } + } + + NVT_ERR("end flag \"NVT\" not found!\n"); + return -1; +} + +/******************************************************* +Description: + Novatek touchscreen request update firmware function. + +return: + Executive outcomes. 0---succeed. -1,-22---failed. +*******************************************************/ +int32_t update_firmware_request(char *filename) +{ + int32_t ret = 0; + + if (NULL == filename) { + return -1; + } + + NVT_LOG("filename is %s\n", filename); + + ret = request_firmware(&fw_entry, filename, &ts->client->dev); + if (ret) { + NVT_ERR("firmware load failed, ret=%d\n", ret); + return ret; + } + + // check FW need to write size + if (nvt_get_fw_need_write_size(fw_entry)) { + NVT_ERR("get fw need to write size fail!\n"); + return -EINVAL; + } + + // check if FW version add FW version bar equals 0xFF + if (*(fw_entry->data + FW_BIN_VER_OFFSET) + *(fw_entry->data + FW_BIN_VER_BAR_OFFSET) != 0xFF) { + NVT_ERR("bin file FW_VER + FW_VER_BAR should be 0xFF!\n"); + NVT_ERR("FW_VER=0x%02X, FW_VER_BAR=0x%02X\n", *(fw_entry->data+FW_BIN_VER_OFFSET), *(fw_entry->data+FW_BIN_VER_BAR_OFFSET)); + return -EINVAL; + } + + return 0; +} + +/******************************************************* +Description: + Novatek touchscreen release update firmware function. + +return: + n.a. +*******************************************************/ +void update_firmware_release(void) +{ + if (fw_entry) { + release_firmware(fw_entry); + } + fw_entry=NULL; +} + +/******************************************************* +Description: + Novatek touchscreen check firmware version function. + +return: + Executive outcomes. 0---need update. 1---need not + update. +*******************************************************/ +int32_t Check_FW_Ver(void) +{ + uint8_t buf[16] = {0}; + int32_t ret = 0; + + //write i2c index to EVENT BUF ADDR + ret = nvt_set_page(I2C_BLDR_Address, ts->mmap->EVENT_BUF_ADDR | EVENT_MAP_FWINFO); + if (ret < 0) { + NVT_ERR("i2c write error!(%d)\n", ret); + return ret; + } + + //read Firmware Version + buf[0] = EVENT_MAP_FWINFO; + buf[1] = 0x00; + buf[2] = 0x00; + ret = CTP_I2C_READ(ts->client, I2C_BLDR_Address, buf, 3); + if (ret < 0) { + NVT_ERR("i2c read error!(%d)\n", ret); + return ret; + } + + NVT_LOG("IC FW Ver = 0x%02X, FW Ver Bar = 0x%02X\n", buf[1], buf[2]); + NVT_LOG("Bin FW Ver = 0x%02X, FW ver Bar = 0x%02X\n", + fw_entry->data[FW_BIN_VER_OFFSET], fw_entry->data[FW_BIN_VER_BAR_OFFSET]); + + // check IC FW_VER + FW_VER_BAR equals 0xFF or not, need to update if not + if ((buf[1] + buf[2]) != 0xFF) { + NVT_ERR("IC FW_VER + FW_VER_BAR not equals to 0xFF!\n"); + return 0; + } + + // compare IC and binary FW version + if (buf[1] > fw_entry->data[FW_BIN_VER_OFFSET]) + return 1; + else + return 0; +} + +/******************************************************* +Description: + Novatek touchscreen resume from deep power down function. + +return: + Executive outcomes. 0---succeed. negative---failed. +*******************************************************/ +int32_t Resume_PD(void) +{ + uint8_t buf[8] = {0}; + int32_t ret = 0; + int32_t retry = 0; + + // Resume Command + buf[0] = 0x00; + buf[1] = 0xAB; + ret = CTP_I2C_WRITE(ts->client, I2C_HW_Address, buf, 2); + if (ret < 0) { + NVT_ERR("Write Enable error!!(%d)\n", ret); + return ret; + } + + // Check 0xAA (Resume Command) + retry = 0; + while(1) { + msleep(1); + buf[0] = 0x00; + buf[1] = 0x00; + ret = CTP_I2C_READ(ts->client, I2C_HW_Address, buf, 2); + if (ret < 0) { + NVT_ERR("Check 0xAA (Resume Command) error!!(%d)\n", ret); + return ret; + } + if (buf[1] == 0xAA) { + break; + } + retry++; + if (unlikely(retry > 20)) { + NVT_ERR("Check 0xAA (Resume Command) error!! status=0x%02X\n", buf[1]); + return -1; + } + } + msleep(10); + + NVT_LOG("Resume PD OK\n"); + return 0; +} + +/******************************************************* +Description: + Novatek touchscreen check firmware checksum function. + +return: + Executive outcomes. 0---checksum not match. + 1---checksum match. -1--- checksum read failed. +*******************************************************/ +int32_t Check_CheckSum(void) +{ + uint8_t buf[64] = {0}; + uint32_t XDATA_Addr = ts->mmap->READ_FLASH_CHECKSUM_ADDR; + int32_t ret = 0; + int32_t i = 0; + int32_t k = 0; + uint16_t WR_Filechksum[BLOCK_64KB_NUM] = {0}; + uint16_t RD_Filechksum[BLOCK_64KB_NUM] = {0}; + size_t len_in_blk = 0; + int32_t retry = 0; + + if (Resume_PD()) { + NVT_ERR("Resume PD error!!\n"); + return -1; + } + + for (i = 0; i < BLOCK_64KB_NUM; i++) { + if (fw_need_write_size > (i * SIZE_64KB)) { + // Calculate WR_Filechksum of each 64KB block + len_in_blk = min(fw_need_write_size - i * SIZE_64KB, (size_t)SIZE_64KB); + WR_Filechksum[i] = i + 0x00 + 0x00 + (((len_in_blk - 1) >> 8) & 0xFF) + ((len_in_blk - 1) & 0xFF); + for (k = 0; k < len_in_blk; k++) { + WR_Filechksum[i] += fw_entry->data[k + i * SIZE_64KB]; + } + WR_Filechksum[i] = 65535 - WR_Filechksum[i] + 1; + + // Fast Read Command + buf[0] = 0x00; + buf[1] = 0x07; + buf[2] = i; + buf[3] = 0x00; + buf[4] = 0x00; + buf[5] = ((len_in_blk - 1) >> 8) & 0xFF; + buf[6] = (len_in_blk - 1) & 0xFF; + ret = CTP_I2C_WRITE(ts->client, I2C_HW_Address, buf, 7); + if (ret < 0) { + NVT_ERR("Fast Read Command error!!(%d)\n", ret); + return ret; + } + // Check 0xAA (Fast Read Command) + retry = 0; + while (1) { + msleep(80); + buf[0] = 0x00; + buf[1] = 0x00; + ret = CTP_I2C_READ(ts->client, I2C_HW_Address, buf, 2); + if (ret < 0) { + NVT_ERR("Check 0xAA (Fast Read Command) error!!(%d)\n", ret); + return ret; + } + if (buf[1] == 0xAA) { + break; + } + retry++; + if (unlikely(retry > 5)) { + NVT_ERR("Check 0xAA (Fast Read Command) failed, buf[1]=0x%02X, retry=%d\n", buf[1], retry); + return -1; + } + } + // Read Checksum (write addr high byte & middle byte) + ret = nvt_set_page(I2C_BLDR_Address, XDATA_Addr); + if (ret < 0) { + NVT_ERR("Read Checksum (write addr high byte & middle byte) error!!(%d)\n", ret); + return ret; + } + // Read Checksum + buf[0] = (XDATA_Addr) & 0xFF; + buf[1] = 0x00; + buf[2] = 0x00; + ret = CTP_I2C_READ(ts->client, I2C_BLDR_Address, buf, 3); + if (ret < 0) { + NVT_ERR("Read Checksum error!!(%d)\n", ret); + return ret; + } + + RD_Filechksum[i] = (uint16_t)((buf[2] << 8) | buf[1]); + if (WR_Filechksum[i] != RD_Filechksum[i]) { + NVT_ERR("RD_Filechksum[%d]=0x%04X, WR_Filechksum[%d]=0x%04X\n", i, RD_Filechksum[i], i, WR_Filechksum[i]); + NVT_ERR("firmware checksum not match!!\n"); + return 0; + } + } + } + + NVT_LOG("firmware checksum match\n"); + return 1; +} + +/******************************************************* +Description: + Novatek touchscreen initial bootloader and flash + block function. + +return: + Executive outcomes. 0---succeed. negative---failed. +*******************************************************/ +int32_t Init_BootLoader(void) +{ + uint8_t buf[64] = {0}; + int32_t ret = 0; + int32_t retry = 0; + + // SW Reset & Idle + nvt_sw_reset_idle(); + + // Initiate Flash Block + buf[0] = 0x00; + buf[1] = 0x00; + buf[2] = I2C_FW_Address; + ret = CTP_I2C_WRITE(ts->client, I2C_HW_Address, buf, 3); + if (ret < 0) { + NVT_ERR("Inittial Flash Block error!!(%d)\n", ret); + return ret; + } + + // Check 0xAA (Initiate Flash Block) + retry = 0; + while(1) { + msleep(1); + buf[0] = 0x00; + buf[1] = 0x00; + ret = CTP_I2C_READ(ts->client, I2C_HW_Address, buf, 2); + if (ret < 0) { + NVT_ERR("Check 0xAA (Inittial Flash Block) error!!(%d)\n", ret); + return ret; + } + if (buf[1] == 0xAA) { + break; + } + retry++; + if (unlikely(retry > 20)) { + NVT_ERR("Check 0xAA (Inittial Flash Block) error!! status=0x%02X\n", buf[1]); + return -1; + } + } + + NVT_LOG("Init OK \n"); + msleep(20); + + return 0; +} + +/******************************************************* +Description: + Novatek touchscreen erase flash sectors function. + +return: + Executive outcomes. 0---succeed. negative---failed. +*******************************************************/ +int32_t Erase_Flash(void) +{ + uint8_t buf[64] = {0}; + int32_t ret = 0; + int32_t count = 0; + int32_t i = 0; + int32_t Flash_Address = 0; + int32_t retry = 0; + + // Write Enable + buf[0] = 0x00; + buf[1] = 0x06; + ret = CTP_I2C_WRITE(ts->client, I2C_HW_Address, buf, 2); + if (ret < 0) { + NVT_ERR("Write Enable (for Write Status Register) error!!(%d)\n", ret); + return ret; + } + // Check 0xAA (Write Enable) + retry = 0; + while (1) { + msleep(1); + buf[0] = 0x00; + buf[1] = 0x00; + ret = CTP_I2C_READ(ts->client, I2C_HW_Address, buf, 2); + if (ret < 0) { + NVT_ERR("Check 0xAA (Write Enable for Write Status Register) error!!(%d)\n", ret); + return ret; + } + if (buf[1] == 0xAA) { + break; + } + retry++; + if (unlikely(retry > 20)) { + NVT_ERR("Check 0xAA (Write Enable for Write Status Register) error!! status=0x%02X\n", buf[1]); + return -1; + } + } + + // Write Status Register + buf[0] = 0x00; + buf[1] = 0x01; + buf[2] = 0x00; + ret = CTP_I2C_WRITE(ts->client, I2C_HW_Address, buf, 3); + if (ret < 0) { + NVT_ERR("Write Status Register error!!(%d)\n", ret); + return ret; + } + // Check 0xAA (Write Status Register) + retry = 0; + while (1) { + msleep(1); + buf[0] = 0x00; + buf[1] = 0x00; + ret = CTP_I2C_READ(ts->client, I2C_HW_Address, buf, 2); + if (ret < 0) { + NVT_ERR("Check 0xAA (Write Status Register) error!!(%d)\n", ret); + return ret; + } + if (buf[1] == 0xAA) { + break; + } + retry++; + if (unlikely(retry > 20)) { + NVT_ERR("Check 0xAA (Write Status Register) error!! status=0x%02X\n", buf[1]); + return -1; + } + } + + // Read Status + retry = 0; + while (1) { + msleep(5); + buf[0] = 0x00; + buf[1] = 0x05; + ret = CTP_I2C_WRITE(ts->client, I2C_HW_Address, buf, 2); + if (ret < 0) { + NVT_ERR("Read Status (for Write Status Register) error!!(%d)\n", ret); + return ret; + } + + // Check 0xAA (Read Status) + buf[0] = 0x00; + buf[1] = 0x00; + buf[2] = 0x00; + ret = CTP_I2C_READ(ts->client, I2C_HW_Address, buf, 3); + if (ret < 0) { + NVT_ERR("Check 0xAA (Read Status for Write Status Register) error!!(%d)\n", ret); + return ret; + } + if ((buf[1] == 0xAA) && (buf[2] == 0x00)) { + break; + } + retry++; + if (unlikely(retry > 100)) { + NVT_ERR("Check 0xAA (Read Status for Write Status Register) failed, buf[1]=0x%02X, buf[2]=0x%02X, retry=%d\n", buf[1], buf[2], retry); + return -1; + } + } + + if (fw_need_write_size % FLASH_SECTOR_SIZE) + count = fw_need_write_size / FLASH_SECTOR_SIZE + 1; + else + count = fw_need_write_size / FLASH_SECTOR_SIZE; + + for(i = 0; i < count; i++) { + // Write Enable + buf[0] = 0x00; + buf[1] = 0x06; + ret = CTP_I2C_WRITE(ts->client, I2C_HW_Address, buf, 2); + if (ret < 0) { + NVT_ERR("Write Enable error!!(%d,%d)\n", ret, i); + return ret; + } + // Check 0xAA (Write Enable) + retry = 0; + while (1) { + msleep(1); + buf[0] = 0x00; + buf[1] = 0x00; + ret = CTP_I2C_READ(ts->client, I2C_HW_Address, buf, 2); + if (ret < 0) { + NVT_ERR("Check 0xAA (Write Enable) error!!(%d,%d)\n", ret, i); + return ret; + } + if (buf[1] == 0xAA) { + break; + } + retry++; + if (unlikely(retry > 20)) { + NVT_ERR("Check 0xAA (Write Enable) error!! status=0x%02X\n", buf[1]); + return -1; + } + } + + Flash_Address = i * FLASH_SECTOR_SIZE; + + // Sector Erase + buf[0] = 0x00; + buf[1] = 0x20; // Command : Sector Erase + buf[2] = ((Flash_Address >> 16) & 0xFF); + buf[3] = ((Flash_Address >> 8) & 0xFF); + buf[4] = (Flash_Address & 0xFF); + ret = CTP_I2C_WRITE(ts->client, I2C_HW_Address, buf, 5); + if (ret < 0) { + NVT_ERR("Sector Erase error!!(%d,%d)\n", ret, i); + return ret; + } + // Check 0xAA (Sector Erase) + retry = 0; + while (1) { + msleep(1); + buf[0] = 0x00; + buf[1] = 0x00; + ret = CTP_I2C_READ(ts->client, I2C_HW_Address, buf, 2); + if (ret < 0) { + NVT_ERR("Check 0xAA (Sector Erase) error!!(%d,%d)\n", ret, i); + return ret; + } + if (buf[1] == 0xAA) { + break; + } + retry++; + if (unlikely(retry > 20)) { + NVT_ERR("Check 0xAA (Sector Erase) failed, buf[1]=0x%02X, retry=%d\n", buf[1], retry); + return -1; + } + } + + // Read Status + retry = 0; + while (1) { + msleep(5); + buf[0] = 0x00; + buf[1] = 0x05; + ret = CTP_I2C_WRITE(ts->client, I2C_HW_Address, buf, 2); + if (ret < 0) { + NVT_ERR("Read Status error!!(%d,%d)\n", ret, i); + return ret; + } + + // Check 0xAA (Read Status) + buf[0] = 0x00; + buf[1] = 0x00; + buf[2] = 0x00; + ret = CTP_I2C_READ(ts->client, I2C_HW_Address, buf, 3); + if (ret < 0) { + NVT_ERR("Check 0xAA (Read Status) error!!(%d,%d)\n", ret, i); + return ret; + } + if ((buf[1] == 0xAA) && (buf[2] == 0x00)) { + break; + } + retry++; + if (unlikely(retry > 100)) { + NVT_ERR("Check 0xAA (Read Status) failed, buf[1]=0x%02X, buf[2]=0x%02X, retry=%d\n", buf[1], buf[2], retry); + return -1; + } + } + } + + NVT_LOG("Erase OK \n"); + return 0; +} + +/******************************************************* +Description: + Novatek touchscreen write flash sectors function. + +return: + Executive outcomes. 0---succeed. negative---failed. +*******************************************************/ +int32_t Write_Flash(void) +{ + uint8_t buf[64] = {0}; + uint32_t XDATA_Addr = ts->mmap->RW_FLASH_DATA_ADDR; + uint32_t Flash_Address = 0; + int32_t i = 0, j = 0, k = 0; + uint8_t tmpvalue = 0; + int32_t count = 0; + int32_t ret = 0; + int32_t retry = 0; + int32_t percent = 0; + int32_t previous_percent = -1; + + // change I2C buffer index + ret = nvt_set_page(I2C_BLDR_Address, XDATA_Addr); + if (ret < 0) { + NVT_ERR("change I2C buffer index error!!(%d)\n", ret); + return ret; + } + + if (fw_need_write_size % 256) + count = fw_need_write_size / 256 + 1; + else + count = fw_need_write_size / 256; + + for (i = 0; i < count; i++) { + Flash_Address = i * 256; + + // Write Enable + buf[0] = 0x00; + buf[1] = 0x06; + ret = CTP_I2C_WRITE(ts->client, I2C_HW_Address, buf, 2); + if (ret < 0) { + NVT_ERR("Write Enable error!!(%d)\n", ret); + return ret; + } + // Check 0xAA (Write Enable) + retry = 0; + while (1) { + udelay(100); + buf[0] = 0x00; + buf[1] = 0x00; + ret = CTP_I2C_READ(ts->client, I2C_HW_Address, buf, 2); + if (ret < 0) { + NVT_ERR("Check 0xAA (Write Enable) error!!(%d,%d)\n", ret, i); + return ret; + } + if (buf[1] == 0xAA) { + break; + } + retry++; + if (unlikely(retry > 20)) { + NVT_ERR("Check 0xAA (Write Enable) error!! status=0x%02X\n", buf[1]); + return -1; + } + } + + // Write Page : 256 bytes + for (j = 0; j < min(fw_need_write_size - i * 256, (size_t)256); j += 32) { + buf[0] = (XDATA_Addr + j) & 0xFF; + for (k = 0; k < 32; k++) { + buf[1 + k] = fw_entry->data[Flash_Address + j + k]; + } + ret = CTP_I2C_WRITE(ts->client, I2C_BLDR_Address, buf, 33); + if (ret < 0) { + NVT_ERR("Write Page error!!(%d), j=%d\n", ret, j); + return ret; + } + } + if (fw_need_write_size - Flash_Address >= 256) + tmpvalue=(Flash_Address >> 16) + ((Flash_Address >> 8) & 0xFF) + (Flash_Address & 0xFF) + 0x00 + (255); + else + tmpvalue=(Flash_Address >> 16) + ((Flash_Address >> 8) & 0xFF) + (Flash_Address & 0xFF) + 0x00 + (fw_need_write_size - Flash_Address - 1); + + for (k = 0; k < min(fw_need_write_size - Flash_Address, (size_t)256); k++) + tmpvalue += fw_entry->data[Flash_Address + k]; + + tmpvalue = 255 - tmpvalue + 1; + + // Page Program + buf[0] = 0x00; + buf[1] = 0x02; + buf[2] = ((Flash_Address >> 16) & 0xFF); + buf[3] = ((Flash_Address >> 8) & 0xFF); + buf[4] = (Flash_Address & 0xFF); + buf[5] = 0x00; + buf[6] = min(fw_need_write_size - Flash_Address, (size_t)256) - 1; + buf[7] = tmpvalue; + ret = CTP_I2C_WRITE(ts->client, I2C_HW_Address, buf, 8); + if (ret < 0) { + NVT_ERR("Page Program error!!(%d), i=%d\n", ret, i); + return ret; + } + // Check 0xAA (Page Program) + retry = 0; + while (1) { + msleep(1); + buf[0] = 0x00; + buf[1] = 0x00; + ret = CTP_I2C_READ(ts->client, I2C_HW_Address, buf, 2); + if (ret < 0) { + NVT_ERR("Page Program error!!(%d)\n", ret); + return ret; + } + if (buf[1] == 0xAA || buf[1] == 0xEA) { + break; + } + retry++; + if (unlikely(retry > 20)) { + NVT_ERR("Check 0xAA (Page Program) failed, buf[1]=0x%02X, retry=%d\n", buf[1], retry); + return -1; + } + } + if (buf[1] == 0xEA) { + NVT_ERR("Page Program error!! i=%d\n", i); + return -3; + } + + // Read Status + retry = 0; + while (1) { + msleep(5); + buf[0] = 0x00; + buf[1] = 0x05; + ret = CTP_I2C_WRITE(ts->client, I2C_HW_Address, buf, 2); + if (ret < 0) { + NVT_ERR("Read Status error!!(%d)\n", ret); + return ret; + } + + // Check 0xAA (Read Status) + buf[0] = 0x00; + buf[1] = 0x00; + buf[2] = 0x00; + ret = CTP_I2C_READ(ts->client, I2C_HW_Address, buf, 3); + if (ret < 0) { + NVT_ERR("Check 0xAA (Read Status) error!!(%d)\n", ret); + return ret; + } + if (((buf[1] == 0xAA) && (buf[2] == 0x00)) || (buf[1] == 0xEA)) { + break; + } + retry++; + if (unlikely(retry > 100)) { + NVT_ERR("Check 0xAA (Read Status) failed, buf[1]=0x%02X, buf[2]=0x%02X, retry=%d\n", buf[1], buf[2], retry); + return -1; + } + } + if (buf[1] == 0xEA) { + NVT_ERR("Page Program error!! i=%d\n", i); + return -4; + } + + percent = ((i + 1) * 100) / count; + if (((percent % 10) == 0) && (percent != previous_percent)) { + NVT_LOG("Programming...%2d%%\n", percent); + previous_percent = percent; + } + } + + NVT_LOG("Program OK \n"); + return 0; +} + +/******************************************************* +Description: + Novatek touchscreen verify checksum of written + flash function. + +return: + Executive outcomes. 0---succeed. negative---failed. +*******************************************************/ +int32_t Verify_Flash(void) +{ + uint8_t buf[64] = {0}; + uint32_t XDATA_Addr = ts->mmap->READ_FLASH_CHECKSUM_ADDR; + int32_t ret = 0; + int32_t i = 0; + int32_t k = 0; + uint16_t WR_Filechksum[BLOCK_64KB_NUM] = {0}; + uint16_t RD_Filechksum[BLOCK_64KB_NUM] = {0}; + size_t len_in_blk = 0; + int32_t retry = 0; + + for (i = 0; i < BLOCK_64KB_NUM; i++) { + if (fw_need_write_size > (i * SIZE_64KB)) { + // Calculate WR_Filechksum of each 64KB block + len_in_blk = min(fw_need_write_size - i * SIZE_64KB, (size_t)SIZE_64KB); + WR_Filechksum[i] = i + 0x00 + 0x00 + (((len_in_blk - 1) >> 8) & 0xFF) + ((len_in_blk - 1) & 0xFF); + for (k = 0; k < len_in_blk; k++) { + WR_Filechksum[i] += fw_entry->data[k + i * SIZE_64KB]; + } + WR_Filechksum[i] = 65535 - WR_Filechksum[i] + 1; + + // Fast Read Command + buf[0] = 0x00; + buf[1] = 0x07; + buf[2] = i; + buf[3] = 0x00; + buf[4] = 0x00; + buf[5] = ((len_in_blk - 1) >> 8) & 0xFF; + buf[6] = (len_in_blk - 1) & 0xFF; + ret = CTP_I2C_WRITE(ts->client, I2C_HW_Address, buf, 7); + if (ret < 0) { + NVT_ERR("Fast Read Command error!!(%d)\n", ret); + return ret; + } + // Check 0xAA (Fast Read Command) + retry = 0; + while (1) { + msleep(80); + buf[0] = 0x00; + buf[1] = 0x00; + ret = CTP_I2C_READ(ts->client, I2C_HW_Address, buf, 2); + if (ret < 0) { + NVT_ERR("Check 0xAA (Fast Read Command) error!!(%d)\n", ret); + return ret; + } + if (buf[1] == 0xAA) { + break; + } + retry++; + if (unlikely(retry > 5)) { + NVT_ERR("Check 0xAA (Fast Read Command) failed, buf[1]=0x%02X, retry=%d\n", buf[1], retry); + return -1; + } + } + // Read Checksum (write addr high byte & middle byte) + ret = nvt_set_page(I2C_BLDR_Address, XDATA_Addr); + if (ret < 0) { + NVT_ERR("Read Checksum (write addr high byte & middle byte) error!!(%d)\n", ret); + return ret; + } + // Read Checksum + buf[0] = (XDATA_Addr) & 0xFF; + buf[1] = 0x00; + buf[2] = 0x00; + ret = CTP_I2C_READ(ts->client, I2C_BLDR_Address, buf, 3); + if (ret < 0) { + NVT_ERR("Read Checksum error!!(%d)\n", ret); + return ret; + } + + RD_Filechksum[i] = (uint16_t)((buf[2] << 8) | buf[1]); + if (WR_Filechksum[i] != RD_Filechksum[i]) { + NVT_ERR("Verify Fail%d!!\n", i); + NVT_ERR("RD_Filechksum[%d]=0x%04X, WR_Filechksum[%d]=0x%04X\n", i, RD_Filechksum[i], i, WR_Filechksum[i]); + return -1; + } + } + } + + NVT_LOG("Verify OK \n"); + return 0; +} + +/******************************************************* +Description: + Novatek touchscreen update firmware function. + +return: + Executive outcomes. 0---succeed. negative---failed. +*******************************************************/ +int32_t Update_Firmware(void) +{ + int32_t ret = 0; + + //---Stop CRC check to prevent IC auto reboot--- + nvt_stop_crc_reboot(); + + // Step 1 : initial bootloader + ret = Init_BootLoader(); + if (ret) { + return ret; + } + + // Step 2 : Resume PD + ret = Resume_PD(); + if (ret) { + return ret; + } + + // Step 3 : Erase + ret = Erase_Flash(); + if (ret) { + return ret; + } + + // Step 4 : Program + ret = Write_Flash(); + if (ret) { + return ret; + } + + // Step 5 : Verify + ret = Verify_Flash(); + if (ret) { + return ret; + } + + //Step 6 : Bootloader Reset + nvt_bootloader_reset(); + nvt_check_fw_reset_state(RESET_STATE_INIT); + nvt_get_fw_info(); + + return ret; +} + +/******************************************************* +Description: + Novatek touchscreen check flash end flag function. + +return: + Executive outcomes. 0---succeed. 1,negative---failed. +*******************************************************/ +int32_t nvt_check_flash_end_flag(void) +{ + uint8_t buf[8] = {0}; + uint8_t nvt_end_flag[NVT_FLASH_END_FLAG_LEN + 1] = {0}; + int32_t ret = 0; + + // Step 1 : initial bootloader + ret = Init_BootLoader(); + if (ret) { + return ret; + } + + // Step 2 : Resume PD + ret = Resume_PD(); + if (ret) { + return ret; + } + + // Step 3 : unlock + buf[0] = 0x00; + buf[1] = 0x35; + ret = CTP_I2C_WRITE(ts->client, I2C_HW_Address, buf, 2); + if (ret < 0) { + NVT_ERR("write unlock error!!(%d)\n", ret); + return ret; + } + msleep(10); + + //Step 4 : Flash Read Command + buf[0] = 0x00; + buf[1] = 0x03; + buf[2] = (NVT_FLASH_END_FLAG_ADDR >> 16) & 0xFF; //Addr_H + buf[3] = (NVT_FLASH_END_FLAG_ADDR >> 8) & 0xFF; //Addr_M + buf[4] = NVT_FLASH_END_FLAG_ADDR & 0xFF; //Addr_L + buf[5] = (NVT_FLASH_END_FLAG_LEN >> 8) & 0xFF; //Len_H + buf[6] = NVT_FLASH_END_FLAG_LEN & 0xFF; //Len_L + ret = CTP_I2C_WRITE(ts->client, I2C_HW_Address, buf, 7); + if (ret < 0) { + NVT_ERR("write Read Command error!!(%d)\n", ret); + return ret; + } + msleep(10); + + // Check 0xAA (Read Command) + buf[0] = 0x00; + buf[1] = 0x00; + ret = CTP_I2C_READ(ts->client, I2C_HW_Address, buf, 2); + if (ret < 0) { + NVT_ERR("Check 0xAA (Read Command) error!!(%d)\n", ret); + return ret; + } + if (buf[1] != 0xAA) { + NVT_ERR("Check 0xAA (Read Command) error!! status=0x%02X\n", buf[1]); + return -1; + } + + msleep(10); + + //Step 5 : Read Flash Data + ret = nvt_set_page(I2C_BLDR_Address, ts->mmap->READ_FLASH_CHECKSUM_ADDR); + if (ret < 0) { + NVT_ERR("change index error!! (%d)\n", ret); + return ret; + } + msleep(10); + + // Read Back + buf[0] = ts->mmap->READ_FLASH_CHECKSUM_ADDR & 0xFF; + ret = CTP_I2C_READ(ts->client, I2C_BLDR_Address, buf, 6); + if (ret < 0) { + NVT_ERR("Read Back error!! (%d)\n", ret); + return ret; + } + + //buf[3:5] => NVT End Flag + strlcpy(nvt_end_flag, &buf[3], sizeof(nvt_end_flag)); + NVT_LOG("nvt_end_flag=%s (%02X %02X %02X)\n", nvt_end_flag, buf[3], buf[4], buf[5]); + + if ((memcmp(nvt_end_flag, "NVT", NVT_FLASH_END_FLAG_LEN) == 0) || + (memcmp(nvt_end_flag, "MOD", NVT_FLASH_END_FLAG_LEN) == 0)) { + return 0; + } else { + NVT_ERR("\"NVT\" end flag not found!\n"); + return 1; + } +} + +/******************************************************* +Description: + Novatek touchscreen update firmware when booting + function. + +return: + n.a. +*******************************************************/ +void Boot_Update_Firmware(struct work_struct *work) +{ + int32_t ret = 0; + + char firmware_name[256] = ""; + + snprintf(firmware_name, sizeof(firmware_name), + BOOT_UPDATE_FIRMWARE_NAME); + + // request bin file in "/etc/firmware" + ret = update_firmware_request(firmware_name); + if (ret) { + NVT_ERR("update_firmware_request failed. (%d)\n", ret); + return; + } + + mutex_lock(&ts->lock); + +#if NVT_TOUCH_ESD_PROTECT + nvt_esd_check_enable(false); +#endif /* #if NVT_TOUCH_ESD_PROTECT */ + + nvt_sw_reset_idle(); + + ret = Check_CheckSum(); + + if (ret < 0) { // read firmware checksum failed + NVT_ERR("read firmware checksum failed\n"); + Update_Firmware(); + } else if ((ret == 0) && (Check_FW_Ver() == 0)) { // (fw checksum not match) && (bin fw version >= ic fw version) + NVT_LOG("firmware version not match\n"); + Update_Firmware(); + } else if (nvt_check_flash_end_flag()) { + NVT_LOG("check flash end flag failed\n"); + Update_Firmware(); + } else { + // Bootloader Reset + nvt_bootloader_reset(); + ret = nvt_check_fw_reset_state(RESET_STATE_INIT); + if (ret) { + NVT_LOG("check fw reset state failed\n"); + Update_Firmware(); + } + } + + mutex_unlock(&ts->lock); + + update_firmware_release(); +} +#endif /* BOOT_UPDATE_FIRMWARE */ + +#else /* NT36XXX_SPI */ + +#include +#include + +#include "nt36xxx.h" + +#if NVT_SPI_BOOT_UPDATE_FIRMWARE + +#define SIZE_4KB 4096 +#define FLASH_SECTOR_SIZE SIZE_4KB +#define FW_BIN_VER_OFFSET (nvt_spi_fw_need_write_size - SIZE_4KB) +#define FW_BIN_VER_BAR_OFFSET (FW_BIN_VER_OFFSET + 1) +#define NVT_FLASH_END_FLAG_LEN 3 +#define NVT_FLASH_END_FLAG_ADDR (nvt_spi_fw_need_write_size - NVT_FLASH_END_FLAG_LEN) + +#define NVT_DUMP_PARTITION (0) +#define NVT_DUMP_PARTITION_LEN (1024) +#define NVT_DUMP_PARTITION_PATH "/data/local/tmp" + +static ktime_t nvt_spi_start, nvt_spi_end; +static const struct firmware *nvt_spi_fw_entry; +static size_t nvt_spi_fw_need_write_size; +static uint8_t *nvt_spi_fwbuf; + +struct nvt_spi_bin_map_t { + char name[12]; + uint32_t BIN_addr; + uint32_t SRAM_addr; + uint32_t size; + uint32_t crc; +}; + +static struct nvt_spi_bin_map_t *nvt_spi_bin_map; + +static int32_t nvt_spi_get_fw_need_write_size(const struct firmware *fw_entry) +{ + int32_t i = 0; + int32_t total_sectors_to_check = 0; + + total_sectors_to_check = fw_entry->size / FLASH_SECTOR_SIZE; + /* printk("total_sectors_to_check = %d\n", total_sectors_to_check); */ + + for (i = total_sectors_to_check; i > 0; i--) { + // printk("current end flag address checked = 0x%X\n", + // i * FLASH_SECTOR_SIZE - NVT_FLASH_END_FLAG_LEN); + /* check if there is end flag "NVT" at the end of this sector */ + if (memcmp(&fw_entry->data[i * FLASH_SECTOR_SIZE - NVT_FLASH_END_FLAG_LEN], + "NVT", NVT_FLASH_END_FLAG_LEN) == 0) { + nvt_spi_fw_need_write_size = i * FLASH_SECTOR_SIZE; + NVT_LOG("fw_need_write_size = %zu(0x%zx), NVT end flag\n", + nvt_spi_fw_need_write_size, nvt_spi_fw_need_write_size); + return 0; + } + + /* check if there is end flag "MOD" at the end of this sector */ + if (memcmp(&fw_entry->data[i * FLASH_SECTOR_SIZE - NVT_FLASH_END_FLAG_LEN], + "MOD", NVT_FLASH_END_FLAG_LEN) == 0) { + nvt_spi_fw_need_write_size = i * FLASH_SECTOR_SIZE; + NVT_LOG("fw_need_write_size = %zu(0x%zx), MOD end flag\n", + nvt_spi_fw_need_write_size, nvt_spi_fw_need_write_size); + return 0; + } + } + + NVT_ERR("end flag \"NVT\" \"MOD\" not found!\n"); + return -EINVAL; +} + +/* + ******************************************************* + * Description: + * Novatek touchscreen init variable and allocate buffer + * for download firmware function. + * + * return: + * n.a. + ****************************************************** + */ +static int32_t nvt_spi_download_init(void) +{ + uint8_t *buf; + /* allocate buffer for transfer firmware */ + //NVT_LOG("NVT_TRANSFER_LEN = 0x%06X\n", NVT_SPI_TRANSFER_LEN); + + if (nvt_spi_fwbuf == NULL) { + buf = kzalloc((NVT_SPI_TRANSFER_LEN + 1 + NVT_SPI_DUMMY_BYTES), GFP_KERNEL); + if (buf == NULL) { + NVT_ERR("kzalloc for fwbuf failed!\n"); + return -ENOMEM; + } + nvt_spi_fwbuf = buf; + } + + return 0; +} + +/* + ****************************************************** + * Description: + * Novatek touchscreen checksum function. Calculate bin + * file checksum for comparison. + * + * return: + * n.a. + ****************************************************** + */ +static uint32_t CheckSum(const u8 *data, size_t len) +{ + uint32_t i = 0; + uint32_t checksum = 0; + + for (i = 0 ; i < len + 1; i++) + checksum += data[i]; + + checksum += len; + checksum = ~checksum + 1; + + return checksum; +} + +static uint32_t byte_to_word(const uint8_t *data) +{ + return data[0] + (data[1] << 8) + (data[2] << 16) + (data[3] << 24); +} + +/* + ****************************************************** + * Description: + * Novatek touchscreen parsing bin header function. + * + * return: + * n.a. + ****************************************************** + */ +static uint32_t nvt_spi_partition; +static uint8_t nvt_spi_ilm_dlm_num = 2; +static uint8_t nvt_spi_cascade_2nd_header_info; +static int32_t nvt_spi_bin_header_parser(const u8 *fwdata, size_t fwsize) +{ + uint32_t list = 0; + uint32_t pos = 0x00; + uint32_t end = 0x00; + uint8_t info_sec_num = 0; + uint8_t ovly_sec_num = 0; + uint8_t ovly_info = 0; + uint8_t find_bin_header = 0; + struct nvt_spi_bin_map_t *bin_map = NULL; + struct nvt_spi_data_t *ts = nvt_spi_data; + + /* Find the header size */ + end = fwdata[0] + (fwdata[1] << 8) + (fwdata[2] << 16) + (fwdata[3] << 24); + + /* check cascade next header */ + nvt_spi_cascade_2nd_header_info = (fwdata[0x20] & 0x02) >> 1; + NVT_LOG("cascade_2nd_header_info = %d\n", nvt_spi_cascade_2nd_header_info); + + if (nvt_spi_cascade_2nd_header_info) { + pos = 0x30; // info section start at 0x30 offset + while (pos < (end / 2)) { + info_sec_num++; + pos += 0x10; /* each header info is 16 bytes */ + } + + info_sec_num = info_sec_num + 1; //next header section + } else { + pos = 0x30; // info section start at 0x30 offset + while (pos < end) { + info_sec_num++; + pos += 0x10; /* each header info is 16 bytes */ + } + } + + /* + * Find the DLM OVLY section + * [0:3] Overlay Section Number + * [4] Overlay Info + */ + ovly_info = (fwdata[0x28] & 0x10) >> 4; + ovly_sec_num = (ovly_info) ? (fwdata[0x28] & 0x0F) : 0; + + /* + * calculate all partition number + * ilm_dlm_num (ILM & DLM) + ovly_sec_num + info_sec_num + */ + nvt_spi_partition = nvt_spi_ilm_dlm_num + ovly_sec_num + info_sec_num; + NVT_LOG("ovly_info=%d, ilm_dlm_num=%d, ovly_sec_num=%d, info_sec_num=%d, partition=%d\n", + ovly_info, nvt_spi_ilm_dlm_num, ovly_sec_num, info_sec_num, nvt_spi_partition); + + + /* allocated memory for header info */ + bin_map = kzalloc((nvt_spi_partition+1) * sizeof(struct nvt_spi_bin_map_t), GFP_KERNEL); + if (bin_map == NULL) { + NVT_ERR("kzalloc for bin_map failed!\n"); + return -ENOMEM; + } + nvt_spi_bin_map = bin_map; + + for (list = 0; list < nvt_spi_partition; list++) { + /* + * [1] parsing ILM & DLM header info + * BIN_addr : SRAM_addr : size (12-bytes) + * crc located at 0x18 & 0x1C + */ + if (list < nvt_spi_ilm_dlm_num) { + bin_map[list].BIN_addr = byte_to_word(&fwdata[0 + list * 12]); + bin_map[list].SRAM_addr = byte_to_word(&fwdata[4 + list * 12]); + bin_map[list].size = byte_to_word(&fwdata[8 + list * 12]); + if (ts->hw_crc) + bin_map[list].crc = byte_to_word(&fwdata[0x18 + list * 4]); + else { //ts->hw_crc + if ((bin_map[list].BIN_addr + bin_map[list].size) < fwsize) + bin_map[list].crc = CheckSum( + &fwdata[bin_map[list].BIN_addr], + bin_map[list].size); + else { + NVT_ERR("access range (0x%08X to 0x%08X) is too large!\n", + bin_map[list].BIN_addr, + bin_map[list].BIN_addr + bin_map[list].size); + return -EINVAL; + } + } //ts->hw_crc + if (list == 0) + snprintf(bin_map[list].name, sizeof(bin_map[list].name), "ILM"); + else if (list == 1) + snprintf(bin_map[list].name, sizeof(bin_map[list].name), "DLM"); + } + + /* + * [2] parsing others header info + * SRAM_addr : size : BIN_addr : crc (16-bytes) + */ + if ((list >= nvt_spi_ilm_dlm_num) + && (list < (nvt_spi_ilm_dlm_num + info_sec_num))) { + + if (find_bin_header == 0) { + /* others partition located at 0x30 offset */ + pos = 0x30 + (0x10 * (list - nvt_spi_ilm_dlm_num)); + } else if (find_bin_header && nvt_spi_cascade_2nd_header_info) { + /* cascade 2nd header info */ + pos = end - 0x10; + } + + bin_map[list].SRAM_addr = byte_to_word(&fwdata[pos]); + bin_map[list].size = byte_to_word(&fwdata[pos + 4]); + bin_map[list].BIN_addr = byte_to_word(&fwdata[pos + 8]); + if (ts->hw_crc) + bin_map[list].crc = byte_to_word(&fwdata[pos + 12]); + else { //ts->hw_crc + if ((bin_map[list].BIN_addr + bin_map[list].size) < fwsize) + bin_map[list].crc = CheckSum( + &fwdata[bin_map[list].BIN_addr], + bin_map[list].size); + else { + NVT_ERR("access range (0x%08X to 0x%08X) is too large!\n", + bin_map[list].BIN_addr, + bin_map[list].BIN_addr + bin_map[list].size); + return -EINVAL; + } + } //ts->hw_crc + /* detect header end to protect parser function */ + if ((bin_map[list].BIN_addr < end) && (bin_map[list].size != 0)) { + snprintf(bin_map[list].name, sizeof(bin_map[list].name), + "Header"); + find_bin_header = 1; + } else + snprintf(bin_map[list].name, sizeof(bin_map[list].name), + "Info-%d", (list - nvt_spi_ilm_dlm_num)); + } + + /* + * [3] parsing overlay section header info + * SRAM_addr : size : BIN_addr : crc (16-bytes) + */ + if (list >= (nvt_spi_ilm_dlm_num + info_sec_num)) { + /* overlay info located at DLM (list = 1) start addr */ + pos = bin_map[1].BIN_addr; + pos += (0x10 * (list - nvt_spi_ilm_dlm_num - info_sec_num)); + + bin_map[list].SRAM_addr = byte_to_word(&fwdata[pos]); + bin_map[list].size = byte_to_word(&fwdata[pos + 4]); + bin_map[list].BIN_addr = byte_to_word(&fwdata[pos + 8]); + if (ts->hw_crc) + bin_map[list].crc = byte_to_word(&fwdata[pos + 12]); + else { //ts->hw_crc + if ((bin_map[list].BIN_addr + bin_map[list].size) < fwsize) + bin_map[list].crc = CheckSum( + &fwdata[bin_map[list].BIN_addr], + bin_map[list].size); + else { + NVT_ERR("access range (0x%08X to 0x%08X) is too large!\n", + bin_map[list].BIN_addr, + bin_map[list].BIN_addr + bin_map[list].size); + return -EINVAL; + } + } //ts->hw_crc + snprintf(bin_map[list].name, sizeof(bin_map[list].name), + "Overlay-%d", + (list - nvt_spi_ilm_dlm_num - info_sec_num)); + } + + /* BIN size error detect */ + if ((bin_map[list].BIN_addr + bin_map[list].size) > fwsize) { + NVT_ERR("access range (0x%08X to 0x%08X) is larger than bin size!\n", + bin_map[list].BIN_addr, + bin_map[list].BIN_addr + bin_map[list].size); + return -EINVAL; + } + +// NVT_LOG("[%d][%s] SRAM (0x%08X), SIZE (0x%08X), BIN (0x%08X), CRC (0x%08X)\n", +// list, bin_map[list].name, +// bin_map[list].SRAM_addr, bin_map[list].size, +// bin_map[list].BIN_addr, bin_map[list].crc); + } + + return 0; +} + +/* + ******************************************************* + * Description: + * Novatek touchscreen release update firmware function. + * + * return: + * n.a. + ****************************************************** + */ +static void nvt_spi_update_firmware_release(void) +{ + if (nvt_spi_fw_entry) + release_firmware(nvt_spi_fw_entry); + + nvt_spi_fw_entry = NULL; +} + +/* + ****************************************************** + * Description: + * Novatek touchscreen request update firmware function. + * + * return: + * Executive outcomes. 0---succeed. -1,-22---failed. + ****************************************************** + */ +static int32_t nvt_spi_update_firmware_request(char *filename) +{ + uint8_t retry = 0; + int32_t ret = 0; + struct nvt_spi_data_t *ts = nvt_spi_data; + uint8_t ver; + + if (filename == NULL) + return -ENOENT; + + while (1) { + NVT_LOG("filename is %s\n", filename); + + ret = request_firmware(&nvt_spi_fw_entry, filename, &ts->client->dev); + if (ret) { + NVT_ERR("firmware load failed, ret=%d\n", ret); + goto request_fail; + } + + // check FW need to write size + if (nvt_spi_get_fw_need_write_size(nvt_spi_fw_entry)) { + NVT_ERR("get fw need to write size fail!\n"); + ret = -EINVAL; + goto invalid; + } + + // check if FW version add FW version bar equals 0xFF + ver = *(nvt_spi_fw_entry->data + FW_BIN_VER_OFFSET); + if (ver + *(nvt_spi_fw_entry->data + FW_BIN_VER_BAR_OFFSET) != 0xFF) { + NVT_ERR("bin file FW_VER + FW_VER_BAR should be 0xFF!\n"); + NVT_ERR("FW_VER=0x%02X, FW_VER_BAR=0x%02X\n", + *(nvt_spi_fw_entry->data+FW_BIN_VER_OFFSET), + *(nvt_spi_fw_entry->data+FW_BIN_VER_BAR_OFFSET)); + ret = -ENOEXEC; + goto invalid; + } + + /* BIN Header Parser */ + ret = nvt_spi_bin_header_parser(nvt_spi_fw_entry->data, nvt_spi_fw_entry->size); + if (ret) { + NVT_ERR("bin header parser failed\n"); + goto invalid; + } else + break; + +invalid: + nvt_spi_update_firmware_release(); + if (!IS_ERR_OR_NULL(nvt_spi_bin_map)) { + kfree(nvt_spi_bin_map); + nvt_spi_bin_map = NULL; + } + +request_fail: + retry++; + if (unlikely(retry > 2)) { + NVT_ERR("error, retry=%d\n", retry); + break; + } + } + + return ret; +} + +/* + ******************************************************* + * Description: + * Novatek touchscreen write data to sram function. + * + * - fwdata : The buffer is written + * - SRAM_addr: The sram destination address + * - size : Number of data bytes in @fwdata being written + * - BIN_addr : The transferred data offset of @fwdata + * + * return: + * Executive outcomes. 0---succeed. else---fail. + ******************************************************* + */ +static int32_t nvt_spi_write_sram(const u8 *fwdata, + uint32_t SRAM_addr, uint32_t size, uint32_t BIN_addr) +{ + int32_t ret = 0; + uint32_t i = 0; + uint16_t len = 0; + int32_t count = 0; + + if (size % NVT_SPI_TRANSFER_LEN) + count = (size / NVT_SPI_TRANSFER_LEN) + 1; + else + count = (size / NVT_SPI_TRANSFER_LEN); + + for (i = 0 ; i < count ; i++) { + len = (size < NVT_SPI_TRANSFER_LEN) ? size : NVT_SPI_TRANSFER_LEN; + + //---set xdata index to start address of SRAM--- + ret = nvt_spi_set_page(SRAM_addr); + if (ret) { + NVT_ERR("set page failed, ret = %d\n", ret); + return ret; + } + + //---write data into SRAM--- + nvt_spi_fwbuf[0] = SRAM_addr & 0x7F; //offset + memcpy(nvt_spi_fwbuf+1, &fwdata[BIN_addr], len); //payload + ret = nvt_spi_write(nvt_spi_fwbuf, len+1); + if (ret) { + NVT_ERR("write to sram failed, ret = %d\n", ret); + return ret; + } + + SRAM_addr += NVT_SPI_TRANSFER_LEN; + BIN_addr += NVT_SPI_TRANSFER_LEN; + size -= NVT_SPI_TRANSFER_LEN; + } + + return ret; +} + +/* + ******************************************************* + * Description: + * Novatek touchscreen nvt_spi_write_firmware function to write + * firmware into each partition. + * + * return: + * n.a. + ****************************************************** + */ +static int32_t nvt_spi_write_firmware(const u8 *fwdata, size_t fwsize) +{ + uint32_t list = 0; + char *name; + uint32_t BIN_addr, SRAM_addr, size; + int32_t ret = 0; + + memset(nvt_spi_fwbuf, 0, (NVT_SPI_TRANSFER_LEN+1)); + + for (list = 0; list < nvt_spi_partition; list++) { + /* initialize variable */ + SRAM_addr = nvt_spi_bin_map[list].SRAM_addr; + size = nvt_spi_bin_map[list].size; + BIN_addr = nvt_spi_bin_map[list].BIN_addr; + name = nvt_spi_bin_map[list].name; + +// NVT_LOG("[%d][%s] SRAM (0x%08X), SIZE (0x%08X), BIN (0x%08X)\n", +// list, name, SRAM_addr, size, BIN_addr); + + /* Check data size */ + if ((BIN_addr + size) > fwsize) { + NVT_ERR("access range (0x%08X to 0x%08X) is larger than bin size!\n", + BIN_addr, BIN_addr + size); + ret = -EINVAL; + goto out; + } + + /* ignore reserved partition (Reserved Partition size is zero) */ + if (!size) + continue; + else + size = size + 1; + + /* write data to SRAM */ + ret = nvt_spi_write_sram(fwdata, SRAM_addr, size, BIN_addr); + if (ret) { + NVT_ERR("sram program failed, ret = %d\n", ret); + goto out; + } + } + +out: + return ret; +} + +/* + ******************************************************* + * Description: + * Novatek touchscreen check checksum function. + * This function will compare file checksum and fw checksum. + * + * return: + * n.a. + ******************************************************* + */ +static int32_t nvt_spi_check_fw_checksum(void) +{ + uint32_t fw_checksum = 0; + uint32_t len = nvt_spi_partition * 4; + uint32_t list = 0; + int32_t ret = 0; + struct nvt_spi_data_t *ts = nvt_spi_data; + + memset(nvt_spi_fwbuf, 0, (len+1)); + + //---set xdata index to checksum--- + nvt_spi_set_page(ts->mmap->R_ILM_CHECKSUM_ADDR); + + /* read checksum */ + nvt_spi_fwbuf[0] = (ts->mmap->R_ILM_CHECKSUM_ADDR) & 0x7F; + ret = nvt_spi_read(nvt_spi_fwbuf, len+1); + if (ret) { + NVT_ERR("Read fw checksum failed\n"); + return ret; + } + + /* + * Compare each checksum from fw + * ILM + DLM + Overlay + Info + * nvt_spi_ilm_dlm_num (ILM & DLM) + ovly_sec_num + info_sec_num + */ + for (list = 0; list < nvt_spi_partition; list++) { + fw_checksum = byte_to_word(&nvt_spi_fwbuf[1+list*4]); + + /* ignore reserved partition (Reserved Partition size is zero) */ + if (!nvt_spi_bin_map[list].size) + continue; + + if (nvt_spi_bin_map[list].crc != fw_checksum) { + NVT_ERR("[%d] BIN_checksum=0x%08X, FW_checksum=0x%08X\n", + list, nvt_spi_bin_map[list].crc, fw_checksum); + ret = -EIO; + } + } + + return ret; +} + +/* + ******************************************************* + * Description: + * Novatek touchscreen set bootload crc reg bank function. + * This function will set hw crc reg before enable crc function. + * + * return: + * n.a. + ****************************************************** + */ +static void nvt_spi_set_bld_crc_bank(uint32_t DES_ADDR, uint32_t SRAM_ADDR, + uint32_t LENGTH_ADDR, uint32_t size, + uint32_t G_CHECKSUM_ADDR, uint32_t crc) +{ + struct nvt_spi_data_t *ts = nvt_spi_data; + + /* write destination address */ + nvt_spi_set_page(DES_ADDR); + nvt_spi_fwbuf[0] = DES_ADDR & 0x7F; + nvt_spi_fwbuf[1] = (SRAM_ADDR) & 0xFF; + nvt_spi_fwbuf[2] = (SRAM_ADDR >> 8) & 0xFF; + nvt_spi_fwbuf[3] = (SRAM_ADDR >> 16) & 0xFF; + nvt_spi_write(nvt_spi_fwbuf, 4); + + /* write length */ + //nvt_spi_set_page(LENGTH_ADDR); + nvt_spi_fwbuf[0] = LENGTH_ADDR & 0x7F; + nvt_spi_fwbuf[1] = (size) & 0xFF; + nvt_spi_fwbuf[2] = (size >> 8) & 0xFF; + nvt_spi_fwbuf[3] = (size >> 16) & 0x01; + if (ts->hw_crc == 1) + nvt_spi_write(nvt_spi_fwbuf, 3); + else if (ts->hw_crc > 1) + nvt_spi_write(nvt_spi_fwbuf, 4); + + /* write golden dlm checksum */ + //nvt_spi_set_page(G_CHECKSUM_ADDR); + nvt_spi_fwbuf[0] = G_CHECKSUM_ADDR & 0x7F; + nvt_spi_fwbuf[1] = (crc) & 0xFF; + nvt_spi_fwbuf[2] = (crc >> 8) & 0xFF; + nvt_spi_fwbuf[3] = (crc >> 16) & 0xFF; + nvt_spi_fwbuf[4] = (crc >> 24) & 0xFF; + nvt_spi_write(nvt_spi_fwbuf, 5); +} + +/* + ********************************************************* + * Description: + * Novatek touchscreen set BLD hw crc function. + * This function will set ILM and DLM crc information to register. + * + * return: + * n.a. + ********************************************************* + */ +static void nvt_spi_set_bld_hw_crc(void) +{ + struct nvt_spi_data_t *ts = nvt_spi_data; + + /* [0] ILM */ + /* write register bank */ + nvt_spi_set_bld_crc_bank(ts->mmap->ILM_DES_ADDR, nvt_spi_bin_map[0].SRAM_addr, + ts->mmap->ILM_LENGTH_ADDR, nvt_spi_bin_map[0].size, + ts->mmap->G_ILM_CHECKSUM_ADDR, nvt_spi_bin_map[0].crc); + + /* [1] DLM */ + /* write register bank */ + nvt_spi_set_bld_crc_bank(ts->mmap->DLM_DES_ADDR, nvt_spi_bin_map[1].SRAM_addr, + ts->mmap->DLM_LENGTH_ADDR, nvt_spi_bin_map[1].size, + ts->mmap->G_DLM_CHECKSUM_ADDR, nvt_spi_bin_map[1].crc); +} + +/* + ******************************************************* + * Description: + * Novatek touchscreen read BLD hw crc info function. + * This function will check crc results from register. + * + * return: + * n.a. + ****************************************************** + */ +static void nvt_spi_read_bld_hw_crc(void) +{ + uint8_t buf[8] = {0}; + uint32_t g_crc = 0, r_crc = 0; + struct nvt_spi_data_t *ts = nvt_spi_data; + + /* CRC Flag */ + nvt_spi_set_page(ts->mmap->BLD_ILM_DLM_CRC_ADDR); + buf[0] = ts->mmap->BLD_ILM_DLM_CRC_ADDR & 0x7F; + buf[1] = 0x00; + nvt_spi_read(buf, 2); + NVT_ERR("crc_done = %d, ilm_crc_flag = %d, dlm_crc_flag = %d\n", + (buf[1] >> 2) & 0x01, (buf[1] >> 0) & 0x01, (buf[1] >> 1) & 0x01); + + /* ILM CRC */ + nvt_spi_set_page(ts->mmap->G_ILM_CHECKSUM_ADDR); + buf[0] = ts->mmap->G_ILM_CHECKSUM_ADDR & 0x7F; + buf[1] = 0x00; + buf[2] = 0x00; + buf[3] = 0x00; + buf[4] = 0x00; + nvt_spi_read(buf, 5); + g_crc = buf[1] | (buf[2] << 8) | (buf[3] << 16) | (buf[4] << 24); + + nvt_spi_set_page(ts->mmap->R_ILM_CHECKSUM_ADDR); + buf[0] = ts->mmap->R_ILM_CHECKSUM_ADDR & 0x7F; + buf[1] = 0x00; + buf[2] = 0x00; + buf[3] = 0x00; + buf[4] = 0x00; + nvt_spi_read(buf, 5); + r_crc = buf[1] | (buf[2] << 8) | (buf[3] << 16) | (buf[4] << 24); + + NVT_ERR("ilm: bin crc = 0x%08X, golden = 0x%08X, result = 0x%08X\n", + nvt_spi_bin_map[0].crc, g_crc, r_crc); + + /* DLM CRC */ + nvt_spi_set_page(ts->mmap->G_DLM_CHECKSUM_ADDR); + buf[0] = ts->mmap->G_DLM_CHECKSUM_ADDR & 0x7F; + buf[1] = 0x00; + buf[2] = 0x00; + buf[3] = 0x00; + buf[4] = 0x00; + nvt_spi_read(buf, 5); + g_crc = buf[1] | (buf[2] << 8) | (buf[3] << 16) | (buf[4] << 24); + + nvt_spi_set_page(ts->mmap->R_DLM_CHECKSUM_ADDR); + buf[0] = ts->mmap->R_DLM_CHECKSUM_ADDR & 0x7F; + buf[1] = 0x00; + buf[2] = 0x00; + buf[3] = 0x00; + buf[4] = 0x00; + nvt_spi_read(buf, 5); + r_crc = buf[1] | (buf[2] << 8) | (buf[3] << 16) | (buf[4] << 24); + + NVT_ERR("dlm: bin crc = 0x%08X, golden = 0x%08X, result = 0x%08X\n", + nvt_spi_bin_map[1].crc, g_crc, r_crc); + +} + +/* + ****************************************************** + * Description: + * Novatek touchscreen Download_Firmware with HW CRC + * function. It's complete download firmware flow. + * + * return: + * Executive outcomes. 0---succeed. else---fail. + ****************************************************** + */ +static int32_t nvt_spi_download_firmware_hw_crc(void) +{ + uint8_t retry = 0; + int32_t ret = 0; + const struct firmware *fw = nvt_spi_fw_entry; + + nvt_spi_start = ktime_get(); + + while (1) { + /* bootloader reset to reset MCU */ + nvt_spi_bootloader_reset(); + + /* set ilm & dlm reg bank */ + nvt_spi_set_bld_hw_crc(); + + /* Start to write firmware process */ + if (nvt_spi_cascade_2nd_header_info) { + /* for cascade */ + nvt_spi_tx_auto_copy_mode(); + + ret = nvt_spi_write_firmware(fw->data, fw->size); + if (ret) { + NVT_ERR("Write_Firmware failed. (%d)\n", ret); + goto fail; + } + + ret = nvt_spi_check_spi_dma_tx_info(); + if (ret) { + NVT_ERR("spi dma tx info failed. (%d)\n", ret); + goto fail; + } + } else { + ret = nvt_spi_write_firmware(fw->data, fw->size); + if (ret) { + NVT_ERR("Write_Firmware failed. (%d)\n", ret); + goto fail; + } + } + +#if NVT_DUMP_PARTITION + ret = nvt_dump_partition(); + if (ret) + NVT_ERR("nvt_dump_partition failed, ret = %d\n", ret); +#endif + + /* enable hw bld crc function */ + nvt_spi_bld_crc_enable(); + + /* clear fw reset status & enable fw crc check */ + nvt_spi_fw_crc_enable(); + + /* Set Boot Ready Bit */ + nvt_spi_boot_ready(); + + ret = nvt_spi_check_fw_reset_state(NVT_SPI_RESET_STATE_INIT); + if (ret) { + NVT_ERR("nvt_check_fw_reset_state failed. (%d)\n", ret); + goto fail; + } else { + break; + } + +fail: + retry++; + if (unlikely(retry > 2)) { + NVT_ERR("error, retry=%d\n", retry); + nvt_spi_read_bld_hw_crc(); + break; + } + } + + nvt_spi_end = ktime_get(); + + return ret; +} + +/* + ******************************************************** + * Description: + * Novatek touchscreen Download_Firmware function. It's + * complete download firmware flow. + * + * return: + * n.a. + ****************************************************** + */ +static int32_t nvt_spi_download_firmware(void) +{ + uint32_t addr; + uint8_t retry = 0; + int32_t ret = 0; + struct nvt_spi_data_t *ts = nvt_spi_data; + + nvt_spi_start = ktime_get(); + + while (1) { + /* + * Send eng reset cmd before download FW + * Keep TP_RESX low when send eng reset cmd + */ +#if NVT_SPI_TOUCH_SUPPORT_HW_RST + gpio_set_value(ts->reset_gpio, 0); + mdelay(1); //wait 1ms +#endif + nvt_spi_eng_reset(); +#if NVT_SPI_TOUCH_SUPPORT_HW_RST + gpio_set_value(ts->reset_gpio, 1); + mdelay(10); //wait tRT2BRST after TP_RST +#endif + nvt_spi_bootloader_reset(); + + addr = ts->mmap->EVENT_BUF_ADDR; + /* clear fw reset status */ + nvt_spi_write_addr(addr | NVT_SPI_EVENT_MAP_RESET_COMPLETE, 0x00); + + /* Start to write firmware process */ + ret = nvt_spi_write_firmware(nvt_spi_fw_entry->data, nvt_spi_fw_entry->size); + if (ret) { + NVT_ERR("Write_Firmware failed. (%d)\n", ret); + goto fail; + } + +#if NVT_DUMP_PARTITION + ret = nvt_dump_partition(); + if (ret) + NVT_ERR("nvt_dump_partition failed, ret = %d\n", ret); +#endif + + /* Set Boot Ready Bit */ + nvt_spi_boot_ready(); + + ret = nvt_spi_check_fw_reset_state(NVT_SPI_RESET_STATE_INIT); + if (ret) { + NVT_ERR("nvt_check_fw_reset_state failed. (%d)\n", ret); + goto fail; + } + + /* check fw checksum result */ + ret = nvt_spi_check_fw_checksum(); + if (ret) { + NVT_ERR("firmware checksum not match, retry=%d\n", retry); + goto fail; + } else + break; + +fail: + retry++; + if (unlikely(retry > 2)) { + NVT_ERR("error, retry=%d\n", retry); + break; + } + } + + nvt_spi_end = ktime_get(); + + return ret; +} + +/* + ****************************************************** + * Description: + * Novatek touchscreen update firmware main function. + * + * return: + * n.a. + ****************************************************** + */ +int32_t nvt_spi_update_firmware(char *firmware_name) +{ + int32_t ret = 0; + struct nvt_spi_data_t *ts = nvt_spi_data; + + // request bin file in "/etc/firmware" + ret = nvt_spi_update_firmware_request(firmware_name); + if (ret) { + NVT_ERR("update_firmware_request failed. (%d)\n", ret); + goto request_firmware_fail; + } + + /* initial buffer and variable */ + ret = nvt_spi_download_init(); + if (ret) { + NVT_ERR("Download Init failed. (%d)\n", ret); + goto download_fail; + } + + /* download firmware process */ + if (ts->hw_crc) + ret = nvt_spi_download_firmware_hw_crc(); + else + ret = nvt_spi_download_firmware(); + if (ret) { + NVT_ERR("Download Firmware failed. (%d)\n", ret); + goto download_fail; + } + + NVT_LOG("Update firmware success! <%ld us>\n", + (long) ktime_us_delta(nvt_spi_end, nvt_spi_start)); + + /* Get FW Info */ + ret = nvt_spi_get_fw_info(); + if (ret) + NVT_ERR("nvt_get_fw_info failed. (%d)\n", ret); + +download_fail: + if (!IS_ERR_OR_NULL(nvt_spi_bin_map)) { + kfree(nvt_spi_bin_map); + nvt_spi_bin_map = NULL; + } + + nvt_spi_update_firmware_release(); +request_firmware_fail: + + return ret; +} + +/* + ******************************************************* + * Description: + * Novatek touchscreen update firmware when booting + * function. + * + * return: + * n.a. + ****************************************************** + */ +void nvt_spi_update_firmware_work(struct work_struct *work) +{ + struct nvt_spi_data_t *ts = nvt_spi_data; + + mutex_lock(&ts->lock); + nvt_spi_update_firmware(NVT_SPI_BOOT_UPDATE_FIRMWARE_NAME); + mutex_unlock(&ts->lock); +} +#endif + +#endif diff --git a/qcom/opensource/touch-drivers/nt36xxx/nt36xxx_mem_map.h b/qcom/opensource/touch-drivers/nt36xxx/nt36xxx_mem_map.h new file mode 100644 index 0000000000..c75d2d4cdb --- /dev/null +++ b/qcom/opensource/touch-drivers/nt36xxx/nt36xxx_mem_map.h @@ -0,0 +1,607 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2010 - 2018 Novatek, Inc. + * + * $Revision: 48764 $ + * $Date: 2019-08-08 14:52:12 +0800 (Thu, 08 Aug 2019) $ + * + * 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. + * + */ +#define CHIP_VER_TRIM_ADDR 0x3F004 +#define CHIP_VER_TRIM_OLD_ADDR 0x1F64E + +#if !defined(NVT_NT36XXX_SPI) /* NT36XXX I2C */ + +struct nvt_ts_mem_map { + uint32_t EVENT_BUF_ADDR; + uint32_t RAW_PIPE0_ADDR; + uint32_t RAW_PIPE1_ADDR; + uint32_t BASELINE_ADDR; + uint32_t BASELINE_BTN_ADDR; + uint32_t DIFF_PIPE0_ADDR; + uint32_t DIFF_PIPE1_ADDR; + uint32_t RAW_BTN_PIPE0_ADDR; + uint32_t RAW_BTN_PIPE1_ADDR; + uint32_t DIFF_BTN_PIPE0_ADDR; + uint32_t DIFF_BTN_PIPE1_ADDR; + uint32_t READ_FLASH_CHECKSUM_ADDR; + uint32_t RW_FLASH_DATA_ADDR; +}; + +struct nvt_ts_hw_info { + uint8_t carrier_system; + uint8_t hw_crc; +}; + +static const struct nvt_ts_mem_map NT36526_memory_map = { + .EVENT_BUF_ADDR = 0x22D00, + .RAW_PIPE0_ADDR = 0x24000, + .RAW_PIPE1_ADDR = 0x24000, + .BASELINE_ADDR = 0x21758, + .BASELINE_BTN_ADDR = 0, + .DIFF_PIPE0_ADDR = 0x20AB0, + .DIFF_PIPE1_ADDR = 0x24AB0, + .RAW_BTN_PIPE0_ADDR = 0, + .RAW_BTN_PIPE1_ADDR = 0, + .DIFF_BTN_PIPE0_ADDR = 0, + .DIFF_BTN_PIPE1_ADDR = 0, + .READ_FLASH_CHECKSUM_ADDR = 0x24000, + .RW_FLASH_DATA_ADDR = 0x24002, +}; + +static const struct nvt_ts_mem_map NT36675_memory_map = { + .EVENT_BUF_ADDR = 0x22D00, + .RAW_PIPE0_ADDR = 0x24000, + .RAW_PIPE1_ADDR = 0x24000, + .BASELINE_ADDR = 0x21B90, + .BASELINE_BTN_ADDR = 0, + .DIFF_PIPE0_ADDR = 0x20C60, + .DIFF_PIPE1_ADDR = 0x24C60, + .RAW_BTN_PIPE0_ADDR = 0, + .RAW_BTN_PIPE1_ADDR = 0, + .DIFF_BTN_PIPE0_ADDR = 0, + .DIFF_BTN_PIPE1_ADDR = 0, + .READ_FLASH_CHECKSUM_ADDR = 0x24000, + .RW_FLASH_DATA_ADDR = 0x24002, +}; + +static const struct nvt_ts_mem_map NT36672A_memory_map = { + .EVENT_BUF_ADDR = 0x21C00, + .RAW_PIPE0_ADDR = 0x20000, + .RAW_PIPE1_ADDR = 0x23000, + .BASELINE_ADDR = 0x20BFC, + .BASELINE_BTN_ADDR = 0x23BFC, + .DIFF_PIPE0_ADDR = 0x206DC, + .DIFF_PIPE1_ADDR = 0x236DC, + .RAW_BTN_PIPE0_ADDR = 0x20510, + .RAW_BTN_PIPE1_ADDR = 0x23510, + .DIFF_BTN_PIPE0_ADDR = 0x20BF0, + .DIFF_BTN_PIPE1_ADDR = 0x23BF0, + .READ_FLASH_CHECKSUM_ADDR = 0x24000, + .RW_FLASH_DATA_ADDR = 0x24002, +}; + +static const struct nvt_ts_mem_map NT36772_memory_map = { + .EVENT_BUF_ADDR = 0x11E00, + .RAW_PIPE0_ADDR = 0x10000, + .RAW_PIPE1_ADDR = 0x12000, + .BASELINE_ADDR = 0x10E70, + .BASELINE_BTN_ADDR = 0x12E70, + .DIFF_PIPE0_ADDR = 0x10830, + .DIFF_PIPE1_ADDR = 0x12830, + .RAW_BTN_PIPE0_ADDR = 0x10E60, + .RAW_BTN_PIPE1_ADDR = 0x12E60, + .DIFF_BTN_PIPE0_ADDR = 0x10E68, + .DIFF_BTN_PIPE1_ADDR = 0x12E68, + .READ_FLASH_CHECKSUM_ADDR = 0x14000, + .RW_FLASH_DATA_ADDR = 0x14002, +}; + +static const struct nvt_ts_mem_map NT36525_memory_map = { + .EVENT_BUF_ADDR = 0x11A00, + .RAW_PIPE0_ADDR = 0x10000, + .RAW_PIPE1_ADDR = 0x12000, + .BASELINE_ADDR = 0x10B08, + .BASELINE_BTN_ADDR = 0x12B08, + .DIFF_PIPE0_ADDR = 0x1064C, + .DIFF_PIPE1_ADDR = 0x1264C, + .RAW_BTN_PIPE0_ADDR = 0x10634, + .RAW_BTN_PIPE1_ADDR = 0x12634, + .DIFF_BTN_PIPE0_ADDR = 0x10AFC, + .DIFF_BTN_PIPE1_ADDR = 0x12AFC, + .READ_FLASH_CHECKSUM_ADDR = 0x14000, + .RW_FLASH_DATA_ADDR = 0x14002, +}; + +static const struct nvt_ts_mem_map NT36676F_memory_map = { + .EVENT_BUF_ADDR = 0x11A00, + .RAW_PIPE0_ADDR = 0x10000, + .RAW_PIPE1_ADDR = 0x12000, + .BASELINE_ADDR = 0x10B08, + .BASELINE_BTN_ADDR = 0x12B08, + .DIFF_PIPE0_ADDR = 0x1064C, + .DIFF_PIPE1_ADDR = 0x1264C, + .RAW_BTN_PIPE0_ADDR = 0x10634, + .RAW_BTN_PIPE1_ADDR = 0x12634, + .DIFF_BTN_PIPE0_ADDR = 0x10AFC, + .DIFF_BTN_PIPE1_ADDR = 0x12AFC, + .READ_FLASH_CHECKSUM_ADDR = 0x14000, + .RW_FLASH_DATA_ADDR = 0x14002, +}; + +static struct nvt_ts_hw_info NT36526_hw_info = { + .carrier_system = 2, + .hw_crc = 2, +}; + +static struct nvt_ts_hw_info NT36675_hw_info = { + .carrier_system = 2, + .hw_crc = 2, +}; + +static struct nvt_ts_hw_info NT36672A_hw_info = { + .carrier_system = 0, + .hw_crc = 1, +}; + +static struct nvt_ts_hw_info NT36772_hw_info = { + .carrier_system = 0, + .hw_crc = 0, +}; + +static struct nvt_ts_hw_info NT36525_hw_info = { + .carrier_system = 0, + .hw_crc = 0, +}; + +static struct nvt_ts_hw_info NT36676F_hw_info = { + .carrier_system = 0, + .hw_crc = 0, +}; + +#define NVT_ID_BYTE_MAX 6 +struct nvt_ts_trim_id_table { + uint8_t id[NVT_ID_BYTE_MAX]; + uint8_t mask[NVT_ID_BYTE_MAX]; + const struct nvt_ts_mem_map *mmap; + const struct nvt_ts_hw_info *hwinfo; +}; + +static const struct nvt_ts_trim_id_table trim_id_table[] = { + {.id = {0x20, 0xFF, 0xFF, 0x72, 0x66, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36675_memory_map, .hwinfo = &NT36675_hw_info}, + {.id = {0x00, 0xFF, 0xFF, 0x80, 0x66, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36675_memory_map, .hwinfo = &NT36675_hw_info}, + {.id = {0x0C, 0xFF, 0xFF, 0x25, 0x65, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36672A_memory_map, .hwinfo = &NT36672A_hw_info}, + {.id = {0x0E, 0xFF, 0xFF, 0x72, 0x66, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36675_memory_map, .hwinfo = &NT36675_hw_info}, + {.id = {0x0C, 0xFF, 0xFF, 0x72, 0x66, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36675_memory_map, .hwinfo = &NT36675_hw_info}, + {.id = {0xFF, 0xFF, 0xFF, 0x26, 0x65, 0x03}, .mask = {0, 0, 0, 1, 1, 1}, + .mmap = &NT36526_memory_map, .hwinfo = &NT36526_hw_info}, + {.id = {0xFF, 0xFF, 0xFF, 0x75, 0x66, 0x03}, .mask = {0, 0, 0, 1, 1, 1}, + .mmap = &NT36675_memory_map, .hwinfo = &NT36675_hw_info}, + {.id = {0x0B, 0xFF, 0xFF, 0x72, 0x66, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36672A_memory_map, .hwinfo = &NT36672A_hw_info}, + {.id = {0x0B, 0xFF, 0xFF, 0x82, 0x66, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36672A_memory_map, .hwinfo = &NT36672A_hw_info}, + {.id = {0x0B, 0xFF, 0xFF, 0x25, 0x65, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36672A_memory_map, .hwinfo = &NT36672A_hw_info}, + {.id = {0x0A, 0xFF, 0xFF, 0x72, 0x65, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36672A_memory_map, .hwinfo = &NT36672A_hw_info}, + {.id = {0x0A, 0xFF, 0xFF, 0x72, 0x66, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36672A_memory_map, .hwinfo = &NT36672A_hw_info}, + {.id = {0x0A, 0xFF, 0xFF, 0x82, 0x66, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36672A_memory_map, .hwinfo = &NT36672A_hw_info}, + {.id = {0x0A, 0xFF, 0xFF, 0x70, 0x66, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36672A_memory_map, .hwinfo = &NT36672A_hw_info}, + {.id = {0x0B, 0xFF, 0xFF, 0x70, 0x66, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36672A_memory_map, .hwinfo = &NT36672A_hw_info}, + {.id = {0x0A, 0xFF, 0xFF, 0x72, 0x67, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36672A_memory_map, .hwinfo = &NT36672A_hw_info}, + {.id = {0x55, 0x00, 0xFF, 0x00, 0x00, 0x00}, .mask = {1, 1, 0, 1, 1, 1}, + .mmap = &NT36772_memory_map, .hwinfo = &NT36772_hw_info}, + {.id = {0x55, 0x72, 0xFF, 0x00, 0x00, 0x00}, .mask = {1, 1, 0, 1, 1, 1}, + .mmap = &NT36772_memory_map, .hwinfo = &NT36772_hw_info}, + {.id = {0xAA, 0x00, 0xFF, 0x00, 0x00, 0x00}, .mask = {1, 1, 0, 1, 1, 1}, + .mmap = &NT36772_memory_map, .hwinfo = &NT36772_hw_info}, + {.id = {0xAA, 0x72, 0xFF, 0x00, 0x00, 0x00}, .mask = {1, 1, 0, 1, 1, 1}, + .mmap = &NT36772_memory_map, .hwinfo = &NT36772_hw_info}, + {.id = {0xFF, 0xFF, 0xFF, 0x72, 0x67, 0x03}, .mask = {0, 0, 0, 1, 1, 1}, + .mmap = &NT36772_memory_map, .hwinfo = &NT36772_hw_info}, + {.id = {0xFF, 0xFF, 0xFF, 0x70, 0x66, 0x03}, .mask = {0, 0, 0, 1, 1, 1}, + .mmap = &NT36772_memory_map, .hwinfo = &NT36772_hw_info}, + {.id = {0xFF, 0xFF, 0xFF, 0x70, 0x67, 0x03}, .mask = {0, 0, 0, 1, 1, 1}, + .mmap = &NT36772_memory_map, .hwinfo = &NT36772_hw_info}, + {.id = {0xFF, 0xFF, 0xFF, 0x72, 0x66, 0x03}, .mask = {0, 0, 0, 1, 1, 1}, + .mmap = &NT36772_memory_map, .hwinfo = &NT36772_hw_info}, + {.id = {0xFF, 0xFF, 0xFF, 0x25, 0x65, 0x03}, .mask = {0, 0, 0, 1, 1, 1}, + .mmap = &NT36525_memory_map, .hwinfo = &NT36525_hw_info}, + {.id = {0xFF, 0xFF, 0xFF, 0x76, 0x66, 0x03}, .mask = {0, 0, 0, 1, 1, 1}, + .mmap = &NT36676F_memory_map, .hwinfo = &NT36676F_hw_info} +}; + +#else /* NT36XXX_SPI */ + +#define NVT_SPI_CHIP_VER_TRIM_ADDR 0x3F004 +#define NVT_SPI_CHIP_VER_TRIM_OLD_ADDR 0x1F64E + +struct nvt_spi_mem_map { + uint32_t EVENT_BUF_ADDR; + uint32_t RAW_PIPE0_ADDR; + uint32_t RAW_PIPE1_ADDR; + uint32_t BASELINE_ADDR; + uint32_t BASELINE_BTN_ADDR; + uint32_t DIFF_PIPE0_ADDR; + uint32_t DIFF_PIPE1_ADDR; + uint32_t RAW_BTN_PIPE0_ADDR; + uint32_t RAW_BTN_PIPE1_ADDR; + uint32_t DIFF_BTN_PIPE0_ADDR; + uint32_t DIFF_BTN_PIPE1_ADDR; + uint32_t PEN_2D_BL_TIP_X_ADDR; + uint32_t PEN_2D_BL_TIP_Y_ADDR; + uint32_t PEN_2D_BL_RING_X_ADDR; + uint32_t PEN_2D_BL_RING_Y_ADDR; + uint32_t PEN_2D_DIFF_TIP_X_ADDR; + uint32_t PEN_2D_DIFF_TIP_Y_ADDR; + uint32_t PEN_2D_DIFF_RING_X_ADDR; + uint32_t PEN_2D_DIFF_RING_Y_ADDR; + uint32_t PEN_2D_RAW_TIP_X_ADDR; + uint32_t PEN_2D_RAW_TIP_Y_ADDR; + uint32_t PEN_2D_RAW_RING_X_ADDR; + uint32_t PEN_2D_RAW_RING_Y_ADDR; + uint32_t PEN_1D_DIFF_TIP_X_ADDR; + uint32_t PEN_1D_DIFF_TIP_Y_ADDR; + uint32_t PEN_1D_DIFF_RING_X_ADDR; + uint32_t PEN_1D_DIFF_RING_Y_ADDR; + uint32_t READ_FLASH_CHECKSUM_ADDR; + uint32_t RW_FLASH_DATA_ADDR; + /* Phase 2 Host Download */ + uint32_t BOOT_RDY_ADDR; + uint32_t POR_CD_ADDR; + uint32_t TX_AUTO_COPY_EN; + uint32_t SPI_DMA_TX_INFO; + /* BLD CRC */ + uint32_t BLD_LENGTH_ADDR; + uint32_t ILM_LENGTH_ADDR; + uint32_t DLM_LENGTH_ADDR; + uint32_t BLD_DES_ADDR; + uint32_t ILM_DES_ADDR; + uint32_t DLM_DES_ADDR; + uint32_t G_ILM_CHECKSUM_ADDR; + uint32_t G_DLM_CHECKSUM_ADDR; + uint32_t R_ILM_CHECKSUM_ADDR; + uint32_t R_DLM_CHECKSUM_ADDR; + uint32_t BLD_CRC_EN_ADDR; + uint32_t DMA_CRC_EN_ADDR; + uint32_t BLD_ILM_DLM_CRC_ADDR; + uint32_t DMA_CRC_FLAG_ADDR; +}; + +struct nvt_spi_hw_info { + uint8_t hw_crc; +}; + +static const struct nvt_spi_mem_map NT36523_memory_map = { + .EVENT_BUF_ADDR = 0x2FE00, + .RAW_PIPE0_ADDR = 0x30FA0, + .RAW_PIPE1_ADDR = 0x30FA0, + .BASELINE_ADDR = 0x36510, + .BASELINE_BTN_ADDR = 0, + .DIFF_PIPE0_ADDR = 0x373E8, + .DIFF_PIPE1_ADDR = 0x38068, + .RAW_BTN_PIPE0_ADDR = 0, + .RAW_BTN_PIPE1_ADDR = 0, + .DIFF_BTN_PIPE0_ADDR = 0, + .DIFF_BTN_PIPE1_ADDR = 0, + .PEN_2D_BL_TIP_X_ADDR = 0x2988A, + .PEN_2D_BL_TIP_Y_ADDR = 0x29A1A, + .PEN_2D_BL_RING_X_ADDR = 0x29BAA, + .PEN_2D_BL_RING_Y_ADDR = 0x29D3A, + .PEN_2D_DIFF_TIP_X_ADDR = 0x29ECA, + .PEN_2D_DIFF_TIP_Y_ADDR = 0x2A05A, + .PEN_2D_DIFF_RING_X_ADDR = 0x2A1EA, + .PEN_2D_DIFF_RING_Y_ADDR = 0x2A37A, + .PEN_2D_RAW_TIP_X_ADDR = 0x2A50A, + .PEN_2D_RAW_TIP_Y_ADDR = 0x2A69A, + .PEN_2D_RAW_RING_X_ADDR = 0x2A82A, + .PEN_2D_RAW_RING_Y_ADDR = 0x2A9BA, + .PEN_1D_DIFF_TIP_X_ADDR = 0x2AB4A, + .PEN_1D_DIFF_TIP_Y_ADDR = 0x2ABAE, + .PEN_1D_DIFF_RING_X_ADDR = 0x2AC12, + .PEN_1D_DIFF_RING_Y_ADDR = 0x2AC76, + .READ_FLASH_CHECKSUM_ADDR = 0x24000, + .RW_FLASH_DATA_ADDR = 0x24002, + /* Phase 2 Host Download */ + .BOOT_RDY_ADDR = 0x3F10D, + .TX_AUTO_COPY_EN = 0x3F7E8, + .SPI_DMA_TX_INFO = 0x3F7F1, + /* BLD CRC */ + .BLD_LENGTH_ADDR = 0x3F138, //0x3F138 ~ 0x3F13A (3 bytes) + .ILM_LENGTH_ADDR = 0x3F118, //0x3F118 ~ 0x3F11A (3 bytes) + .DLM_LENGTH_ADDR = 0x3F130, //0x3F130 ~ 0x3F132 (3 bytes) + .BLD_DES_ADDR = 0x3F114, //0x3F114 ~ 0x3F116 (3 bytes) + .ILM_DES_ADDR = 0x3F128, //0x3F128 ~ 0x3F12A (3 bytes) + .DLM_DES_ADDR = 0x3F12C, //0x3F12C ~ 0x3F12E (3 bytes) + .G_ILM_CHECKSUM_ADDR = 0x3F100, //0x3F100 ~ 0x3F103 (4 bytes) + .G_DLM_CHECKSUM_ADDR = 0x3F104, //0x3F104 ~ 0x3F107 (4 bytes) + .R_ILM_CHECKSUM_ADDR = 0x3F120, //0x3F120 ~ 0x3F123 (4 bytes) + .R_DLM_CHECKSUM_ADDR = 0x3F124, //0x3F124 ~ 0x3F127 (4 bytes) + .BLD_CRC_EN_ADDR = 0x3F30E, + .DMA_CRC_EN_ADDR = 0x3F136, + .BLD_ILM_DLM_CRC_ADDR = 0x3F133, + .DMA_CRC_FLAG_ADDR = 0x3F134, +}; + +static const struct nvt_spi_mem_map NT36526_memory_map = { + .EVENT_BUF_ADDR = 0x22D00, + .RAW_PIPE0_ADDR = 0x24000, + .RAW_PIPE1_ADDR = 0x24000, + .BASELINE_ADDR = 0x21758, + .BASELINE_BTN_ADDR = 0, + .DIFF_PIPE0_ADDR = 0x20AB0, + .DIFF_PIPE1_ADDR = 0x24AB0, + .RAW_BTN_PIPE0_ADDR = 0, + .RAW_BTN_PIPE1_ADDR = 0, + .DIFF_BTN_PIPE0_ADDR = 0, + .DIFF_BTN_PIPE1_ADDR = 0, + .READ_FLASH_CHECKSUM_ADDR = 0x24000, + .RW_FLASH_DATA_ADDR = 0x24002, + /* Phase 2 Host Download */ + .BOOT_RDY_ADDR = 0x3F10D, + /* BLD CRC */ + .BLD_LENGTH_ADDR = 0x3F138, //0x3F138 ~ 0x3F13A (3 bytes) + .ILM_LENGTH_ADDR = 0x3F118, //0x3F118 ~ 0x3F11A (3 bytes) + .DLM_LENGTH_ADDR = 0x3F130, //0x3F130 ~ 0x3F132 (3 bytes) + .BLD_DES_ADDR = 0x3F114, //0x3F114 ~ 0x3F116 (3 bytes) + .ILM_DES_ADDR = 0x3F128, //0x3F128 ~ 0x3F12A (3 bytes) + .DLM_DES_ADDR = 0x3F12C, //0x3F12C ~ 0x3F12E (3 bytes) + .G_ILM_CHECKSUM_ADDR = 0x3F100, //0x3F100 ~ 0x3F103 (4 bytes) + .G_DLM_CHECKSUM_ADDR = 0x3F104, //0x3F104 ~ 0x3F107 (4 bytes) + .R_ILM_CHECKSUM_ADDR = 0x3F120, //0x3F120 ~ 0x3F123 (4 bytes) + .R_DLM_CHECKSUM_ADDR = 0x3F124, //0x3F124 ~ 0x3F127 (4 bytes) + .BLD_CRC_EN_ADDR = 0x3F30E, + .DMA_CRC_EN_ADDR = 0x3F136, + .BLD_ILM_DLM_CRC_ADDR = 0x3F133, + .DMA_CRC_FLAG_ADDR = 0x3F134, +}; + + +static const struct nvt_spi_mem_map NT36675_memory_map = { + .EVENT_BUF_ADDR = 0x22D00, + .RAW_PIPE0_ADDR = 0x24000, + .RAW_PIPE1_ADDR = 0x24000, + .BASELINE_ADDR = 0x21B90, + .BASELINE_BTN_ADDR = 0, + .DIFF_PIPE0_ADDR = 0x20C60, + .DIFF_PIPE1_ADDR = 0x24C60, + .RAW_BTN_PIPE0_ADDR = 0, + .RAW_BTN_PIPE1_ADDR = 0, + .DIFF_BTN_PIPE0_ADDR = 0, + .DIFF_BTN_PIPE1_ADDR = 0, + .READ_FLASH_CHECKSUM_ADDR = 0x24000, + .RW_FLASH_DATA_ADDR = 0x24002, + /* Phase 2 Host Download */ + .BOOT_RDY_ADDR = 0x3F10D, + /* BLD CRC */ + .BLD_LENGTH_ADDR = 0x3F138, //0x3F138 ~ 0x3F13A (3 bytes) + .ILM_LENGTH_ADDR = 0x3F118, //0x3F118 ~ 0x3F11A (3 bytes) + .DLM_LENGTH_ADDR = 0x3F130, //0x3F130 ~ 0x3F132 (3 bytes) + .BLD_DES_ADDR = 0x3F114, //0x3F114 ~ 0x3F116 (3 bytes) + .ILM_DES_ADDR = 0x3F128, //0x3F128 ~ 0x3F12A (3 bytes) + .DLM_DES_ADDR = 0x3F12C, //0x3F12C ~ 0x3F12E (3 bytes) + .G_ILM_CHECKSUM_ADDR = 0x3F100, //0x3F100 ~ 0x3F103 (4 bytes) + .G_DLM_CHECKSUM_ADDR = 0x3F104, //0x3F104 ~ 0x3F107 (4 bytes) + .R_ILM_CHECKSUM_ADDR = 0x3F120, //0x3F120 ~ 0x3F123 (4 bytes) + .R_DLM_CHECKSUM_ADDR = 0x3F124, //0x3F124 ~ 0x3F127 (4 bytes) + .BLD_CRC_EN_ADDR = 0x3F30E, + .DMA_CRC_EN_ADDR = 0x3F136, + .BLD_ILM_DLM_CRC_ADDR = 0x3F133, + .DMA_CRC_FLAG_ADDR = 0x3F134, +}; + +static const struct nvt_spi_mem_map NT36672A_memory_map = { + .EVENT_BUF_ADDR = 0x21C00, + .RAW_PIPE0_ADDR = 0x20000, + .RAW_PIPE1_ADDR = 0x23000, + .BASELINE_ADDR = 0x20BFC, + .BASELINE_BTN_ADDR = 0x23BFC, + .DIFF_PIPE0_ADDR = 0x206DC, + .DIFF_PIPE1_ADDR = 0x236DC, + .RAW_BTN_PIPE0_ADDR = 0x20510, + .RAW_BTN_PIPE1_ADDR = 0x23510, + .DIFF_BTN_PIPE0_ADDR = 0x20BF0, + .DIFF_BTN_PIPE1_ADDR = 0x23BF0, + .READ_FLASH_CHECKSUM_ADDR = 0x24000, + .RW_FLASH_DATA_ADDR = 0x24002, + /* Phase 2 Host Download */ + .BOOT_RDY_ADDR = 0x3F10D, + /* BLD CRC */ + .BLD_LENGTH_ADDR = 0x3F10E, //0x3F10E ~ 0x3F10F (2 bytes) + .ILM_LENGTH_ADDR = 0x3F118, //0x3F118 ~ 0x3F119 (2 bytes) + .DLM_LENGTH_ADDR = 0x3F130, //0x3F130 ~ 0x3F131 (2 bytes) + .BLD_DES_ADDR = 0x3F114, //0x3F114 ~ 0x3F116 (3 bytes) + .ILM_DES_ADDR = 0x3F128, //0x3F128 ~ 0x3F12A (3 bytes) + .DLM_DES_ADDR = 0x3F12C, //0x3F12C ~ 0x3F12E (3 bytes) + .G_ILM_CHECKSUM_ADDR = 0x3F100, //0x3F100 ~ 0x3F103 (4 bytes) + .G_DLM_CHECKSUM_ADDR = 0x3F104, //0x3F104 ~ 0x3F107 (4 bytes) + .R_ILM_CHECKSUM_ADDR = 0x3F120, //0x3F120 ~ 0x3F123 (4 bytes) + .R_DLM_CHECKSUM_ADDR = 0x3F124, //0x3F124 ~ 0x3F127 (4 bytes) + .BLD_CRC_EN_ADDR = 0x3F30E, + .DMA_CRC_EN_ADDR = 0x3F132, + .BLD_ILM_DLM_CRC_ADDR = 0x3F133, + .DMA_CRC_FLAG_ADDR = 0x3F134, +}; + +static const struct nvt_spi_mem_map NT36772_memory_map = { + .EVENT_BUF_ADDR = 0x11E00, + .RAW_PIPE0_ADDR = 0x10000, + .RAW_PIPE1_ADDR = 0x12000, + .BASELINE_ADDR = 0x10E70, + .BASELINE_BTN_ADDR = 0x12E70, + .DIFF_PIPE0_ADDR = 0x10830, + .DIFF_PIPE1_ADDR = 0x12830, + .RAW_BTN_PIPE0_ADDR = 0x10E60, + .RAW_BTN_PIPE1_ADDR = 0x12E60, + .DIFF_BTN_PIPE0_ADDR = 0x10E68, + .DIFF_BTN_PIPE1_ADDR = 0x12E68, + .READ_FLASH_CHECKSUM_ADDR = 0x14000, + .RW_FLASH_DATA_ADDR = 0x14002, + /* Phase 2 Host Download */ + .BOOT_RDY_ADDR = 0x1F141, + .POR_CD_ADDR = 0x1F61C, + /* BLD CRC */ + .R_ILM_CHECKSUM_ADDR = 0x1BF00, +}; + +static const struct nvt_spi_mem_map NT36525_memory_map = { + .EVENT_BUF_ADDR = 0x11A00, + .RAW_PIPE0_ADDR = 0x10000, + .RAW_PIPE1_ADDR = 0x12000, + .BASELINE_ADDR = 0x10B08, + .BASELINE_BTN_ADDR = 0x12B08, + .DIFF_PIPE0_ADDR = 0x1064C, + .DIFF_PIPE1_ADDR = 0x1264C, + .RAW_BTN_PIPE0_ADDR = 0x10634, + .RAW_BTN_PIPE1_ADDR = 0x12634, + .DIFF_BTN_PIPE0_ADDR = 0x10AFC, + .DIFF_BTN_PIPE1_ADDR = 0x12AFC, + .READ_FLASH_CHECKSUM_ADDR = 0x14000, + .RW_FLASH_DATA_ADDR = 0x14002, + /* Phase 2 Host Download */ + .BOOT_RDY_ADDR = 0x1F141, + .POR_CD_ADDR = 0x1F61C, + /* BLD CRC */ + .R_ILM_CHECKSUM_ADDR = 0x1BF00, +}; + +static const struct nvt_spi_mem_map NT36676F_memory_map = { + .EVENT_BUF_ADDR = 0x11A00, + .RAW_PIPE0_ADDR = 0x10000, + .RAW_PIPE1_ADDR = 0x12000, + .BASELINE_ADDR = 0x10B08, + .BASELINE_BTN_ADDR = 0x12B08, + .DIFF_PIPE0_ADDR = 0x1064C, + .DIFF_PIPE1_ADDR = 0x1264C, + .RAW_BTN_PIPE0_ADDR = 0x10634, + .RAW_BTN_PIPE1_ADDR = 0x12634, + .DIFF_BTN_PIPE0_ADDR = 0x10AFC, + .DIFF_BTN_PIPE1_ADDR = 0x12AFC, + .READ_FLASH_CHECKSUM_ADDR = 0x14000, + .RW_FLASH_DATA_ADDR = 0x14002, +}; + +static struct nvt_spi_hw_info NT36523_hw_info = { + .hw_crc = 2, +}; + +static struct nvt_spi_hw_info NT36526_hw_info = { + .hw_crc = 2, +}; + +static struct nvt_spi_hw_info NT36675_hw_info = { + .hw_crc = 2, +}; + +static struct nvt_spi_hw_info NT36672A_hw_info = { + .hw_crc = 1, +}; + +static struct nvt_spi_hw_info NT36772_hw_info = { + .hw_crc = 0, +}; + +static struct nvt_spi_hw_info NT36525_hw_info = { + .hw_crc = 0, +}; + +static struct nvt_spi_hw_info NT36676F_hw_info = { + .hw_crc = 0, +}; + +#define NVT_SPI_ID_BYTE_MAX 6 +struct nvt_spi_trim_id_table_t { + uint8_t id[NVT_SPI_ID_BYTE_MAX]; + uint8_t mask[NVT_SPI_ID_BYTE_MAX]; + const struct nvt_spi_mem_map *mmap; + const struct nvt_spi_hw_info *hwinfo; +}; + +static const struct nvt_spi_trim_id_table_t nvt_spi_trim_id_table[] = { + {.id = {0x0D, 0xFF, 0xFF, 0x72, 0x66, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36675_memory_map, .hwinfo = &NT36675_hw_info}, + {.id = {0x20, 0xFF, 0xFF, 0x72, 0x66, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36675_memory_map, .hwinfo = &NT36675_hw_info}, + {.id = {0x00, 0xFF, 0xFF, 0x80, 0x66, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36675_memory_map, .hwinfo = &NT36675_hw_info}, + {.id = {0x0C, 0xFF, 0xFF, 0x25, 0x65, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36672A_memory_map, .hwinfo = &NT36672A_hw_info}, + {.id = {0x0E, 0xFF, 0xFF, 0x72, 0x66, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36675_memory_map, .hwinfo = &NT36675_hw_info}, + {.id = {0x20, 0xFF, 0xFF, 0x23, 0x65, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36523_memory_map, .hwinfo = &NT36523_hw_info}, + {.id = {0x0C, 0xFF, 0xFF, 0x23, 0x65, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36523_memory_map, .hwinfo = &NT36523_hw_info}, + {.id = {0x0B, 0xFF, 0xFF, 0x23, 0x65, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36523_memory_map, .hwinfo = &NT36523_hw_info}, + {.id = {0x0A, 0xFF, 0xFF, 0x23, 0x65, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36523_memory_map, .hwinfo = &NT36523_hw_info}, + {.id = {0xFF, 0xFF, 0xFF, 0x23, 0x65, 0x03}, .mask = {0, 0, 0, 1, 1, 1}, + .mmap = &NT36523_memory_map, .hwinfo = &NT36523_hw_info}, + {.id = {0x0C, 0xFF, 0xFF, 0x72, 0x66, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36675_memory_map, .hwinfo = &NT36675_hw_info}, + {.id = {0xFF, 0xFF, 0xFF, 0x26, 0x65, 0x03}, .mask = {0, 0, 0, 1, 1, 1}, + .mmap = &NT36526_memory_map, .hwinfo = &NT36526_hw_info}, + {.id = {0xFF, 0xFF, 0xFF, 0x75, 0x66, 0x03}, .mask = {0, 0, 0, 1, 1, 1}, + .mmap = &NT36675_memory_map, .hwinfo = &NT36675_hw_info}, + {.id = {0x0B, 0xFF, 0xFF, 0x72, 0x66, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36672A_memory_map, .hwinfo = &NT36672A_hw_info}, + {.id = {0x0B, 0xFF, 0xFF, 0x82, 0x66, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36672A_memory_map, .hwinfo = &NT36672A_hw_info}, + {.id = {0x0B, 0xFF, 0xFF, 0x25, 0x65, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36672A_memory_map, .hwinfo = &NT36672A_hw_info}, + {.id = {0x0A, 0xFF, 0xFF, 0x72, 0x65, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36672A_memory_map, .hwinfo = &NT36672A_hw_info}, + {.id = {0x0A, 0xFF, 0xFF, 0x72, 0x66, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36672A_memory_map, .hwinfo = &NT36672A_hw_info}, + {.id = {0x0A, 0xFF, 0xFF, 0x82, 0x66, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36672A_memory_map, .hwinfo = &NT36672A_hw_info}, + {.id = {0x0A, 0xFF, 0xFF, 0x70, 0x66, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36672A_memory_map, .hwinfo = &NT36672A_hw_info}, + {.id = {0x0B, 0xFF, 0xFF, 0x70, 0x66, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36672A_memory_map, .hwinfo = &NT36672A_hw_info}, + {.id = {0x0A, 0xFF, 0xFF, 0x72, 0x67, 0x03}, .mask = {1, 0, 0, 1, 1, 1}, + .mmap = &NT36672A_memory_map, .hwinfo = &NT36672A_hw_info}, + {.id = {0x55, 0x00, 0xFF, 0x00, 0x00, 0x00}, .mask = {1, 1, 0, 1, 1, 1}, + .mmap = &NT36772_memory_map, .hwinfo = &NT36772_hw_info}, + {.id = {0x55, 0x72, 0xFF, 0x00, 0x00, 0x00}, .mask = {1, 1, 0, 1, 1, 1}, + .mmap = &NT36772_memory_map, .hwinfo = &NT36772_hw_info}, + {.id = {0xAA, 0x00, 0xFF, 0x00, 0x00, 0x00}, .mask = {1, 1, 0, 1, 1, 1}, + .mmap = &NT36772_memory_map, .hwinfo = &NT36772_hw_info}, + {.id = {0xAA, 0x72, 0xFF, 0x00, 0x00, 0x00}, .mask = {1, 1, 0, 1, 1, 1}, + .mmap = &NT36772_memory_map, .hwinfo = &NT36772_hw_info}, + {.id = {0xFF, 0xFF, 0xFF, 0x72, 0x67, 0x03}, .mask = {0, 0, 0, 1, 1, 1}, + .mmap = &NT36772_memory_map, .hwinfo = &NT36772_hw_info}, + {.id = {0xFF, 0xFF, 0xFF, 0x70, 0x66, 0x03}, .mask = {0, 0, 0, 1, 1, 1}, + .mmap = &NT36772_memory_map, .hwinfo = &NT36772_hw_info}, + {.id = {0xFF, 0xFF, 0xFF, 0x70, 0x67, 0x03}, .mask = {0, 0, 0, 1, 1, 1}, + .mmap = &NT36772_memory_map, .hwinfo = &NT36772_hw_info}, + {.id = {0xFF, 0xFF, 0xFF, 0x72, 0x66, 0x03}, .mask = {0, 0, 0, 1, 1, 1}, + .mmap = &NT36772_memory_map, .hwinfo = &NT36772_hw_info}, + {.id = {0xFF, 0xFF, 0xFF, 0x25, 0x65, 0x03}, .mask = {0, 0, 0, 1, 1, 1}, + .mmap = &NT36525_memory_map, .hwinfo = &NT36525_hw_info}, + {.id = {0xFF, 0xFF, 0xFF, 0x76, 0x66, 0x03}, .mask = {0, 0, 0, 1, 1, 1}, + .mmap = &NT36676F_memory_map, .hwinfo = &NT36676F_hw_info} +}; + +#endif diff --git a/qcom/opensource/touch-drivers/nt36xxx/nt36xxx_mp_ctrlram.c b/qcom/opensource/touch-drivers/nt36xxx/nt36xxx_mp_ctrlram.c new file mode 100644 index 0000000000..5957f2c761 --- /dev/null +++ b/qcom/opensource/touch-drivers/nt36xxx/nt36xxx_mp_ctrlram.c @@ -0,0 +1,1481 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2010 - 2018 Novatek, Inc. + * + * $Revision: 47247 $ + * $Date: 2019-07-10 10:41:36 +0800 (Wed, 10 Jul 2019) $ + * + * 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. + * + */ + +#include +#include +#include + +#include "nt36xxx.h" +#include "nt36xxx_mp_ctrlram.h" + +#if NVT_TOUCH_MP + +#define NORMAL_MODE 0x00 +#define TEST_MODE_1 0x21 +#define TEST_MODE_2 0x22 +#define MP_MODE_CC 0x41 +#define FREQ_HOP_DISABLE 0x66 +#define FREQ_HOP_ENABLE 0x65 + +#define SHORT_TEST_CSV_FILE "/data/local/tmp/ShortTest.csv" +#define OPEN_TEST_CSV_FILE "/data/local/tmp/OpenTest.csv" +#define FW_RAWDATA_CSV_FILE "/data/local/tmp/FWMutualTest.csv" +#define FW_CC_CSV_FILE "/data/local/tmp/FWCCTest.csv" +#define NOISE_TEST_CSV_FILE "/data/local/tmp/NoiseTest.csv" + +#define nvt_mp_seq_printf(m, fmt, args...) do { \ + seq_printf(m, fmt, ##args); \ + if (!nvt_mp_test_result_printed) \ + printk(fmt, ##args); \ +} while (0) + +static uint8_t *RecordResult_Short = NULL; +static uint8_t *RecordResult_Open = NULL; +static uint8_t *RecordResult_FWMutual = NULL; +static uint8_t *RecordResult_FW_CC = NULL; +static uint8_t *RecordResult_FW_DiffMax = NULL; +static uint8_t *RecordResult_FW_DiffMin = NULL; + +static int32_t TestResult_Short = 0; +static int32_t TestResult_Open = 0; +static int32_t TestResult_FW_Rawdata = 0; +static int32_t TestResult_FWMutual = 0; +static int32_t TestResult_FW_CC = 0; +static int32_t TestResult_Noise = 0; +static int32_t TestResult_FW_DiffMax = 0; +static int32_t TestResult_FW_DiffMin = 0; + +static int32_t *RawData_Short = NULL; +static int32_t *RawData_Open = NULL; +static int32_t *RawData_Diff = NULL; +static int32_t *RawData_Diff_Min = NULL; +static int32_t *RawData_Diff_Max = NULL; +static int32_t *RawData_FWMutual = NULL; +static int32_t *RawData_FW_CC = NULL; + +static struct proc_dir_entry *NVT_proc_selftest_entry = NULL; +static int8_t nvt_mp_test_result_printed = 0; +static uint8_t fw_ver = 0; +unsigned char mpcriteria[PAGE_SIZE]; //novatek-mp-criteria-default + +extern void nvt_change_mode(uint8_t mode); +extern uint8_t nvt_get_fw_pipe(void); +extern void nvt_read_mdata(uint32_t xdata_addr, uint32_t xdata_btn_addr); +extern void nvt_get_mdata(int32_t *buf, uint8_t *m_x_num, uint8_t *m_y_num); +int32_t nvt_mp_parse_dt(struct device_node *root, const char *node_compatible); + +/******************************************************* +Description: + Novatek touchscreen allocate buffer for mp selftest. + +return: + Executive outcomes. 0---succeed. -12---Out of memory +*******************************************************/ +static int nvt_mp_buffer_init(void) +{ + size_t RecordResult_BufSize = IC_X_CFG_SIZE * IC_Y_CFG_SIZE + IC_KEY_CFG_SIZE; + size_t RawData_BufSize = (IC_X_CFG_SIZE * IC_Y_CFG_SIZE + IC_KEY_CFG_SIZE) * sizeof(int32_t); + + RecordResult_Short = kzalloc(RecordResult_BufSize, GFP_KERNEL); + if (!RecordResult_Short) { + NVT_ERR("kzalloc for RecordResult_Short failed!\n"); + return -ENOMEM; + } + + RecordResult_Open = kzalloc(RecordResult_BufSize, GFP_KERNEL); + if (!RecordResult_Open) { + NVT_ERR("kzalloc for RecordResult_Open failed!\n"); + return -ENOMEM; + } + + RecordResult_FWMutual = kzalloc(RecordResult_BufSize, GFP_KERNEL); + if (!RecordResult_FWMutual) { + NVT_ERR("kzalloc for RecordResult_FWMutual failed!\n"); + return -ENOMEM; + } + + RecordResult_FW_CC = kzalloc(RecordResult_BufSize, GFP_KERNEL); + if (!RecordResult_FW_CC) { + NVT_ERR("kzalloc for RecordResult_FW_CC failed!\n"); + return -ENOMEM; + } + + RecordResult_FW_DiffMax = kzalloc(RecordResult_BufSize, GFP_KERNEL); + if (!RecordResult_FW_DiffMax) { + NVT_ERR("kzalloc for RecordResult_FW_DiffMax failed!\n"); + return -ENOMEM; + } + + RecordResult_FW_DiffMin = kzalloc(RecordResult_BufSize, GFP_KERNEL); + if (!RecordResult_FW_DiffMin) { + NVT_ERR("kzalloc for RecordResult_FW_DiffMin failed!\n"); + return -ENOMEM; + } + + RawData_Short = kzalloc(RawData_BufSize, GFP_KERNEL); + if (!RawData_Short) { + NVT_ERR("kzalloc for RawData_Short failed!\n"); + return -ENOMEM; + } + + RawData_Open = kzalloc(RawData_BufSize, GFP_KERNEL); + if (!RawData_Open) { + NVT_ERR("kzalloc for RawData_Open failed!\n"); + return -ENOMEM; + } + + RawData_Diff = kzalloc(RawData_BufSize, GFP_KERNEL); + if (!RawData_Diff) { + NVT_ERR("kzalloc for RawData_Diff failed!\n"); + return -ENOMEM; + } + + RawData_Diff_Min = kzalloc(RawData_BufSize, GFP_KERNEL); + if (!RawData_Diff_Min) { + NVT_ERR("kzalloc for RawData_Diff_Min failed!\n"); + return -ENOMEM; + } + + RawData_Diff_Max = kzalloc(RawData_BufSize, GFP_KERNEL); + if (!RawData_Diff_Max) { + NVT_ERR("kzalloc for RawData_Diff_Max failed!\n"); + return -ENOMEM; + } + + RawData_FWMutual = kzalloc(RawData_BufSize, GFP_KERNEL); + if (!RawData_FWMutual) { + NVT_ERR("kzalloc for RawData_FWMutual failed!\n"); + return -ENOMEM; + } + + RawData_FW_CC = kzalloc(RawData_BufSize, GFP_KERNEL); + if (!RawData_FW_CC) { + NVT_ERR("kzalloc for RawData_FW_CC failed!\n"); + return -ENOMEM; + } + + return 0; +} + +/******************************************************* +Description: + Novatek touchscreen free buffer for mp selftest. + +return: + n.a. +*******************************************************/ +static void nvt_mp_buffer_deinit(void) +{ + if (RecordResult_Short) { + kfree(RecordResult_Short); + RecordResult_Short = NULL; + } + + if (RecordResult_Open) { + kfree(RecordResult_Open); + RecordResult_Open = NULL; + } + + if (RecordResult_FWMutual) { + kfree(RecordResult_FWMutual); + RecordResult_FWMutual = NULL; + } + + if (RecordResult_FW_CC) { + kfree(RecordResult_FW_CC); + RecordResult_FW_CC = NULL; + } + + if (RecordResult_FW_DiffMax) { + kfree(RecordResult_FW_DiffMax); + RecordResult_FW_DiffMax = NULL; + } + + if (RecordResult_FW_DiffMin) { + kfree(RecordResult_FW_DiffMin); + RecordResult_FW_DiffMin = NULL; + } + + if (RawData_Short) { + kfree(RawData_Short); + RawData_Short = NULL; + } + + if (RawData_Open) { + kfree(RawData_Open); + RawData_Open = NULL; + } + + if (RawData_Diff) { + kfree(RawData_Diff); + RawData_Diff = NULL; + } + + if (RawData_Diff_Min) { + kfree(RawData_Diff_Min); + RawData_Diff_Min = NULL; + } + + if (RawData_Diff_Max) { + kfree(RawData_Diff_Max); + RawData_Diff_Max = NULL; + } + + if (RawData_FWMutual) { + kfree(RawData_FWMutual); + RawData_FWMutual = NULL; + } + + if (RawData_FW_CC) { + kfree(RawData_FW_CC); + RawData_FW_CC = NULL; + } +} + +static void nvt_print_data_log_in_one_line(int32_t *data, int32_t data_num) +{ + char *tmp_log = NULL; + int32_t i = 0; + int32_t count = data_num * 7 + 1; + + tmp_log = kzalloc(count, GFP_KERNEL); + if (!tmp_log) { + NVT_ERR("kzalloc for tmp_log failed!\n "); + return; + } + + for (i = 0; i < data_num; i++) { + snprintf(tmp_log + i * 7, count - i * 7, "%5d, ", data[i]); + } + tmp_log[data_num * 7] = '\0'; + printk("%s", tmp_log); + if (tmp_log) { + kfree(tmp_log); + tmp_log = NULL; + } + + return; +} + +static void nvt_print_result_log_in_one_line(uint8_t *result, int32_t result_num) +{ + char *tmp_log = NULL; + int32_t i = 0; + int32_t count = 0; + + count = result_num * 6 + 1; + tmp_log = kzalloc(count, GFP_KERNEL); + if (!tmp_log) { + NVT_ERR("kzalloc for tmp_log failed!\n "); + return; + } + + for (i = 0; i < result_num; i++) { + snprintf(tmp_log + i * 6, count - i * 6, "0x%02X, ", result[i]); + } + tmp_log[result_num * 6] = '\0'; + printk("%s", tmp_log); + if (tmp_log) { + kfree(tmp_log); + tmp_log = NULL; + } + + return; +} + +/******************************************************* +Description: + Novatek touchscreen self-test criteria print function. + +return: + n.a. +*******************************************************/ +static void nvt_print_lmt_array(int32_t *array, int32_t x_ch, int32_t y_ch) +{ + int32_t j = 0; + + for (j = 0; j < y_ch; j++) { + nvt_print_data_log_in_one_line(array + j * x_ch, x_ch); + printk("\n"); + } +#if TOUCH_KEY_NUM > 0 + nvt_print_data_log_in_one_line(array + y_ch * x_ch, Key_Channel); + printk("\n"); +#endif /* #if TOUCH_KEY_NUM > 0 */ +} + +static void nvt_print_criteria(void) +{ + NVT_LOG("++\n"); + + //---PS_Config_Lmt_Short_Rawdata--- + printk("PS_Config_Lmt_Short_Rawdata_P:\n"); + nvt_print_lmt_array(PS_Config_Lmt_Short_Rawdata_P, X_Channel, Y_Channel); + printk("PS_Config_Lmt_Short_Rawdata_N:\n"); + nvt_print_lmt_array(PS_Config_Lmt_Short_Rawdata_N, X_Channel, Y_Channel); + + //---PS_Config_Lmt_Open_Rawdata--- + printk("PS_Config_Lmt_Open_Rawdata_P:\n"); + nvt_print_lmt_array(PS_Config_Lmt_Open_Rawdata_P, X_Channel, Y_Channel); + printk("PS_Config_Lmt_Open_Rawdata_N:\n"); + nvt_print_lmt_array(PS_Config_Lmt_Open_Rawdata_N, X_Channel, Y_Channel); + + //---PS_Config_Lmt_FW_Rawdata--- + printk("PS_Config_Lmt_FW_Rawdata_P:\n"); + nvt_print_lmt_array(PS_Config_Lmt_FW_Rawdata_P, X_Channel, Y_Channel); + printk("PS_Config_Lmt_FW_Rawdata_N:\n"); + nvt_print_lmt_array(PS_Config_Lmt_FW_Rawdata_N, X_Channel, Y_Channel); + + //---PS_Config_Lmt_FW_CC--- + printk("PS_Config_Lmt_FW_CC_P:\n"); + nvt_print_lmt_array(PS_Config_Lmt_FW_CC_P, X_Channel, Y_Channel); + printk("PS_Config_Lmt_FW_CC_N:\n"); + nvt_print_lmt_array(PS_Config_Lmt_FW_CC_N, X_Channel, Y_Channel); + + //---PS_Config_Lmt_FW_Diff--- + printk("PS_Config_Lmt_FW_Diff_P:\n"); + nvt_print_lmt_array(PS_Config_Lmt_FW_Diff_P, X_Channel, Y_Channel); + printk("PS_Config_Lmt_FW_Diff_N:\n"); + nvt_print_lmt_array(PS_Config_Lmt_FW_Diff_N, X_Channel, Y_Channel); + + NVT_LOG("--\n"); +} + +static int32_t nvt_polling_hand_shake_status(void) +{ + uint8_t buf[8] = {0}; + int32_t i = 0; + const int32_t retry = 70; + + for (i = 0; i < retry; i++) { + //---set xdata index to EVENT BUF ADDR--- + nvt_set_page(I2C_FW_Address, ts->mmap->EVENT_BUF_ADDR | EVENT_MAP_HANDSHAKING_or_SUB_CMD_BYTE); + + //---read fw status--- + buf[0] = EVENT_MAP_HANDSHAKING_or_SUB_CMD_BYTE; + buf[1] = 0x00; + CTP_I2C_READ(ts->client, I2C_FW_Address, buf, 2); + + if ((buf[1] == 0xA0) || (buf[1] == 0xA1)) + break; + + usleep_range(10000, 10000); + } + + if (i >= retry) { + NVT_ERR("polling hand shake status failed, buf[1]=0x%02X\n", buf[1]); + + // Read back 5 bytes from offset EVENT_MAP_HOST_CMD for debug check + nvt_set_page(I2C_FW_Address, ts->mmap->EVENT_BUF_ADDR | EVENT_MAP_HOST_CMD); + + buf[0] = EVENT_MAP_HOST_CMD; + buf[1] = 0x00; + buf[2] = 0x00; + buf[3] = 0x00; + buf[4] = 0x00; + buf[5] = 0x00; + CTP_I2C_READ(ts->client, I2C_FW_Address, buf, 6); + NVT_ERR("Read back 5 bytes from offset EVENT_MAP_HOST_CMD: 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X\n", buf[1], buf[2], buf[3], buf[4], buf[5]); + + return -1; + } else { + return 0; + } +} + +static int8_t nvt_switch_FreqHopEnDis(uint8_t FreqHopEnDis) +{ + uint8_t buf[8] = {0}; + uint8_t retry = 0; + int8_t ret = 0; + + NVT_LOG("++\n"); + + for (retry = 0; retry < 20; retry++) { + //---set xdata index to EVENT BUF ADDR--- + nvt_set_page(I2C_FW_Address, ts->mmap->EVENT_BUF_ADDR | EVENT_MAP_HOST_CMD); + + //---switch FreqHopEnDis--- + buf[0] = EVENT_MAP_HOST_CMD; + buf[1] = FreqHopEnDis; + CTP_I2C_WRITE(ts->client, I2C_FW_Address, buf, 2); + + msleep(35); + + buf[0] = EVENT_MAP_HOST_CMD; + buf[1] = 0xFF; + CTP_I2C_READ(ts->client, I2C_FW_Address, buf, 2); + + if (buf[1] == 0x00) + break; + } + + if (unlikely(retry == 20)) { + NVT_ERR("switch FreqHopEnDis 0x%02X failed, buf[1]=0x%02X\n", FreqHopEnDis, buf[1]); + ret = -1; + } + + NVT_LOG("--\n"); + + return ret; +} + +static int32_t nvt_read_baseline(int32_t *xdata) +{ + uint8_t x_num = 0; + uint8_t y_num = 0; + uint32_t x = 0; + uint32_t y = 0; + int32_t iArrayIndex = 0; +#if TOUCH_KEY_NUM > 0 + int32_t k = 0; +#endif /* #if TOUCH_KEY_NUM > 0 */ + + NVT_LOG("++\n"); + + nvt_read_mdata(ts->mmap->BASELINE_ADDR, ts->mmap->BASELINE_BTN_ADDR); + + nvt_get_mdata(xdata, &x_num, &y_num); + + for (y = 0; y < y_num; y++) { + for (x = 0; x < x_num; x++) { + iArrayIndex = y * x_num + x; + xdata[iArrayIndex] = (int16_t)xdata[iArrayIndex]; + } + } +#if TOUCH_KEY_NUM > 0 + for (k = 0; k < Key_Channel; k++) { + iArrayIndex = Y_Channel * X_Channel + k; + xdata[iArrayIndex] = (int16_t)xdata[iArrayIndex]; + } +#endif /* #if TOUCH_KEY_NUM > 0 */ + + NVT_LOG("--\n"); + + return 0; +} + +static int32_t nvt_read_CC(int32_t *xdata) +{ + uint8_t x_num = 0; + uint8_t y_num = 0; + uint32_t x = 0; + uint32_t y = 0; + int32_t iArrayIndex = 0; +#if TOUCH_KEY_NUM > 0 + int32_t k = 0; +#endif /* #if TOUCH_KEY_NUM > 0 */ + + NVT_LOG("++\n"); + + if (nvt_get_fw_pipe() == 0) + nvt_read_mdata(ts->mmap->DIFF_PIPE1_ADDR, ts->mmap->DIFF_BTN_PIPE1_ADDR); + else + nvt_read_mdata(ts->mmap->DIFF_PIPE0_ADDR, ts->mmap->DIFF_BTN_PIPE0_ADDR); + + nvt_get_mdata(xdata, &x_num, &y_num); + + for (y = 0; y < y_num; y++) { + for (x = 0; x < x_num; x++) { + iArrayIndex = y * x_num + x; + xdata[iArrayIndex] = (int16_t)xdata[iArrayIndex]; + } + } +#if TOUCH_KEY_NUM > 0 + for (k = 0; k < Key_Channel; k++) { + iArrayIndex = Y_Channel * X_Channel + k; + xdata[iArrayIndex] = (int16_t)xdata[iArrayIndex]; + } +#endif /* #if TOUCH_KEY_NUM > 0 */ + + NVT_LOG("--\n"); + + return 0; +} + +static void nvt_enable_noise_collect(int32_t frame_num) +{ + uint8_t buf[8] = {0}; + + //---set xdata index to EVENT BUF ADDR--- + nvt_set_page(I2C_FW_Address, ts->mmap->EVENT_BUF_ADDR | EVENT_MAP_HOST_CMD); + + //---enable noise collect--- + buf[0] = EVENT_MAP_HOST_CMD; + buf[1] = 0x47; + buf[2] = 0xAA; + buf[3] = frame_num; + buf[4] = 0x00; + CTP_I2C_WRITE(ts->client, I2C_FW_Address, buf, 5); +} + +static int32_t nvt_read_fw_noise(int32_t *xdata) +{ + uint8_t x_num = 0; + uint8_t y_num = 0; + uint32_t x = 0; + uint32_t y = 0; + int32_t iArrayIndex = 0; + int32_t frame_num = 0; +#if TOUCH_KEY_NUM > 0 + int32_t k = 0; +#endif /* #if TOUCH_KEY_NUM > 0 */ + + NVT_LOG("++\n"); + + //---Enter Test Mode--- + if (nvt_clear_fw_status()) { + return -EAGAIN; + } + + frame_num = PS_Config_Diff_Test_Frame / 10; + if (frame_num <= 0) + frame_num = 1; + printk("%s: frame_num=%d\n", __func__, frame_num); + nvt_enable_noise_collect(frame_num); + // need wait PS_Config_Diff_Test_Frame * 8.3ms + msleep(frame_num * 83); + + if (nvt_polling_hand_shake_status()) { + return -EAGAIN; + } + + if (nvt_get_fw_info()) { + return -EAGAIN; + } + + if (nvt_get_fw_pipe() == 0) + nvt_read_mdata(ts->mmap->DIFF_PIPE0_ADDR, ts->mmap->DIFF_BTN_PIPE0_ADDR); + else + nvt_read_mdata(ts->mmap->DIFF_PIPE1_ADDR, ts->mmap->DIFF_BTN_PIPE1_ADDR); + + nvt_get_mdata(xdata, &x_num, &y_num); + + for (y = 0; y < y_num; y++) { + for (x = 0; x < x_num; x++) { + iArrayIndex = y * x_num + x; + RawData_Diff_Max[iArrayIndex] = (int8_t)((xdata[iArrayIndex] >> 8) & 0xFF); + RawData_Diff_Min[iArrayIndex] = (int8_t)(xdata[iArrayIndex] & 0xFF); + } + } +#if TOUCH_KEY_NUM > 0 + for (k = 0; k < Key_Channel; k++) { + iArrayIndex = Y_Channel * X_Channel + k; + RawData_Diff_Max[iArrayIndex] = (int8_t)((xdata[iArrayIndex] >> 8) & 0xFF); + RawData_Diff_Min[iArrayIndex] = (int8_t)(xdata[iArrayIndex] & 0xFF); + } +#endif /* #if TOUCH_KEY_NUM > 0 */ + + //---Leave Test Mode--- + nvt_change_mode(NORMAL_MODE); + + NVT_LOG("--\n"); + + return 0; +} + +static void nvt_enable_open_test(void) +{ + uint8_t buf[8] = {0}; + + //---set xdata index to EVENT BUF ADDR--- + nvt_set_page(I2C_FW_Address, ts->mmap->EVENT_BUF_ADDR | EVENT_MAP_HOST_CMD); + + //---enable open test--- + buf[0] = EVENT_MAP_HOST_CMD; + buf[1] = 0x45; + buf[2] = 0xAA; + buf[3] = 0x02; + buf[4] = 0x00; + CTP_I2C_WRITE(ts->client, I2C_FW_Address, buf, 5); +} + +static void nvt_enable_short_test(void) +{ + uint8_t buf[8] = {0}; + + //---set xdata index to EVENT BUF ADDR--- + nvt_set_page(I2C_FW_Address, ts->mmap->EVENT_BUF_ADDR | EVENT_MAP_HOST_CMD); + + //---enable short test--- + buf[0] = EVENT_MAP_HOST_CMD; + buf[1] = 0x43; + buf[2] = 0xAA; + buf[3] = 0x02; + buf[4] = 0x00; + CTP_I2C_WRITE(ts->client, I2C_FW_Address, buf, 5); +} + +static int32_t nvt_read_fw_open(int32_t *xdata) +{ + uint32_t raw_pipe_addr = 0; + uint8_t *rawdata_buf = NULL; + uint32_t x = 0; + uint32_t y = 0; + uint8_t buf[128] = {0}; +#if TOUCH_KEY_NUM > 0 + uint32_t raw_btn_pipe_addr = 0; + int32_t k = 0; +#endif /* #if TOUCH_KEY_NUM > 0 */ + + NVT_LOG("++\n"); + + //---Enter Test Mode--- + if (nvt_clear_fw_status()) { + return -EAGAIN; + } + + nvt_enable_open_test(); + + if (nvt_polling_hand_shake_status()) { + return -EAGAIN; + } + +#if TOUCH_KEY_NUM > 0 + rawdata_buf = kzalloc((IC_X_CFG_SIZE * IC_Y_CFG_SIZE + IC_KEY_CFG_SIZE) * 2, GFP_KERNEL); +#else + rawdata_buf = kzalloc(IC_X_CFG_SIZE * IC_Y_CFG_SIZE * 2, GFP_KERNEL); +#endif /* #if TOUCH_KEY_NUM > 0 */ + if (!rawdata_buf) { + NVT_ERR("kzalloc for rawdata_buf failed!\n"); + return -ENOMEM; + } + + if (nvt_get_fw_pipe() == 0) + raw_pipe_addr = ts->mmap->RAW_PIPE0_ADDR; + else + raw_pipe_addr = ts->mmap->RAW_PIPE1_ADDR; + + for (y = 0; y < IC_Y_CFG_SIZE; y++) { + //---change xdata index--- + nvt_set_page(I2C_FW_Address, raw_pipe_addr + y * IC_X_CFG_SIZE * 2); + buf[0] = (uint8_t)((raw_pipe_addr + y * IC_X_CFG_SIZE * 2) & 0xFF); + CTP_I2C_READ(ts->client, I2C_FW_Address, buf, IC_X_CFG_SIZE * 2 + 1); + memcpy(rawdata_buf + y * IC_X_CFG_SIZE * 2, buf + 1, IC_X_CFG_SIZE * 2); + } +#if TOUCH_KEY_NUM > 0 + if (nvt_get_fw_pipe() == 0) + raw_btn_pipe_addr = ts->mmap->RAW_BTN_PIPE0_ADDR; + else + raw_btn_pipe_addr = ts->mmap->RAW_BTN_PIPE1_ADDR; + + //---change xdata index--- + nvt_set_page(I2C_FW_Address, raw_btn_pipe_addr); + buf[0] = (uint8_t)(raw_btn_pipe_addr & 0xFF); + CTP_I2C_READ(ts->client, I2C_FW_Address, buf, IC_KEY_CFG_SIZE * 2 + 1); + memcpy(rawdata_buf + IC_Y_CFG_SIZE * IC_X_CFG_SIZE * 2, buf + 1, IC_KEY_CFG_SIZE * 2); +#endif /* #if TOUCH_KEY_NUM > 0 */ + + for (y = 0; y < IC_Y_CFG_SIZE; y++) { + for (x = 0; x < IC_X_CFG_SIZE; x++) { + if ((AIN_Y[y] != 0xFF) && (AIN_X[x] != 0xFF)) { + xdata[AIN_Y[y] * X_Channel + AIN_X[x]] = (int16_t)((rawdata_buf[(y * IC_X_CFG_SIZE + x) * 2] + 256 * rawdata_buf[(y * IC_X_CFG_SIZE + x) * 2 + 1])); + } + } + } +#if TOUCH_KEY_NUM > 0 + for (k = 0; k < IC_KEY_CFG_SIZE; k++) { + if (AIN_KEY[k] != 0xFF) + xdata[Y_Channel * X_Channel + AIN_KEY[k]] = (int16_t)(rawdata_buf[(IC_Y_CFG_SIZE * IC_X_CFG_SIZE + k) * 2] + 256 * rawdata_buf[(IC_Y_CFG_SIZE * IC_X_CFG_SIZE + k) * 2 + 1]); + } +#endif /* #if TOUCH_KEY_NUM > 0 */ + + if (rawdata_buf) { + kfree(rawdata_buf); + rawdata_buf = NULL; + } + + //---Leave Test Mode--- + nvt_change_mode(NORMAL_MODE); + + NVT_LOG("--\n"); + + return 0; +} + +static int32_t nvt_read_fw_short(int32_t *xdata) +{ + uint32_t raw_pipe_addr = 0; + uint8_t *rawdata_buf = NULL; + uint32_t x = 0; + uint32_t y = 0; + uint8_t buf[128] = {0}; + int32_t iArrayIndex = 0; +#if TOUCH_KEY_NUM > 0 + uint32_t raw_btn_pipe_addr = 0; + int32_t k = 0; +#endif /* #if TOUCH_KEY_NUM > 0 */ + + NVT_LOG("++\n"); + + //---Enter Test Mode--- + if (nvt_clear_fw_status()) { + return -EAGAIN; + } + + nvt_enable_short_test(); + + if (nvt_polling_hand_shake_status()) { + return -EAGAIN; + } + +#if TOUCH_KEY_NUM > 0 + rawdata_buf = kzalloc((X_Channel * Y_Channel + Key_Channel) * 2, GFP_KERNEL); +#else + rawdata_buf = kzalloc(X_Channel * Y_Channel * 2, GFP_KERNEL); +#endif /* #if TOUCH_KEY_NUM > 0 */ + if (!rawdata_buf) { + NVT_ERR("kzalloc for rawdata_buf failed!\n"); + return -ENOMEM; + } + + if (nvt_get_fw_pipe() == 0) + raw_pipe_addr = ts->mmap->RAW_PIPE0_ADDR; + else + raw_pipe_addr = ts->mmap->RAW_PIPE1_ADDR; + + for (y = 0; y < Y_Channel; y++) { + //---change xdata index--- + nvt_set_page(I2C_FW_Address, raw_pipe_addr + y * X_Channel * 2); + buf[0] = (uint8_t)((raw_pipe_addr + y * X_Channel * 2) & 0xFF); + CTP_I2C_READ(ts->client, I2C_FW_Address, buf, X_Channel * 2 + 1); + memcpy(rawdata_buf + y * X_Channel * 2, buf + 1, X_Channel * 2); + } +#if TOUCH_KEY_NUM > 0 + if (nvt_get_fw_pipe() == 0) + raw_btn_pipe_addr = ts->mmap->RAW_BTN_PIPE0_ADDR; + else + raw_btn_pipe_addr = ts->mmap->RAW_BTN_PIPE1_ADDR; + + //---change xdata index--- + nvt_set_page(I2C_FW_Address, raw_btn_pipe_addr); + buf[0] = (uint8_t)(raw_btn_pipe_addr & 0xFF); + CTP_I2C_READ(ts->client, I2C_FW_Address, buf, Key_Channel * 2 + 1); + memcpy(rawdata_buf + Y_Channel * X_Channel * 2, buf + 1, Key_Channel * 2); +#endif /* #if TOUCH_KEY_NUM > 0 */ + + for (y = 0; y < Y_Channel; y++) { + for (x = 0; x < X_Channel; x++) { + iArrayIndex = y * X_Channel + x; + xdata[iArrayIndex] = (int16_t)(rawdata_buf[iArrayIndex * 2] + 256 * rawdata_buf[iArrayIndex * 2 + 1]); + } + } +#if TOUCH_KEY_NUM > 0 + for (k = 0; k < Key_Channel; k++) { + iArrayIndex = Y_Channel * X_Channel + k; + xdata[iArrayIndex] = (int16_t)(rawdata_buf[iArrayIndex * 2] + 256 * rawdata_buf[iArrayIndex * 2 + 1]); + } +#endif /* #if TOUCH_KEY_NUM > 0 */ + + if (rawdata_buf) { + kfree(rawdata_buf); + rawdata_buf = NULL; + } + + //---Leave Test Mode--- + nvt_change_mode(NORMAL_MODE); + + NVT_LOG("--\n"); + + return 0; +} + +/******************************************************* +Description: + Novatek touchscreen raw data test for each single point function. + +return: + Executive outcomes. 0---passed. negative---failed. +*******************************************************/ +static int32_t RawDataTest_SinglePoint_Sub(int32_t rawdata[], uint8_t RecordResult[], uint8_t x_ch, uint8_t y_ch, int32_t Rawdata_Limit_Postive[], int32_t Rawdata_Limit_Negative[]) +{ + int32_t i = 0; + int32_t j = 0; +#if TOUCH_KEY_NUM > 0 + int32_t k = 0; +#endif /* #if TOUCH_KEY_NUM > 0 */ + int32_t iArrayIndex = 0; + bool isPass = true; + + for (j = 0; j < y_ch; j++) { + for (i = 0; i < x_ch; i++) { + iArrayIndex = j * x_ch + i; + + RecordResult[iArrayIndex] = 0x00; // default value for PASS + + if(rawdata[iArrayIndex] > Rawdata_Limit_Postive[iArrayIndex]) + RecordResult[iArrayIndex] |= 0x01; + + if(rawdata[iArrayIndex] < Rawdata_Limit_Negative[iArrayIndex]) + RecordResult[iArrayIndex] |= 0x02; + } + } +#if TOUCH_KEY_NUM > 0 + for (k = 0; k < Key_Channel; k++) { + iArrayIndex = y_ch * x_ch + k; + + RecordResult[iArrayIndex] = 0x00; // default value for PASS + + if(rawdata[iArrayIndex] > Rawdata_Limit_Postive[iArrayIndex]) + RecordResult[iArrayIndex] |= 0x01; + + if(rawdata[iArrayIndex] < Rawdata_Limit_Negative[iArrayIndex]) + RecordResult[iArrayIndex] |= 0x02; + } +#endif /* #if TOUCH_KEY_NUM > 0 */ + + //---Check RecordResult--- + for (j = 0; j < y_ch; j++) { + for (i = 0; i < x_ch; i++) { + if (RecordResult[j * x_ch + i] != 0) { + isPass = false; + break; + } + } + } +#if TOUCH_KEY_NUM > 0 + for (k = 0; k < Key_Channel; k++) { + iArrayIndex = y_ch * x_ch + k; + if (RecordResult[iArrayIndex] != 0) { + isPass = false; + break; + } + } +#endif /* #if TOUCH_KEY_NUM > 0 */ + + if (!isPass) { + return -1; // FAIL + } else { + return 0; // PASS + } +} + +/******************************************************* +Description: + Novatek touchscreen print self-test result function. + +return: + n.a. +*******************************************************/ +void print_selftest_result(struct seq_file *m, int32_t TestResult, uint8_t RecordResult[], int32_t rawdata[], uint8_t x_len, uint8_t y_len) +{ + int32_t i = 0; + int32_t j = 0; + int32_t iArrayIndex = 0; +#if TOUCH_KEY_NUM > 0 + int32_t k = 0; +#endif /* #if TOUCH_KEY_NUM > 0 */ + + switch (TestResult) { + case 0: + nvt_mp_seq_printf(m, " PASS!\n"); + break; + + case 1: + nvt_mp_seq_printf(m, " ERROR! Read Data FAIL!\n"); + break; + + case -1: + nvt_mp_seq_printf(m, " FAIL!\n"); + nvt_mp_seq_printf(m, "RecordResult:\n"); + for (i = 0; i < y_len; i++) { + for (j = 0; j < x_len; j++) { + iArrayIndex = i * x_len + j; + seq_printf(m, "0x%02X, ", RecordResult[iArrayIndex]); + } + if (!nvt_mp_test_result_printed) + nvt_print_result_log_in_one_line(RecordResult + i * x_len, x_len); + nvt_mp_seq_printf(m, "\n"); + } +#if TOUCH_KEY_NUM > 0 + for (k = 0; k < Key_Channel; k++) { + iArrayIndex = y_len * x_len + k; + seq_printf(m, "0x%02X, ", RecordResult[iArrayIndex]); + } + if (!nvt_mp_test_result_printed) + nvt_print_result_log_in_one_line(RecordResult + y_len * x_len, Key_Channel); + nvt_mp_seq_printf(m, "\n"); +#endif /* #if TOUCH_KEY_NUM > 0 */ + nvt_mp_seq_printf(m, "ReadData:\n"); + for (i = 0; i < y_len; i++) { + for (j = 0; j < x_len; j++) { + iArrayIndex = i * x_len + j; + seq_printf(m, "%5d, ", rawdata[iArrayIndex]); + } + if (!nvt_mp_test_result_printed) + nvt_print_data_log_in_one_line(rawdata + i * x_len, x_len); + nvt_mp_seq_printf(m, "\n"); + } +#if TOUCH_KEY_NUM > 0 + for (k = 0; k < Key_Channel; k++) { + iArrayIndex = y_len * x_len + k; + seq_printf(m, "%5d, ", rawdata[iArrayIndex]); + } + if (!nvt_mp_test_result_printed) + nvt_print_data_log_in_one_line(rawdata + y_len * x_len, Key_Channel); + nvt_mp_seq_printf(m, "\n"); +#endif /* #if TOUCH_KEY_NUM > 0 */ + break; + } + nvt_mp_seq_printf(m, "\n"); +} + +/******************************************************* +Description: + Novatek touchscreen self-test sequence print show + function. + +return: + Executive outcomes. 0---succeed. +*******************************************************/ +static int32_t c_show_selftest(struct seq_file *m, void *v) +{ + NVT_LOG("++\n"); + + nvt_mp_seq_printf(m, "FW Version: %d\n\n", fw_ver); + + nvt_mp_seq_printf(m, "Short Test"); + + print_selftest_result(m, TestResult_Short, RecordResult_Short, RawData_Short, X_Channel, Y_Channel); + + nvt_mp_seq_printf(m, "Open Test"); + print_selftest_result(m, TestResult_Open, RecordResult_Open, RawData_Open, X_Channel, Y_Channel); + + nvt_mp_seq_printf(m, "FW Rawdata Test"); + if ((TestResult_FW_Rawdata == 0) || (TestResult_FW_Rawdata == 1)) { + print_selftest_result(m, TestResult_FWMutual, RecordResult_FWMutual, RawData_FWMutual, X_Channel, Y_Channel); + } else { // TestResult_FW_Rawdata is -1 + nvt_mp_seq_printf(m, " FAIL!\n"); + if (TestResult_FWMutual == -1) { + nvt_mp_seq_printf(m, "FW Mutual"); + print_selftest_result(m, TestResult_FWMutual, RecordResult_FWMutual, RawData_FWMutual, X_Channel, Y_Channel); + } + if (TestResult_FW_CC == -1) { + nvt_mp_seq_printf(m, "FW CC"); + print_selftest_result(m, TestResult_FW_CC, RecordResult_FW_CC, RawData_FW_CC, X_Channel, Y_Channel); + } + } + + nvt_mp_seq_printf(m, "Noise Test"); + if ((TestResult_Noise == 0) || (TestResult_Noise == 1)) { + print_selftest_result(m, TestResult_FW_DiffMax, RecordResult_FW_DiffMax, RawData_Diff_Max, X_Channel, Y_Channel); + } else { // TestResult_Noise is -1 + nvt_mp_seq_printf(m, " FAIL!\n"); + + if (TestResult_FW_DiffMax == -1) { + nvt_mp_seq_printf(m, "FW Diff Max"); + print_selftest_result(m, TestResult_FW_DiffMax, RecordResult_FW_DiffMax, RawData_Diff_Max, X_Channel, Y_Channel); + } + if (TestResult_FW_DiffMin == -1) { + nvt_mp_seq_printf(m, "FW Diff Min"); + print_selftest_result(m, TestResult_FW_DiffMin, RecordResult_FW_DiffMin, RawData_Diff_Min, X_Channel, Y_Channel); + } + } + + nvt_mp_test_result_printed = 1; + + NVT_LOG("--\n"); + + return 0; +} + +/******************************************************* +Description: + Novatek touchscreen self-test sequence print start + function. + +return: + Executive outcomes. 1---call next function. + NULL---not call next function and sequence loop + stop. +*******************************************************/ +static void *c_start(struct seq_file *m, loff_t *pos) +{ + return *pos < 1 ? (void *)1 : NULL; +} + +/******************************************************* +Description: + Novatek touchscreen self-test sequence print next + function. + +return: + Executive outcomes. NULL---no next and call sequence + stop function. +*******************************************************/ +static void *c_next(struct seq_file *m, void *v, loff_t *pos) +{ + ++*pos; + return NULL; +} + +/******************************************************* +Description: + Novatek touchscreen self-test sequence print stop + function. + +return: + n.a. +*******************************************************/ +static void c_stop(struct seq_file *m, void *v) +{ + return; +} + +const struct seq_operations nvt_selftest_seq_ops = { + .start = c_start, + .next = c_next, + .stop = c_stop, + .show = c_show_selftest +}; + +/******************************************************* +Description: + Novatek touchscreen /proc/nvt_selftest open function. + +return: + Executive outcomes. 0---succeed. negative---failed. +*******************************************************/ +static int32_t nvt_selftest_open(struct inode *inode, struct file *file) +{ + struct device_node *np = ts->client->dev.of_node; + + memset(mpcriteria, 0, PAGE_SIZE * sizeof(mpcriteria[0])); + TestResult_Short = 0; + TestResult_Open = 0; + TestResult_FW_Rawdata = 0; + TestResult_FWMutual = 0; + TestResult_FW_CC = 0; + TestResult_Noise = 0; + TestResult_FW_DiffMax = 0; + TestResult_FW_DiffMin = 0; + + NVT_LOG("++\n"); + + if (mutex_lock_interruptible(&ts->lock)) { + return -ERESTARTSYS; + } + +#if NVT_TOUCH_ESD_PROTECT + nvt_esd_check_enable(false); +#endif /* #if NVT_TOUCH_ESD_PROTECT */ + + if (nvt_get_fw_info()) { + mutex_unlock(&ts->lock); + NVT_ERR("get fw info failed!\n"); + return -EAGAIN; + } + + fw_ver = ts->fw_ver; + + /* Parsing criteria from dts */ + if(of_property_read_bool(np, "novatek,mp-support-dt")) { + /* + * Parsing Criteria by Novatek PID + * The string rule is "novatek-mp-criteria-" + * nvt_pid is 2 bytes (show hex). + * + * Ex. nvt_pid = 500A + * mpcriteria = "novatek-mp-criteria-500A" + */ + snprintf(mpcriteria, PAGE_SIZE, "novatek-mp-criteria-%04X", ts->nvt_pid); + + if (nvt_mp_parse_dt(np, mpcriteria)) { + mutex_unlock(&ts->lock); + NVT_ERR("mp parse device tree failed!\n"); + return -EINVAL; + } + } else { + NVT_LOG("Not found novatek,mp-support-dt, use default setting\n"); + //---Print Test Criteria--- + nvt_print_criteria(); + } + + if (nvt_switch_FreqHopEnDis(FREQ_HOP_DISABLE)) { + mutex_unlock(&ts->lock); + NVT_ERR("switch frequency hopping disable failed!\n"); + return -EAGAIN; + } + + if (nvt_check_fw_reset_state(RESET_STATE_NORMAL_RUN)) { + mutex_unlock(&ts->lock); + NVT_ERR("check fw reset state failed!\n"); + return -EAGAIN; + } + + msleep(100); + + //---Enter Test Mode--- + if (nvt_clear_fw_status()) { + mutex_unlock(&ts->lock); + NVT_ERR("clear fw status failed!\n"); + return -EAGAIN; + } + + nvt_change_mode(MP_MODE_CC); + + if (nvt_check_fw_status()) { + mutex_unlock(&ts->lock); + NVT_ERR("check fw status failed!\n"); + return -EAGAIN; + } + + //---FW Rawdata Test--- + if (nvt_read_baseline(RawData_FWMutual) != 0) { + TestResult_FWMutual = 1; + } else { + TestResult_FWMutual = RawDataTest_SinglePoint_Sub(RawData_FWMutual, RecordResult_FWMutual, X_Channel, Y_Channel, + PS_Config_Lmt_FW_Rawdata_P, PS_Config_Lmt_FW_Rawdata_N); + } + if (nvt_read_CC(RawData_FW_CC) != 0) { + TestResult_FW_CC = 1; + } else { + TestResult_FW_CC = RawDataTest_SinglePoint_Sub(RawData_FW_CC, RecordResult_FW_CC, X_Channel, Y_Channel, + PS_Config_Lmt_FW_CC_P, PS_Config_Lmt_FW_CC_N); + } + + if ((TestResult_FWMutual == 1) || (TestResult_FW_CC == 1)) { + TestResult_FW_Rawdata = 1; + } else { + if ((TestResult_FWMutual == -1) || (TestResult_FW_CC == -1)) + TestResult_FW_Rawdata = -1; + else + TestResult_FW_Rawdata = 0; + } + + //---Leave Test Mode--- + nvt_change_mode(NORMAL_MODE); + + //---Noise Test--- + if (nvt_read_fw_noise(RawData_Diff) != 0) { + TestResult_Noise = 1; // 1: ERROR + TestResult_FW_DiffMax = 1; + TestResult_FW_DiffMin = 1; + } else { + TestResult_FW_DiffMax = RawDataTest_SinglePoint_Sub(RawData_Diff_Max, RecordResult_FW_DiffMax, X_Channel, Y_Channel, + PS_Config_Lmt_FW_Diff_P, PS_Config_Lmt_FW_Diff_N); + + TestResult_FW_DiffMin = RawDataTest_SinglePoint_Sub(RawData_Diff_Min, RecordResult_FW_DiffMin, X_Channel, Y_Channel, + PS_Config_Lmt_FW_Diff_P, PS_Config_Lmt_FW_Diff_N); + + if ((TestResult_FW_DiffMax == -1) || (TestResult_FW_DiffMin == -1)) + TestResult_Noise = -1; + else + TestResult_Noise = 0; + } + + //--Short Test--- + if (nvt_read_fw_short(RawData_Short) != 0) { + TestResult_Short = 1; // 1:ERROR + } else { + //---Self Test Check --- // 0:PASS, -1:FAIL + TestResult_Short = RawDataTest_SinglePoint_Sub(RawData_Short, RecordResult_Short, X_Channel, Y_Channel, + PS_Config_Lmt_Short_Rawdata_P, PS_Config_Lmt_Short_Rawdata_N); + } + + //---Open Test--- + if (nvt_read_fw_open(RawData_Open) != 0) { + TestResult_Open = 1; // 1:ERROR + } else { + //---Self Test Check --- // 0:PASS, -1:FAIL + TestResult_Open = RawDataTest_SinglePoint_Sub(RawData_Open, RecordResult_Open, X_Channel, Y_Channel, + PS_Config_Lmt_Open_Rawdata_P, PS_Config_Lmt_Open_Rawdata_N); + } + + //---Reset IC--- + nvt_bootloader_reset(); + + mutex_unlock(&ts->lock); + + NVT_LOG("--\n"); + + nvt_mp_test_result_printed = 0; + + return seq_open(file, &nvt_selftest_seq_ops); +} + +static const struct proc_ops nvt_selftest_fops = { + .proc_open = nvt_selftest_open, + .proc_read = seq_read, + .proc_lseek = seq_lseek, + .proc_release = seq_release, +}; + +#ifdef CONFIG_OF +/******************************************************* +Description: + Novatek touchscreen parse AIN setting for array type. + +return: + n.a. +*******************************************************/ +int32_t nvt_mp_parse_ain(struct device_node *np, const char *name, uint8_t *array, int32_t size) +{ + struct property *data; + int32_t len, ret; + int32_t tmp[40]; + int32_t i; + + data = of_find_property(np, name, &len); + len /= sizeof(u32); + if ((!data) || (!len) || (len != size)) { + NVT_ERR("error find %s. len=%d\n", name, len); + return -1; + } else { + NVT_LOG("%s. len=%d\n", name, len); + ret = of_property_read_u32_array(np, name, tmp, len); + if (ret) { + NVT_ERR("error reading %s. ret=%d\n", name, ret); + return -1; + } + + for (i = 0; i < len; i++) + array[i] = tmp[i]; + +#if NVT_DEBUG + printk("[NVT-ts] %s = ", name); + nvt_print_result_log_in_one_line(array, len); + printk("\n"); +#endif + } + + return 0; +} + +/******************************************************* +Description: + Novatek touchscreen parse criterion for u32 type. + +return: + n.a. +*******************************************************/ +int32_t nvt_mp_parse_u32(struct device_node *np, const char *name, int32_t *para) +{ + int32_t ret; + + ret = of_property_read_u32(np, name, para); + if (ret) { + NVT_ERR("error reading %s. ret=%d\n", name, ret); + return -1; + } else { +#if NVT_DEBUG + NVT_LOG("%s=%d\n", name, *para); +#endif + } + + return 0; +} + +/******************************************************* +Description: + Novatek touchscreen parse criterion for array type. + +return: + n.a. +*******************************************************/ +int32_t nvt_mp_parse_array(struct device_node *np, const char *name, int32_t *array, + int32_t size) +{ + struct property *data; + int32_t len, ret; +#if NVT_DEBUG + int32_t j = 0; +#endif + + data = of_find_property(np, name, &len); + len /= sizeof(u32); + if ((!data) || (!len) || (len < size)) { + NVT_ERR("error find %s. len=%d\n", name, len); + return -1; + } else { + NVT_LOG("%s. len=%d\n", name, len); + ret = of_property_read_u32_array(np, name, array, len); + if (ret) { + NVT_ERR("error reading %s. ret=%d\n", name, ret); + return -1; + } + +#if NVT_DEBUG + NVT_LOG("%s =\n", name); + for (j = 0; j < Y_Channel; j++) { + nvt_print_data_log_in_one_line(array + j * X_Channel, X_Channel); + printk("\n"); + } +#if TOUCH_KEY_NUM > 0 + nvt_print_data_log_in_one_line(array + Y_Channel * X_Channel, Key_Channel); + printk("\n"); +#endif +#endif + } + + return 0; +} + +/******************************************************* +Description: + Novatek touchscreen parse device tree mp function. + +return: + n.a. +*******************************************************/ +int32_t nvt_mp_parse_dt(struct device_node *root, const char *node_compatible) +{ + struct device_node *np = root; + struct device_node *child = NULL; + + NVT_LOG("Parse mp criteria for node %s\n", node_compatible); + + /* find each MP sub-nodes */ + for_each_child_of_node(root, child) { + /* find the specified node */ + if (of_device_is_compatible(child, node_compatible)) { + NVT_LOG("found child node %s\n", node_compatible); + np = child; + break; + } + } + if (child == NULL) { + NVT_ERR("Not found compatible node %s!\n", node_compatible); + return -1; + } + + /* MP Config*/ + if (nvt_mp_parse_u32(np, "IC_X_CFG_SIZE", &IC_X_CFG_SIZE)) + return -1; + + if (nvt_mp_parse_u32(np, "IC_Y_CFG_SIZE", &IC_Y_CFG_SIZE)) + return -1; + +#if TOUCH_KEY_NUM > 0 + if (nvt_mp_parse_u32(np, "IC_KEY_CFG_SIZE", &IC_KEY_CFG_SIZE)) + return -1; +#endif + + if (nvt_mp_parse_u32(np, "X_Channel", &X_Channel)) + return -1; + + if (nvt_mp_parse_u32(np, "Y_Channel", &Y_Channel)) + return -1; + + if (nvt_mp_parse_ain(np, "AIN_X", AIN_X, IC_X_CFG_SIZE)) + return -1; + + if (nvt_mp_parse_ain(np, "AIN_Y", AIN_Y, IC_Y_CFG_SIZE)) + return -1; + +#if TOUCH_KEY_NUM > 0 + if (nvt_mp_parse_ain(np, "AIN_KEY", AIN_KEY, IC_KEY_CFG_SIZE)) + return -1; +#endif + + /* MP Criteria */ + if (nvt_mp_parse_array(np, "PS_Config_Lmt_Short_Rawdata_P", PS_Config_Lmt_Short_Rawdata_P, + X_Channel * Y_Channel + Key_Channel)) + return -1; + + if (nvt_mp_parse_array(np, "PS_Config_Lmt_Short_Rawdata_N", PS_Config_Lmt_Short_Rawdata_N, + X_Channel * Y_Channel + Key_Channel)) + return -1; + + if (nvt_mp_parse_array(np, "PS_Config_Lmt_Open_Rawdata_P", PS_Config_Lmt_Open_Rawdata_P, + X_Channel * Y_Channel + Key_Channel)) + return -1; + + if (nvt_mp_parse_array(np, "PS_Config_Lmt_Open_Rawdata_N", PS_Config_Lmt_Open_Rawdata_N, + X_Channel * Y_Channel + Key_Channel)) + return -1; + + if (nvt_mp_parse_array(np, "PS_Config_Lmt_FW_Rawdata_P", PS_Config_Lmt_FW_Rawdata_P, + X_Channel * Y_Channel + Key_Channel)) + return -1; + + if (nvt_mp_parse_array(np, "PS_Config_Lmt_FW_Rawdata_N", PS_Config_Lmt_FW_Rawdata_N, + X_Channel * Y_Channel + Key_Channel)) + return -1; + + if (nvt_mp_parse_array(np, "PS_Config_Lmt_FW_CC_P", PS_Config_Lmt_FW_CC_P, + X_Channel * Y_Channel + Key_Channel)) + return -1; + + if (nvt_mp_parse_array(np, "PS_Config_Lmt_FW_CC_N", PS_Config_Lmt_FW_CC_N, + X_Channel * Y_Channel + Key_Channel)) + return -1; + + if (nvt_mp_parse_array(np, "PS_Config_Lmt_FW_Diff_P", PS_Config_Lmt_FW_Diff_P, + X_Channel * Y_Channel + Key_Channel)) + return -1; + + if (nvt_mp_parse_array(np, "PS_Config_Lmt_FW_Diff_N", PS_Config_Lmt_FW_Diff_N, + X_Channel * Y_Channel + Key_Channel)) + return -1; + + if (nvt_mp_parse_u32(np, "PS_Config_Diff_Test_Frame", &PS_Config_Diff_Test_Frame)) + return -1; + + NVT_LOG("Parse mp criteria done!\n"); + + return 0; +} +#endif /* #ifdef CONFIG_OF */ + +/******************************************************* +Description: + Novatek touchscreen MP function proc. file node + initial function. + +return: + Executive outcomes. 0---succeed. -1---failed. +*******************************************************/ +int32_t nvt_mp_proc_init(void) +{ + NVT_proc_selftest_entry = proc_create("nvt_selftest", 0444, NULL, &nvt_selftest_fops); + if (NVT_proc_selftest_entry == NULL) { + NVT_ERR("create /proc/nvt_selftest Failed!\n"); + return -1; + } else { + if(nvt_mp_buffer_init()) { + NVT_ERR("Allocate mp memory failed\n"); + return -1; + } + else { + NVT_LOG("create /proc/nvt_selftest Succeeded!\n"); + } + return 0; + } +} + +/******************************************************* +Description: + Novatek touchscreen MP function proc. file node + deinitial function. + +return: + n.a. +*******************************************************/ +void nvt_mp_proc_deinit(void) +{ + nvt_mp_buffer_deinit(); + + if (NVT_proc_selftest_entry != NULL) { + remove_proc_entry("nvt_selftest", NULL); + NVT_proc_selftest_entry = NULL; + NVT_LOG("Removed /proc/%s\n", "nvt_selftest"); + } +} +#endif /* #if NVT_TOUCH_MP */ diff --git a/qcom/opensource/touch-drivers/nt36xxx/nt36xxx_mp_ctrlram.h b/qcom/opensource/touch-drivers/nt36xxx/nt36xxx_mp_ctrlram.h new file mode 100644 index 0000000000..69c27cabcc --- /dev/null +++ b/qcom/opensource/touch-drivers/nt36xxx/nt36xxx_mp_ctrlram.h @@ -0,0 +1,460 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2010 - 2018 Novatek, Inc. + * + * $Revision: 46179 $ + * $Date: 2019-06-14 13:47:17 +0800 (Fri, 14 Jun 2019) $ + * + * 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. + * + */ + +#if NVT_TOUCH_MP + +static uint32_t IC_X_CFG_SIZE = 18; +static uint32_t IC_Y_CFG_SIZE = 36; +static uint32_t IC_KEY_CFG_SIZE = 4; +static uint32_t X_Channel = 18; +static uint32_t Y_Channel = 36; +static uint32_t Key_Channel = TOUCH_KEY_NUM; +static uint8_t AIN_X[40] = + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}; +static uint8_t AIN_Y[40] = + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35}; + +#if TOUCH_KEY_NUM > 0 +static uint8_t AIN_KEY[8] = {0, 1, 2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; +#endif /* #if TOUCH_KEY_NUM > 0 */ + +static int32_t PS_Config_Lmt_Short_Rawdata_P[40 * 40] = { + 14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008, + 14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008, + 14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008, + 14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008, + 14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008, + 14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008, + 14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008, + 14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008, + 14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008, + 14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008, + 14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008, + 14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008, + 14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008, + 14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008, + 14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008, + 14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008, + 14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008, + 14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008, + 14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008, + 14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008, + 14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008, + 14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008, + 14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008, + 14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008, + 14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008, + 14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008, + 14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008, + 14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008, + 14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008, + 14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008, + 14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008, + 14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008, + 14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008, + 14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008, + 14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008, + 14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008,14008, +#if TOUCH_KEY_NUM > 0 + 14008,14008,14008, +#endif /* #if TOUCH_KEY_NUM > 0 */ +}; + +static int32_t PS_Config_Lmt_Short_Rawdata_N[40 * 40] = { + 10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000, + 10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000, + 10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000, + 10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000, + 10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000, + 10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000, + 10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000, + 10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000, + 10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000, + 10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000, + 10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000, + 10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000, + 10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000, + 10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000, + 10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000, + 10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000, + 10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000, + 10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000, + 10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000, + 10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000, + 10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000, + 10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000, + 10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000, + 10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000, + 10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000, + 10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000, + 10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000, + 10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000, + 10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000, + 10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000, + 10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000, + 10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000, + 10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000, + 10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000, + 10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000, + 10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000, +#if TOUCH_KEY_NUM > 0 + 10000,10000,10000, +#endif /* #if TOUCH_KEY_NUM > 0 */ +}; + +static int32_t PS_Config_Lmt_Open_Rawdata_P[40 * 40] = { + 5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120, + 5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120, + 5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120, + 5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120, + 5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120, + 5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120, + 5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120, + 5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120, + 5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120, + 5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120, + 5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120, + 5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120, + 5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120, + 5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120, + 5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120, + 5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120, + 5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120, + 5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120, + 5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120, + 5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120, + 5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120, + 5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120, + 5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120, + 5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120, + 5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120, + 5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120, + 5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120, + 5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120, + 5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120, + 5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120, + 5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120, + 5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120, + 5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120, + 5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120, + 5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120, + 5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120,5120, +#if TOUCH_KEY_NUM > 0 + 5120,5120,5120, +#endif /* #if TOUCH_KEY_NUM > 0 */ +}; + +static int32_t PS_Config_Lmt_Open_Rawdata_N[40 * 40] = { + 50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50, + 50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50, + 50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50, + 50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50, + 50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50, + 50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50, + 50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50, + 50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50, + 50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50, + 50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50, + 50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50, + 50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50, + 50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50, + 50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50, + 50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50, + 50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50, + 50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50, + 50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50, + 50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50, + 50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50, + 50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50, + 50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50, + 50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50, + 50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50, + 50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50, + 50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50, + 50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50, + 50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50, + 50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50, + 50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50, + 50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50, + 50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50, + 50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50, + 50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50, + 50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50, + 50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50, +#if TOUCH_KEY_NUM > 0 + 50,50,50, +#endif /* #if TOUCH_KEY_NUM > 0 */ +}; + +static int32_t PS_Config_Lmt_FW_Rawdata_P[40 * 40] = { + 2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560, + 2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560, + 2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560, + 2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560, + 2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560, + 2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560, + 2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560, + 2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560, + 2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560, + 2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560, + 2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560, + 2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560, + 2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560, + 2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560, + 2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560, + 2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560, + 2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560, + 2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560, + 2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560, + 2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560, + 2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560, + 2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560, + 2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560, + 2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560, + 2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560, + 2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560, + 2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560, + 2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560, + 2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560, + 2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560, + 2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560, + 2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560, + 2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560, + 2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560, + 2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560, + 2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560,2560, +#if TOUCH_KEY_NUM > 0 + 2560,2560,2560, +#endif /* #if TOUCH_KEY_NUM > 0 */ +}; + +static int32_t PS_Config_Lmt_FW_Rawdata_N[40 * 40] = { + 240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240, + 240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240, + 240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240, + 240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240, + 240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240, + 240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240, + 240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240, + 240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240, + 240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240, + 240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240, + 240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240, + 240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240, + 240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240, + 240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240, + 240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240, + 240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240, + 240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240, + 240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240, + 240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240, + 240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240, + 240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240, + 240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240, + 240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240, + 240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240, + 240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240, + 240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240, + 240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240, + 240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240, + 240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240, + 240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240, + 240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240, + 240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240, + 240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240, + 240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240, + 240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240, + 240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240, +#if TOUCH_KEY_NUM > 0 + 240,240,240, +#endif /* #if TOUCH_KEY_NUM > 0 */ +}; + +static int32_t PS_Config_Lmt_FW_CC_P[40 * 40] = { + 314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314, + 314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314, + 314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314, + 314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314, + 314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314, + 314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314, + 314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314, + 314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314, + 314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314, + 314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314, + 314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314, + 314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314, + 314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314, + 314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314, + 314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314, + 314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314, + 314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314, + 314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314, + 314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314, + 314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314, + 314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314, + 314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314, + 314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314, + 314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314, + 314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314, + 314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314, + 314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314, + 314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314, + 314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314, + 314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314, + 314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314, + 314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314, + 314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314, + 314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314, + 314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314, + 314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314,314, +#if TOUCH_KEY_NUM > 0 + 314,314,314, +#endif /* #if TOUCH_KEY_NUM > 0 */ +}; + +static int32_t PS_Config_Lmt_FW_CC_N[40 * 40] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +#if TOUCH_KEY_NUM > 0 + 0,0,0, +#endif /* #if TOUCH_KEY_NUM > 0 */ +}; + +static int32_t PS_Config_Lmt_FW_Diff_P[40 * 40] = { + 75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75, + 75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75, + 75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75, + 75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75, + 75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75, + 75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75, + 75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75, + 75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75, + 75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75, + 75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75, + 75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75, + 75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75, + 75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75, + 75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75, + 75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75, + 75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75, + 75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75, + 75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75, + 75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75, + 75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75, + 75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75, + 75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75, + 75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75, + 75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75, + 75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75, + 75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75, + 75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75, + 75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75, + 75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75, + 75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75, + 75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75, + 75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75, + 75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75, + 75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75, + 75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75, + 75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75, +#if TOUCH_KEY_NUM > 0 + 75,75,75, +#endif /* #if TOUCH_KEY_NUM > 0 */ +}; + +static int32_t PS_Config_Lmt_FW_Diff_N[40 *40] = { + -75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75, + -75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75, + -75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75, + -75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75, + -75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75, + -75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75, + -75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75, + -75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75, + -75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75, + -75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75, + -75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75, + -75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75, + -75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75, + -75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75, + -75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75, + -75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75, + -75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75, + -75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75, + -75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75, + -75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75, + -75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75, + -75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75, + -75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75, + -75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75, + -75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75, + -75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75, + -75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75, + -75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75, + -75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75, + -75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75, + -75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75, + -75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75, + -75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75, + -75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75, + -75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75, + -75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75,-75, +#if TOUCH_KEY_NUM > 0 + -75,-75,-75, +#endif /* #if TOUCH_KEY_NUM > 0 */ +}; + +static int32_t PS_Config_Diff_Test_Frame = 50; + +#endif /* #if NVT_TOUCH_MP */ diff --git a/qcom/opensource/touch-drivers/nt36xxx/nt36xxx_spi.c b/qcom/opensource/touch-drivers/nt36xxx/nt36xxx_spi.c new file mode 100644 index 0000000000..c26dbd70b2 --- /dev/null +++ b/qcom/opensource/touch-drivers/nt36xxx/nt36xxx_spi.c @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#define NVT_NT36XXX_SPI + +#include "nt36xxx.c" diff --git a/qcom/opensource/touch-drivers/nt36xxx/nt36xxx_spi_ext_proc.c b/qcom/opensource/touch-drivers/nt36xxx/nt36xxx_spi_ext_proc.c new file mode 100644 index 0000000000..a6ad2f6231 --- /dev/null +++ b/qcom/opensource/touch-drivers/nt36xxx/nt36xxx_spi_ext_proc.c @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#define NVT_NT36XXX_SPI + +#include "nt36xxx_ext_proc.c" diff --git a/qcom/opensource/touch-drivers/nt36xxx/nt36xxx_spi_fw_update.c b/qcom/opensource/touch-drivers/nt36xxx/nt36xxx_spi_fw_update.c new file mode 100644 index 0000000000..2a6c20e2fb --- /dev/null +++ b/qcom/opensource/touch-drivers/nt36xxx/nt36xxx_spi_fw_update.c @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#define NVT_NT36XXX_SPI + +#include "nt36xxx_fw_update.c" diff --git a/qcom/opensource/touch-drivers/pt/Makefile b/qcom/opensource/touch-drivers/pt/Makefile new file mode 100644 index 0000000000..da836e6f10 --- /dev/null +++ b/qcom/opensource/touch-drivers/pt/Makefile @@ -0,0 +1,53 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the touchscreen drivers. +# + +# Each configuration option enables a list of files. +obj-$(CONFIG_TOUCHSCREEN_PARADE) += pt.o +pt-y := pt_core.o pt_mt_common.o +pt-$(CONFIG_TOUCHSCREEN_PARADE_MT_A) += pt_mta.o +pt-$(CONFIG_TOUCHSCREEN_PARADE_MT_B) += pt_mtb.o +pt-$(CONFIG_TOUCHSCREEN_PARADE_BUTTON) += pt_btn.o +pt-$(CONFIG_TOUCHSCREEN_PARADE_PROXIMITY) += pt_proximity.o +obj-$(CONFIG_TOUCHSCREEN_PARADE_DEVICETREE_SUPPORT) += pt_devtree.o +ifdef CONFIG_TOUCHSCREEN_PARADE +obj-y += pt_platform.o +endif +obj-$(CONFIG_TOUCHSCREEN_PARADE_I2C) += pt_i2c.o +obj-$(CONFIG_TOUCHSCREEN_PARADE_SPI) += pt_spi.o +obj-$(CONFIG_TOUCHSCREEN_PARADE_DEBUG_MDL) += pt_debug.o +obj-$(CONFIG_TOUCHSCREEN_PARADE_LOADER) += pt_loader.o +obj-$(CONFIG_TOUCHSCREEN_PARADE_DEVICE_ACCESS) += pt_device_access.o + +ifeq ($(CONFIG_TOUCHSCREEN_PARADE_DEBUG),y) +CFLAGS_pt_core.o += -DDEBUG +CFLAGS_pt_i2c.o += -DDEBUG +CFLAGS_pt_spi.o += -DDEBUG +CFLAGS_pt_mta.o += -DDEBUG +CFLAGS_pt_mtb.o += -DDEBUG +CFLAGS_pt_mt_common.o += -DDEBUG +CFLAGS_pt_btn.o += -DDEBUG +CFLAGS_pt_proximity.o += -DDEBUG +CFLAGS_pt_device_access.o += -DDEBUG +CFLAGS_pt_loader.o += -DDEBUG +CFLAGS_pt_debug.o += -DDEBUG +CFLAGS_pt_devtree.o += -DDEBUG +CFLAGS_pt_platform.o += -DDEBUG +endif + +ifeq ($(CONFIG_TOUCHSCREEN_PARADE_VDEBUG),y) +CFLAGS_pt_core.o += -DVERBOSE_DEBUG +CFLAGS_pt_i2c.o += -DVERBOSE_DEBUG +CFLAGS_pt_spi.o += -DVERBOSE_DEBUG +CFLAGS_pt_mta.o += -DVERBOSE_DEBUG +CFLAGS_pt_mtb.o += -DVERBOSE_DEBUG +CFLAGS_pt_mt_common.o += -DVERBOSE_DEBUG +CFLAGS_pt_btn.o += -DVERBOSE_DEBUG +CFLAGS_pt_proximity.o += -DVERBOSE_DEBUG +CFLAGS_pt_device_access.o += -DVERBOSE_DEBUG +CFLAGS_pt_loader.o += -DVERBOSE_DEBUG +CFLAGS_pt_debug.o += -DVERBOSE_DEBUG +CFLAGS_pt_devtree.o += -DVERBOSE_DEBUG +CFLAGS_pt_platform.o += -DVERBOSE_DEBUG +endif diff --git a/qcom/opensource/touch-drivers/pt/pt_btn.c b/qcom/opensource/touch-drivers/pt/pt_btn.c new file mode 100644 index 0000000000..17d2a93b13 --- /dev/null +++ b/qcom/opensource/touch-drivers/pt/pt_btn.c @@ -0,0 +1,522 @@ +/* + * pt_btn.c + * Parade TrueTouch(TM) Standard Product CapSense Reports Module. + * For use with Parade touchscreen controllers. + * Supported parts include: + * TMA5XX + * TMA448 + * TMA445A + * TT21XXX + * TT31XXX + * TT4XXXX + * TT7XXX + * TC3XXX + * + * Copyright (C) 2015-2020 Parade Technologies + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * 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. + * + * Contact Parade Technologies at www.paradetech.com + */ + +#include "pt_regs.h" + +/******************************************************************************* + * FUNCTION: pt_btn_key_action + * + * SUMMARY: Reports key event + * + * PARAMETERS: + * *bd - pointer to button data structure + * btn_no - number of button + * btn_state - state of button + ******************************************************************************/ +static inline void pt_btn_key_action(struct pt_btn_data *bd, + int btn_no, int btn_state) +{ + struct device *dev = bd->dev; + struct pt_sysinfo *si = bd->si; + + if (!si->btn[btn_no].enabled || + si->btn[btn_no].state == btn_state) + return; + + si->btn[btn_no].state = btn_state; + input_report_key(bd->input, si->btn[btn_no].key_code, btn_state); + input_sync(bd->input); + + pt_debug(dev, DL_INFO, "%s: btn=%d key_code=%d %s\n", + __func__, btn_no, si->btn[btn_no].key_code, + btn_state == PT_BTN_PRESSED ? + "PRESSED" : "RELEASED"); +} + +/******************************************************************************* + * FUNCTION: pt_get_btn_touches + * + * SUMMARY: Parse and report key event + * + * PARAMETERS: + * *bd - pointer to button data structure + ******************************************************************************/ +static void pt_get_btn_touches(struct pt_btn_data *bd) +{ + struct pt_sysinfo *si = bd->si; + int num_btns = si->num_btns; + int cur_btn; + int cur_btn_state; + + for (cur_btn = 0; cur_btn < num_btns; cur_btn++) { + /* Get current button state */ + cur_btn_state = (si->xy_data[0] >> (cur_btn * PT_BITS_PER_BTN)) + & PT_NUM_BTN_EVENT_ID; + + pt_btn_key_action(bd, cur_btn, cur_btn_state); + } +} + +/******************************************************************************* + * FUNCTION: pt_btn_lift_all + * + * SUMMARY: Reports button liftoff action + * + * PARAMETERS: + * *bd - pointer to button data structure + ******************************************************************************/ +static void pt_btn_lift_all(struct pt_btn_data *bd) +{ + struct pt_sysinfo *si = bd->si; + int i; + + if (!si || si->num_btns == 0) + return; + + for (i = 0; i < si->num_btns; i++) + pt_btn_key_action(bd, i, PT_BTN_RELEASED); +} + +/******************************************************************************* + * FUNCTION: pt_xy_worker + * + * SUMMARY: Read xy_data for all current CapSense button touches + * + * RETURN: + * 0 = success + * + * PARAMETERS: + * *bd - pointer to button data structure + ******************************************************************************/ +static int pt_xy_worker(struct pt_btn_data *bd) +{ + struct pt_sysinfo *si = bd->si; + + /* extract button press/release touch information */ + if (si->num_btns > 0) + pt_get_btn_touches(bd); + + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_btn_attention + * + * SUMMARY: Wrapper function for pt_xy_worker() that register to TTDL attention + * list. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int pt_btn_attention(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_btn_data *bd = &cd->bd; + int rc; + + if (bd->si->xy_mode[2] != bd->si->desc.btn_report_id) + return 0; + + /* core handles handshake */ + mutex_lock(&bd->btn_lock); + rc = pt_xy_worker(bd); + mutex_unlock(&bd->btn_lock); + if (rc < 0) + pt_debug(dev, DL_ERROR, + "%s: xy_worker error r=%d\n", __func__, rc); + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_startup_attention + * + * SUMMARY: Wrapper function for pt_btn_lift_all() that register to TTDL + * attention list. + * + * RETURN: + * 0 = success + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int pt_startup_attention(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_btn_data *bd = &cd->bd; + + mutex_lock(&bd->btn_lock); + pt_btn_lift_all(bd); + mutex_unlock(&bd->btn_lock); + + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_btn_suspend_attention + * + * SUMMARY: Function for button to enter suspend state that as following steps: + * 1) Lift all button + * 2) Set flag with suspend state + * 3) Decrese pm system count + * + * RETURN: + * 0 = success + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int pt_btn_suspend_attention(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_btn_data *bd = &cd->bd; + + mutex_lock(&bd->btn_lock); + pt_btn_lift_all(bd); + bd->is_suspended = true; + mutex_unlock(&bd->btn_lock); + + pm_runtime_put(dev); + + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_btn_resume_attention + * + * SUMMARY: Function for button to leave suspend state that as following steps: + * 1) Increse pm system count + * 2) Clear suspend state flag + * + * RETURN: + * 0 = success + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int pt_btn_resume_attention(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_btn_data *bd = &cd->bd; + + pm_runtime_get(dev); + + mutex_lock(&bd->btn_lock); + bd->is_suspended = false; + mutex_unlock(&bd->btn_lock); + + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_btn_open + * + * SUMMARY: Open method for input device(button) that sets up call back + * functions to TTDL attention list + * + * RETURN: + * 0 = success + * + * PARAMETERS: + * *input - pointer to input_dev structure + ******************************************************************************/ +static int pt_btn_open(struct input_dev *input) +{ + struct device *dev = input->dev.parent; + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_btn_data *bd = &cd->bd; + + pm_runtime_get_sync(dev); + + mutex_lock(&bd->btn_lock); + bd->is_suspended = false; + mutex_unlock(&bd->btn_lock); + + pt_debug(dev, DL_INFO, "%s: setup subscriptions\n", __func__); + + /* set up touch call back */ + _pt_subscribe_attention(dev, PT_ATTEN_IRQ, PT_BTN_NAME, + pt_btn_attention, PT_MODE_OPERATIONAL); + + /* set up startup call back */ + _pt_subscribe_attention(dev, PT_ATTEN_STARTUP, PT_BTN_NAME, + pt_startup_attention, 0); + + /* set up suspend call back */ + _pt_subscribe_attention(dev, PT_ATTEN_SUSPEND, PT_BTN_NAME, + pt_btn_suspend_attention, 0); + + /* set up resume call back */ + _pt_subscribe_attention(dev, PT_ATTEN_RESUME, PT_BTN_NAME, + pt_btn_resume_attention, 0); + + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_btn_close + * + * SUMMARY: Close method for input device(button) that clears call back + * functions from TTDL attention list. + * + * PARAMETERS: + * *input - pointer to input_dev structure + ******************************************************************************/ +static void pt_btn_close(struct input_dev *input) +{ + struct device *dev = input->dev.parent; + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_btn_data *bd = &cd->bd; + + _pt_unsubscribe_attention(dev, PT_ATTEN_IRQ, PT_BTN_NAME, + pt_btn_attention, PT_MODE_OPERATIONAL); + + _pt_unsubscribe_attention(dev, PT_ATTEN_STARTUP, PT_BTN_NAME, + pt_startup_attention, 0); + + _pt_unsubscribe_attention(dev, PT_ATTEN_SUSPEND, PT_BTN_NAME, + pt_btn_suspend_attention, 0); + + _pt_unsubscribe_attention(dev, PT_ATTEN_RESUME, PT_BTN_NAME, + pt_btn_resume_attention, 0); + + mutex_lock(&bd->btn_lock); + if (!bd->is_suspended) { + pm_runtime_put(dev); + bd->is_suspended = true; + } + mutex_unlock(&bd->btn_lock); +} + +/******************************************************************************* + * FUNCTION: pt_setup_input_device + * + * SUMMARY: Set up resolution, event signal capabilities and register input + * device for button. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int pt_setup_input_device(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_btn_data *bd = &cd->bd; + int i; + int rc; + + pt_debug(dev, DL_INFO, "%s: Initialize event signals\n", + __func__); + __set_bit(EV_KEY, bd->input->evbit); + pt_debug(dev, DL_INFO, "%s: Number of buttons %d\n", + __func__, bd->si->num_btns); + for (i = 0; i < bd->si->num_btns; i++) { + pt_debug(dev, DL_INFO, "%s: btn:%d keycode:%d\n", + __func__, i, bd->si->btn[i].key_code); + __set_bit(bd->si->btn[i].key_code, bd->input->keybit); + } + + rc = input_register_device(bd->input); + if (rc < 0) + pt_debug(dev, DL_ERROR, + "%s: Error, failed register input device r=%d\n", + __func__, rc); + else + bd->input_device_registered = true; + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_setup_input_attention + * + * SUMMARY: Wrapper function for pt_setup_input_device() register to TTDL + * attention list. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int pt_setup_input_attention(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_btn_data *bd = &cd->bd; + int rc; + + bd->si = _pt_request_sysinfo(dev); + if (!bd->si) + return -EPERM; + + rc = pt_setup_input_device(dev); + + _pt_unsubscribe_attention(dev, PT_ATTEN_STARTUP, PT_BTN_NAME, + pt_setup_input_attention, 0); + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_btn_probe + * + * SUMMARY: The probe function for button input device + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +int pt_btn_probe(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_btn_data *bd = &cd->bd; + struct pt_platform_data *pdata = dev_get_platdata(dev); + struct pt_btn_platform_data *btn_pdata; + int rc = 0; + + if (!pdata || !pdata->btn_pdata) { + pt_debug(dev, DL_ERROR, + "%s: Missing platform data\n", __func__); + rc = -ENODEV; + goto error_no_pdata; + } + btn_pdata = pdata->btn_pdata; + + mutex_init(&bd->btn_lock); + bd->dev = dev; + bd->pdata = btn_pdata; + + /* Create the input device and register it. */ + pt_debug(dev, DL_INFO, + "%s: Create the input device and register it\n", __func__); + bd->input = input_allocate_device(); + if (!bd->input) { + pt_debug(dev, DL_ERROR, + "%s: Error, failed to allocate input device\n", + __func__); + rc = -ENODEV; + goto error_alloc_failed; + } else + bd->input_device_allocated = true; + + if (bd->pdata->inp_dev_name) + bd->input->name = bd->pdata->inp_dev_name; + else + bd->input->name = PT_BTN_NAME; + scnprintf(bd->phys, sizeof(bd->phys), "%s/input%d", dev_name(dev), + cd->phys_num++); + bd->input->phys = bd->phys; + bd->input->dev.parent = bd->dev; + bd->input->open = pt_btn_open; + bd->input->close = pt_btn_close; + input_set_drvdata(bd->input, bd); + + /* get sysinfo */ + bd->si = _pt_request_sysinfo(dev); + + if (bd->si) { + rc = pt_setup_input_device(dev); + if (rc) + goto error_init_input; + } else { + pt_debug(dev, DL_ERROR, + "%s: Fail get sysinfo pointer from core p=%p\n", + __func__, bd->si); + _pt_subscribe_attention(dev, PT_ATTEN_STARTUP, + PT_BTN_NAME, pt_setup_input_attention, 0); + } + + return 0; + +error_init_input: + input_free_device(bd->input); + bd->input_device_allocated = false; +error_alloc_failed: +error_no_pdata: + pt_debug(dev, DL_ERROR, "%s failed.\n", __func__); + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_btn_release + * + * SUMMARY: The release function for button input device + * + * RETURN: + * 0 = success + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +int pt_btn_release(struct device *dev) +{ + struct pt_core_data *cd; + struct pt_btn_data *bd; + + /* Ensure valid pointers before de-referencing them */ + if (dev) { + cd = dev_get_drvdata(dev); + if (cd) + bd = &cd->bd; + else + return 0; + } else { + return 0; + } + + /* + * Second call this function may cause kernel panic if probe fail. + * Use input_device_registered & input_device_allocated variable to + * avoid unregister or free unavailable devive. + */ + if (bd && bd->input_device_registered) { + bd->input_device_registered = false; + input_unregister_device(bd->input); + /* Unregistering device will free the device too */ + bd->input_device_allocated = false; + } else if (bd && bd->input_device_allocated) { + bd->input_device_allocated = false; + input_free_device(bd->input); + _pt_unsubscribe_attention(dev, PT_ATTEN_STARTUP, + PT_BTN_NAME, pt_setup_input_attention, 0); + } + + return 0; +} diff --git a/qcom/opensource/touch-drivers/pt/pt_core.c b/qcom/opensource/touch-drivers/pt/pt_core.c new file mode 100644 index 0000000000..3a4e914627 --- /dev/null +++ b/qcom/opensource/touch-drivers/pt/pt_core.c @@ -0,0 +1,18570 @@ +/* + * pt_core.c + * Parade TrueTouch(TM) Standard Product Core Module. + * For use with Parade touchscreen controllers. + * Supported parts include: + * TMA5XX + * TMA448 + * TMA445A + * TT21XXX + * TT31XXX + * TT4XXXX + * TT7XXX + * TC3XXX + * + * Copyright (C) 2015-2020 Parade Technologies + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * 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. + * + * Contact Parade Technologies at www.paradetech.com + */ + +#include +#include +#include +#include +#include +#include +#include "pt_regs.h" +#if defined(CONFIG_PANEL_NOTIFIER) +#include +#endif + +#define PINCTRL_STATE_ACTIVE "pmx_ts_active" +#define PINCTRL_STATE_SUSPEND "pmx_ts_suspend" +#define PINCTRL_STATE_RELEASE "pmx_ts_release" + +#define FT_VTG_MIN_UV 2800000 +#define FT_VTG_MAX_UV 2800000 +#define FT_I2C_VTG_MIN_UV 1800000 +#define FT_I2C_VTG_MAX_UV 1800000 + +#define PWR_SUSPEND_LOAD_UA 165 +#define I2C_SUSPEND_LOAD_UA 100 +#define PWR_ACTIVE_LOAD_MA 12000 +#define I2C_ACTIVE_LOAD_MA 30000 + +#define PT_CORE_STARTUP_RETRY_COUNT 3 + +#define PT_STATUS_STR_LEN (50) + +#define PT_DATA_SIZE (2 * 256) + + +#if defined(CONFIG_DRM) || defined(CONFIG_PANEL_NOTIFIER) +static struct drm_panel *active_panel; +#endif + +MODULE_FIRMWARE(PT_FW_FILE_NAME); + +#define ENABLE_I2C_REG_ONLY + +enum core_states { + STATE_NONE, + STATE_RESUME, + STATE_SUSPEND +}; +#ifdef ENABLE_I2C_REG_ONLY +static int pt_enable_i2c_regulator(struct pt_core_data *cd, bool en); +#endif + +#define PT_AMBIENT_MODE + +#ifdef PT_AMBIENT_MODE +static int pt_device_exit(struct i2c_client *client); +int pt_device_entry(struct device *dev, + u16 irq, size_t xfer_buf_size); +#endif + +static const char *pt_driver_core_name = PT_CORE_NAME; +static const char *pt_driver_core_version = PT_DRIVER_VERSION; +static const char *pt_driver_core_date = PT_DRIVER_DATE; +enum core_states pt_core_state = STATE_NONE; + +uint32_t pt_slate_resp_ack; +struct pt_hid_field { + int report_count; + int report_size; + int size; /* report_count * report_size */ + int offset; + int data_type; + int logical_min; + int logical_max; + /* Usage Page (Hi 16 bit) + Usage (Lo 16 bit) */ + u32 usage_page; + u32 collection_usage_pages[PT_HID_MAX_COLLECTIONS]; + struct pt_hid_report *report; + bool record_field; +}; + +struct pt_hid_report { + u8 id; + u8 type; + int size; + struct pt_hid_field *fields[PT_HID_MAX_FIELDS]; + int num_fields; + int record_field_index; + int header_size; + int record_size; + u32 usage_page; +}; + +struct atten_node { + struct list_head node; + char *id; + struct device *dev; + + int (*func)(struct device *dev); + int mode; +}; + +struct param_node { + struct list_head node; + u8 id; + u32 value; + u8 size; +}; + +struct module_node { + struct list_head node; + struct pt_module *module; + void *data; +}; + +struct pt_hid_cmd { + u8 opcode; + u8 report_type; + union { + u8 report_id; + u8 power_state; + }; + u8 has_data_register; + size_t write_length; + u8 *write_buf; + u8 *read_buf; + u8 wait_interrupt; + u8 reset_cmd; + u16 timeout_ms; +}; + +struct pt_hid_output { + u8 cmd_type; + u16 length; + u8 command_code; + size_t write_length; + u8 *write_buf; + u8 novalidate; + u8 reset_expected; + u16 timeout_ms; +}; + +#define SET_CMD_OPCODE(byte, opcode) SET_CMD_LOW(byte, opcode) +#define SET_CMD_REPORT_TYPE(byte, type) SET_CMD_HIGH(byte, ((type) << 4)) +#define SET_CMD_REPORT_ID(byte, id) SET_CMD_LOW(byte, id) + +#define CREATE_PIP1_FW_CMD(command) \ + .cmd_type = PIP1_CMD_TYPE_FW, \ + .command_code = command + +#define CREATE_PIP1_BL_CMD(command) \ + .cmd_type = PIP1_CMD_TYPE_BL, \ + .command_code = command + +#define PT_MAX_PR_BUF_SIZE 2048 +/******************************************************************************* + * FUNCTION: pt_pr_buf + * + * SUMMARY: Print out the contents of a buffer to kmsg based on the debug level + * + * RETURN: Void + * + * PARAMETERS: + * *dev - pointer to Device structure + * debug_level - requested debug level to print at + * *buf - pointer to buffer to print + * buf_len - size of buf + * *data_name - Descriptive name of data prefixed to data + ******************************************************************************/ +void pt_pr_buf(struct device *dev, u8 debug_level, u8 *buf, + u16 buf_len, const char *data_name) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + int i; + ssize_t pr_buf_index = 0; + int max_size; + + /* only proceed if valid debug level and there is data to print */ + if (debug_level <= cd->debug_level && buf_len > 0) { + char *pr_buf = kzalloc(PT_MAX_PR_BUF_SIZE, GFP_KERNEL); + + if (!pr_buf) + return; + + /* + * With a space each printed char takes 3 bytes, subtract + * the length of the data_name prefix as well as 11 bytes + * for the " [0..xxx]: " printed before the data. + */ + max_size = (PT_MAX_PR_BUF_SIZE - sizeof(data_name) - 11) / 3; + + /* Ensure pr_buf_index stays within the 1018 size */ + pr_buf_index += scnprintf(pr_buf, PT_MAX_PR_BUF_SIZE, "%s [0..%d]: ", + data_name); + for (i = 0; i < buf_len && i < max_size; i++) + pr_buf_index += scnprintf(pr_buf + pr_buf_index, + PT_MAX_PR_BUF_SIZE, "%02X ", buf[i]); + + pt_debug(dev, debug_level, "%s\n", pr_buf); + kfree(pr_buf); + } +} +EXPORT_SYMBOL_GPL(pt_pr_buf); + +#ifdef TTHE_TUNER_SUPPORT +/******************************************************************************* + * FUNCTION: tthe_print + * + * SUMMARY: Format data name and time stamp as the header and format the + * content of input buffer with hex base to "tthe_buf". And then wake up event + * semaphore for tthe debugfs node. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * *buf - pointer to input buffer + * buf_len - size of input buffer + * *data_name - pointer to data name + ******************************************************************************/ +static int tthe_print(struct pt_core_data *cd, u8 *buf, int buf_len, + const u8 *data_name) +{ + int name_len = strlen(data_name); + int i, n; + u8 *p; + int remain; + u8 data_name_with_time_stamp[100]; + + /* Prepend timestamp, if requested, to data_name */ + if (cd->show_timestamp) { + scnprintf(data_name_with_time_stamp, + sizeof(data_name_with_time_stamp), + "[%u] %s", pt_get_time_stamp(), data_name); + data_name = data_name_with_time_stamp; + name_len = strlen(data_name); + } + + mutex_lock(&cd->tthe_lock); + if (!cd->tthe_buf) + goto exit; + + /* Add 1 due to the '\n' that is appended at the end */ + if (cd->tthe_buf_len + name_len + buf_len + 1 > cd->tthe_buf_size) + goto exit; + + if (name_len + buf_len == 0) + goto exit; + + remain = cd->tthe_buf_size - cd->tthe_buf_len; + if (remain < name_len) + name_len = remain; + + p = cd->tthe_buf + cd->tthe_buf_len; + memcpy(p, data_name, name_len); + cd->tthe_buf_len += name_len; + p += name_len; + remain -= name_len; + + *p = 0; + for (i = 0; i < buf_len; i++) { + n = scnprintf(p, remain, "%02X ", buf[i]); + if (n <= 0) + break; + p += n; + remain -= n; + cd->tthe_buf_len += n; + } + + n = scnprintf(p, remain, "\n"); + cd->tthe_buf_len += n; +exit: + wake_up(&cd->wait_q); + mutex_unlock(&cd->tthe_lock); + return 0; +} + +/******************************************************************************* + * FUNCTION: _pt_request_tthe_print + * + * SUMMARY: Function pointer included in core_cmds to allow other modules + * to request to print data to the "tthe_buffer". + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int _pt_request_tthe_print(struct device *dev, u8 *buf, + int buf_len, const u8 *data_name) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + return tthe_print(cd, buf, buf_len, data_name); +} +#endif + +/******************************************************************************* + * FUNCTION: pt_platform_detect_read + * + * SUMMARY: To be passed to platform dectect function to perform a read + * operation. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to Device structure + * *buf - pointer to buffer where the data read will be stored + * size - size to be read + ******************************************************************************/ +static int pt_platform_detect_read(struct device *dev, void *buf, int size) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + return pt_adap_read_default(cd, buf, size); +} + +/******************************************************************************* + * FUNCTION: pt_add_parameter + * + * SUMMARY: Adds a parameter that has been altered to the parameter linked list. + * On every reset of the DUT this linked list is traversed and all + * parameters in it are restored to the DUT. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * param_id - parameter ID to add + * param_value - Value corresponding to the ID + * param_size - Size of param_value + ******************************************************************************/ +static int pt_add_parameter(struct pt_core_data *cd, + u8 param_id, u32 param_value, u8 param_size) +{ + struct param_node *param, *param_new; + + /* Check if parameter already exists in the list */ + spin_lock(&cd->spinlock); + + list_for_each_entry(param, &cd->param_list, node) { + if (param->id == param_id) { + /* Update parameter */ + param->value = param_value; + pt_debug(cd->dev, DL_INFO, + "%s: Update parameter id:%d value:%d size:%d\n", + __func__, param_id, param_value, param_size); + goto exit_unlock; + } + } + spin_unlock(&cd->spinlock); + + param_new = kzalloc(sizeof(*param_new), GFP_KERNEL); + if (!param_new) + return -ENOMEM; + + param_new->id = param_id; + param_new->value = param_value; + param_new->size = param_size; + + pt_debug(cd->dev, DL_INFO, + "%s: Add parameter id:%d value:%d size:%d\n", + __func__, param_id, param_value, param_size); + + spin_lock(&cd->spinlock); + list_add(¶m_new->node, &cd->param_list); +exit_unlock: + spin_unlock(&cd->spinlock); + + return 0; +} + +#ifdef TTDL_DIAGNOSTICS +/******************************************************************************* + * FUNCTION: pt_erase_parameter_list + * + * SUMMARY: Empty out the entire parameter linked list of all parameter/value + * pairs. In some test cases this functionality is needed to ensure DUT + * returns to a virgin state after a reset and no parameters are restored. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static int pt_erase_parameter_list(struct pt_core_data *cd) +{ + struct param_node *pos, *temp; + + spin_lock(&cd->spinlock); + list_for_each_entry_safe(pos, temp, &cd->param_list, node) { + pt_debug(cd->dev, DL_INFO, + "%s: Parameter Restore List - remove 0x%02x\n", + __func__, pos->id); + list_del(&pos->node); + kfree(pos); + } + spin_unlock(&cd->spinlock); + + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_count_parameter_list + * + * SUMMARY: Count the items in the RAM parameter restor list + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static int pt_count_parameter_list(struct pt_core_data *cd) +{ + struct param_node *pos, *temp; + int entries = 0; + + spin_lock(&cd->spinlock); + list_for_each_entry_safe(pos, temp, &cd->param_list, node) + entries++; + spin_unlock(&cd->spinlock); + + return entries; +} +#endif /* TTDL_DIAGNOSTICS */ + +/******************************************************************************* + * FUNCTION: request_exclusive + * + * SUMMARY: Request exclusive access to the DUT + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * *ownptr - pointer to device + * timeout_ms - Timeout value + ******************************************************************************/ +int request_exclusive(struct pt_core_data *cd, void *ownptr, + int timeout_ms) +{ + int t = msecs_to_jiffies(timeout_ms); + bool with_timeout = (timeout_ms != 0); + + pt_debug(cd->dev, DL_INFO, "%s: Attempt to Request EXCLUSIVE t=%d\n", + __func__, timeout_ms); + + mutex_lock(&cd->system_lock); + if (!cd->exclusive_dev && cd->exclusive_waits == 0) { + cd->exclusive_dev = ownptr; + goto exit; + } + + cd->exclusive_waits++; +wait: + mutex_unlock(&cd->system_lock); + if (with_timeout) { + t = wait_event_timeout(cd->wait_q, !cd->exclusive_dev, t); + if (IS_TMO(t)) { + pt_debug(cd->dev, DL_ERROR, + "%s: tmo waiting exclusive access\n", __func__); + return -ETIME; + } + } else { + wait_event(cd->wait_q, !cd->exclusive_dev); + } + mutex_lock(&cd->system_lock); + if (cd->exclusive_dev) + goto wait; + cd->exclusive_dev = ownptr; + cd->exclusive_waits--; +exit: + mutex_unlock(&cd->system_lock); + pt_debug(cd->dev, DL_DEBUG, "%s: request exclusive ok=%p\n", + __func__, ownptr); + + return 0; +} + +/******************************************************************************* + * FUNCTION: release_exclusive_ + * + * SUMMARY: Release exclusive access to the DUT + * + * RETURN: + * 0 = success + * + * PARAMETERS: + * *cd - pointer to core data + * *ownptr - pointer to device + ******************************************************************************/ +static int release_exclusive_(struct pt_core_data *cd, void *ownptr) +{ + pt_debug(cd->dev, DL_INFO, "%s: Attempt to Release EXCLUSIVE\n", + __func__); + if (cd->exclusive_dev != ownptr) + return -EINVAL; + + pt_debug(cd->dev, DL_DEBUG, "%s: exclusive_dev %p freed\n", + __func__, cd->exclusive_dev); + cd->exclusive_dev = NULL; + wake_up(&cd->wait_q); + return 0; +} + +/******************************************************************************* + * FUNCTION: release_exclusive + * + * SUMMARY: Protected wrapper to release_exclusive_() + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * *ownptr - pointer to device + ******************************************************************************/ +int release_exclusive(struct pt_core_data *cd, void *ownptr) +{ + int rc; + + mutex_lock(&cd->system_lock); + rc = release_exclusive_(cd, ownptr); + mutex_unlock(&cd->system_lock); + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_hid_exec_cmd_ + * + * SUMMARY: Send the HID command to the DUT + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * *hid_cmd - pointer to the HID command to send + ******************************************************************************/ +static int pt_hid_exec_cmd_(struct pt_core_data *cd, + struct pt_hid_cmd *hid_cmd) +{ + int rc = 0; + u8 *cmd; + u16 cmd_length; + u8 cmd_offset = 0; + + cmd_length = 2 /* command register */ + + 2 /* command */ + + (hid_cmd->report_id >= 0XF ? 1 : 0) /* Report ID */ + + (hid_cmd->has_data_register ? 2 : 0) /* Data register */ + + hid_cmd->write_length; /* Data length */ + + if (cmd_length < 4 || cmd_length > PT_MAX_PIP1_MSG_SIZE) + return -EPROTO; + + cmd = kzalloc(cmd_length, GFP_KERNEL); + if (!cmd) + return -ENOMEM; + + /* Set Command register */ + memcpy(&cmd[cmd_offset], &cd->hid_desc.command_register, + sizeof(cd->hid_desc.command_register)); + cmd_offset += sizeof(cd->hid_desc.command_register); + + /* Set Command */ + SET_CMD_REPORT_TYPE(cmd[cmd_offset], hid_cmd->report_type); + + if (hid_cmd->report_id >= 0XF) + SET_CMD_REPORT_ID(cmd[cmd_offset], 0xF); + else + SET_CMD_REPORT_ID(cmd[cmd_offset], hid_cmd->report_id); + cmd_offset++; + + SET_CMD_OPCODE(cmd[cmd_offset], hid_cmd->opcode); + cmd_offset++; + + if (hid_cmd->report_id >= 0XF) { + cmd[cmd_offset] = hid_cmd->report_id; + cmd_offset++; + } + + /* Set Data register */ + if (hid_cmd->has_data_register) { + memcpy(&cmd[cmd_offset], &cd->hid_desc.data_register, + sizeof(cd->hid_desc.data_register)); + cmd_offset += sizeof(cd->hid_desc.data_register); + } + + /* Set Data */ + if (hid_cmd->write_length && hid_cmd->write_buf) { + memcpy(&cmd[cmd_offset], hid_cmd->write_buf, + hid_cmd->write_length); + cmd_offset += hid_cmd->write_length; + } + + pt_debug(cd->dev, DL_INFO, + ">>> %s: Write Buffer Size[%d] Cmd[0x%02X]\n", + __func__, cmd_length, hid_cmd->report_id); + + pt_pr_buf(cd->dev, DL_DEBUG, cmd, cmd_length, ">>> CMD"); + rc = pt_adap_write_read_specific(cd, cmd_length, cmd, + hid_cmd->read_buf); + if (rc) + pt_debug(cd->dev, DL_ERROR, + "%s: Fail pt_adap_transfer\n", __func__); + + kfree(cmd); + return rc; +} +#ifdef TTDL_DIAGNOSTICS +/******************************************************************************* + * FUNCTION: pt_toggle_err_gpio + * + * SUMMARY: Toggles the pre-defined error GPIO + * + * RETURN: n/a + * + * PARAMETERS: + * *cd - pointer to core data + * type - type of err that occurred + ******************************************************************************/ +void pt_toggle_err_gpio(struct pt_core_data *cd, u8 type) +{ + pt_debug(cd->dev, DL_DEBUG, "%s called with type = %d\n", + __func__, type); + if (cd->err_gpio && type == cd->err_gpio_type) { + pt_debug(cd->dev, DL_WARN, "%s: Toggle ERR GPIO\n", __func__); + gpio_direction_output(cd->err_gpio, + !gpio_get_value(cd->err_gpio)); + } +} + +/******************************************************************************* + * FUNCTION: _pt_request_toggle_err_gpio + * + * SUMMARY: Function pointer included in core_cmds to allow other modules + * to request to toggle the err_gpio + * + * RETURN: n/a + * + * PARAMETERS: + * *cd - pointer to core data + * type - type of err that occurred + ******************************************************************************/ +void _pt_request_toggle_err_gpio(struct device *dev, u8 type) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + pt_toggle_err_gpio(cd, type); +} +#endif /* TTDL_DIAGNOSTICS */ + +/******************************************************************************* + * FUNCTION: pt_hid_exec_cmd_and_wait_ + * + * SUMMARY: Send the HID command to the DUT and wait for the response + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * *hid_cmd - pointer to the HID command to send + ******************************************************************************/ +static int pt_hid_exec_cmd_and_wait_(struct pt_core_data *cd, + struct pt_hid_cmd *hid_cmd) +{ + int rc = 0; + int t; + u16 timeout_ms; + int *cmd_state; + + if (hid_cmd->reset_cmd) + cmd_state = &cd->hid_reset_cmd_state; + else + cmd_state = &cd->hid_cmd_state; + + if (hid_cmd->wait_interrupt) { + mutex_lock(&cd->system_lock); + *cmd_state = 1; + mutex_unlock(&cd->system_lock); + } + + rc = pt_hid_exec_cmd_(cd, hid_cmd); + if (rc) { + if (hid_cmd->wait_interrupt) + goto error; + + goto exit; + } + + if (!hid_cmd->wait_interrupt) + goto exit; + + if (hid_cmd->timeout_ms) + timeout_ms = hid_cmd->timeout_ms; + else + timeout_ms = PT_HID_CMD_DEFAULT_TIMEOUT; + + t = wait_event_timeout(cd->wait_q, (*cmd_state == 0), + msecs_to_jiffies(timeout_ms)); + if (IS_TMO(t)) { +#ifdef TTDL_DIAGNOSTICS + cd->bus_transmit_error_count++; + pt_toggle_err_gpio(cd, PT_ERR_GPIO_I2C_TRANS); +#endif /* TTDL_DIAGNOSTICS */ + pt_debug(cd->dev, DL_ERROR, + "%s: HID output cmd execution timed out\n", + __func__); + rc = -ETIME; + goto error; + } + + goto exit; + +error: + mutex_lock(&cd->system_lock); + *cmd_state = 0; + mutex_unlock(&cd->system_lock); + +exit: + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_hid_cmd_reset_ + * + * SUMMARY: Send the HID RESET command to the DUT + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static int pt_hid_cmd_reset_(struct pt_core_data *cd) +{ + struct pt_hid_cmd hid_cmd = { + .opcode = HID_CMD_RESET, + .wait_interrupt = 1, + .reset_cmd = 1, + .timeout_ms = PT_HID_CMD_DEFAULT_TIMEOUT, + }; + + return pt_hid_exec_cmd_and_wait_(cd, &hid_cmd); +} + +/******************************************************************************* + * FUNCTION: pt_hid_cmd_reset + * + * SUMMARY: Wrapper function for pt_hid_cmd_reset_ that guarantees exclusive + * access. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static int pt_hid_cmd_reset(struct pt_core_data *cd) +{ + int rc = 0; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + pt_debug(cd->dev, DL_INFO, "%s: Send HID Reset command\n", __func__); + rc = pt_hid_cmd_reset_(cd); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_hid_cmd_set_power_ + * + * SUMMARY: Send hid cmd to set power state for the DUT and wait for response + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * power_state - power state to set(HID_POWER_ON/HID_POWER_SLEEP) + ******************************************************************************/ +static int pt_hid_cmd_set_power_(struct pt_core_data *cd, + u8 power_state) +{ + int rc = 0; + struct pt_hid_cmd hid_cmd = { + .opcode = HID_CMD_SET_POWER, + .wait_interrupt = 1, + .timeout_ms = PT_HID_CMD_DEFAULT_TIMEOUT, + }; + hid_cmd.power_state = power_state; + + /* The chip won't give response if goes to Deep Standby */ + if (power_state == HID_POWER_STANDBY) { + rc = pt_hid_exec_cmd_(cd, &hid_cmd); + if (rc) + pt_debug(cd->dev, DL_ERROR, + "%s: Failed to set power to state:%d\n", + __func__, power_state); + else + cd->fw_sys_mode_in_standby_state = true; + return rc; + } + cd->fw_sys_mode_in_standby_state = false; + + rc = pt_hid_exec_cmd_and_wait_(cd, &hid_cmd); + if (rc) { + pt_debug(cd->dev, DL_ERROR, + "%s: Failed to set power to state:%d\n", + __func__, power_state); + return rc; + } + + /* validate */ + if ((cd->response_buf[2] != HID_RESPONSE_REPORT_ID) + || ((cd->response_buf[3] & 0x3) != power_state) + || ((cd->response_buf[4] & 0xF) != HID_CMD_SET_POWER)) + rc = -EINVAL; + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_hid_cmd_set_power + * + * SUMMARY: Wrapper function for pt_hid_cmd_set_power_ that guarantees + * exclusive access. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * power_state - power state to set(HID_POWER_ON/HID_POWER_SLEEP) + ******************************************************************************/ +static int pt_hid_cmd_set_power(struct pt_core_data *cd, + u8 power_state) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = pt_hid_cmd_set_power_(cd, power_state); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + + return rc; +} + +static const u16 crc_table[16] = { + 0x0000, 0x1021, 0x2042, 0x3063, + 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, + 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, +}; + +/******************************************************************************* + * FUNCTION: _pt_compute_crc + * + * SUMMARY: Calculate CRC by CRC table. + * + * RETURN: + * CRC calculation result + * + * PARAMETERS: + * *buf - pointer to the data array to be calculated + * size - size of data array + ******************************************************************************/ +static u16 _pt_compute_crc(u8 *buf, u32 size) +{ + u16 remainder = 0xFFFF; + u16 xor_mask = 0x0000; + u32 index; + u32 byte_value; + u32 table_index; + u32 crc_bit_width = sizeof(u16) * 8; + + /* Divide the message by polynomial, via the table. */ + for (index = 0; index < size; index++) { + byte_value = buf[index]; + table_index = ((byte_value >> 4) & 0x0F) + ^ (remainder >> (crc_bit_width - 4)); + remainder = crc_table[table_index] ^ (remainder << 4); + table_index = (byte_value & 0x0F) + ^ (remainder >> (crc_bit_width - 4)); + remainder = crc_table[table_index] ^ (remainder << 4); + } + + /* Perform the final remainder CRC. */ + return remainder ^ xor_mask; +} + +u16 ccitt_Table[] = { + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, + 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, + 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, + 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, + 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, + 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, + 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, + 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, + 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, + 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, + 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, + 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, + 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, + 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, + 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, + 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, + 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, + 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, + 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, + 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, + 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, + 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, + 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0, +}; + +/******************************************************************************* + * FUNCTION: crc_ccitt_calculate + * + * SUMMARY: Calculate CRC with ccitt standard by CRC table. + * + * RETURN: + * CRC calculation result + * + * PARAMETERS: + * *q - pointer to the data array to be calculated + * len - size of data array + ******************************************************************************/ +static unsigned short crc_ccitt_calculate(unsigned char *q, int len) +{ + unsigned short crc = 0xffff; + + while (len-- > 0) + crc = ccitt_Table[(crc >> 8 ^ *q++) & 0xff] ^ (crc << 8); + return crc; +} + +/******************************************************************************* + * FUNCTION: pt_pip2_cmd_calculate_crc + * + * SUMMARY: Calculate the CRC of a command packet + * + * RETURN: void + * + * PARAMETERS: + * *cmd - pointer to command data + * extra_bytes - Extra bytes included in command length + ******************************************************************************/ +static void pt_pip2_cmd_calculate_crc(struct pip2_cmd_structure *cmd, + u8 extra_bytes) +{ + u8 buf[PT_MAX_PIP2_MSG_SIZE + 1] = {0}; + unsigned short crc; + + buf[0] = cmd->len & 0xff; + buf[1] = (cmd->len & 0xff00) >> 8; + buf[2] = cmd->seq; + buf[3] = cmd->id; + memcpy(&buf[4], cmd->data, cmd->len - extra_bytes); + /* Calculate the CRC for the first 4 bytes above and the data payload */ + crc = crc_ccitt_calculate(buf, 4 + (cmd->len - extra_bytes)); + cmd->crc[0] = (crc & 0xff00) >> 8; + cmd->crc[1] = (crc & 0xff); +} + +/******************************************************************************* + * FUNCTION: pt_pip2_get_next_cmd_seq + * + * SUMMARY: Gets the next sequence number for a PIP2 command. The sequence + * number is a 3 bit value (bits [0-2]) but because TTDL will always have + * the TAG bit set (bit 3), the counter starts at 0x08 and goes to 0x0F. + * If the "force_pip2_seq" holds a valid seq value (0x08-0x0F) then do not + * increment, just use the forced value. + * + * RETURN: Next command sequence number [0x08-0x0F] + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static u8 pt_pip2_get_next_cmd_seq(struct pt_core_data *cd) +{ +#ifdef TTDL_DIAGNOSTICS + if (cd->force_pip2_seq <= 0x07) { + cd->pip2_cmd_tag_seq++; + if (cd->pip2_cmd_tag_seq > 0x0F) + cd->pip2_cmd_tag_seq = 0x08; + } else { + cd->pip2_cmd_tag_seq = cd->force_pip2_seq; + } +#else + cd->pip2_cmd_tag_seq++; + if (cd->pip2_cmd_tag_seq > 0x0F) + cd->pip2_cmd_tag_seq = 0x08; +#endif + return cd->pip2_cmd_tag_seq; +} + +/* + * Following macros are to define the response time (the interval between PIP2 + * command finishes sending and INT pin falls). The unit is in microsecond. + * It has different time settings between the solution GPIO polling and Bus + * polling due to the considration for system load. + */ +#ifdef PT_POLL_RESP_BY_BUS +#define POLL_RETRY_DEFAULT_INTERVAL 50 +#define PIP2_RESP_DEFAULT_TIME_MIN 50 +#define PIP2_RESP_DEFAULT_TIME_MAX (PT_PIP_CMD_DEFAULT_TIMEOUT * 1000) +#define PIP2_RESP_FILE_WRITE_TIME_MIN 220 +#define PIP2_RESP_FILE_IOCTL_TIME_MAX (PT_PIP2_CMD_FILE_ERASE_TIMEOUT * 1000) +#else +#define POLL_RETRY_DEFAULT_INTERVAL 20 +#define PIP2_RESP_DEFAULT_TIME_MIN 20 +#define PIP2_RESP_DEFAULT_TIME_MAX (PT_PIP_CMD_DEFAULT_TIMEOUT * 1000) +#define PIP2_RESP_FILE_WRITE_TIME_MIN 20 +#define PIP2_RESP_FILE_IOCTL_TIME_MAX (PT_PIP2_CMD_FILE_ERASE_TIMEOUT * 1000) +#endif +/* + * id: the command id defined in PIP2 + * response_len: the (maximum) length of response. + * response_time_min: minimum response time in microsecond + * response_time_max: maximum response time in microsecond + */ +static const struct pip2_cmd_response_structure pip2_cmd_response[] = { + {.id = PIP2_CMD_ID_PING, + .response_len = 255, + .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, + .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, + {.id = PIP2_CMD_ID_STATUS, + .response_len = PIP2_EXTRA_BYTES_NUM + 5, + .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, + .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, + {.id = PIP2_CMD_ID_CTRL, + .response_len = PIP2_EXTRA_BYTES_NUM + 1, + .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, + .response_time_max = PT_PIP2_CMD_FILE_ERASE_TIMEOUT}, + {.id = PIP2_CMD_ID_CONFIG, + .response_len = PIP2_EXTRA_BYTES_NUM + 1, + .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, + .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, + {.id = PIP2_CMD_ID_CLEAR, + .response_len = PIP2_EXTRA_BYTES_NUM + 0, + .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, + .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, + {.id = PIP2_CMD_ID_RESET, + .response_len = PIP2_EXTRA_BYTES_NUM + 1, + .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, + .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, + {.id = PIP2_CMD_ID_VERSION, + .response_len = PIP2_EXTRA_BYTES_NUM + 23, + .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, + .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, + {.id = PIP2_CMD_ID_FILE_OPEN, + .response_len = PIP2_EXTRA_BYTES_NUM + 2, + .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, + .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, + {.id = PIP2_CMD_ID_FILE_CLOSE, + .response_len = PIP2_EXTRA_BYTES_NUM + 1, + .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, + .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, + {.id = PIP2_CMD_ID_FILE_READ, + .response_len = 255, + .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, + .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, + {.id = PIP2_CMD_ID_FILE_WRITE, + .response_len = PIP2_EXTRA_BYTES_NUM + 1, + .response_time_min = PIP2_RESP_FILE_WRITE_TIME_MIN, + .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, + {.id = PIP2_CMD_ID_FILE_IOCTL, + .response_len = PIP2_EXTRA_BYTES_NUM + 10, + .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, + .response_time_max = PIP2_RESP_FILE_IOCTL_TIME_MAX}, + {.id = PIP2_CMD_ID_FLASH_INFO, + .response_len = PIP2_EXTRA_BYTES_NUM + 17, + .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, + .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, + {.id = PIP2_CMD_ID_EXECUTE, + .response_len = PIP2_EXTRA_BYTES_NUM + 1, + .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, + .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, + {.id = PIP2_CMD_ID_GET_LAST_ERRNO, + .response_len = PIP2_EXTRA_BYTES_NUM + 3, + .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, + .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, + {.id = PIP2_CMD_ID_EXIT_HOST_MODE, + .response_len = PIP2_EXTRA_BYTES_NUM + 1, + .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, + .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, + {.id = PIP2_CMD_ID_READ_GPIO, + .response_len = PIP2_EXTRA_BYTES_NUM + 5, + .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, + .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, + {.id = PIP2_CMD_EXECUTE_SCAN, + .response_len = PIP2_EXTRA_BYTES_NUM + 1, + .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, + .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, + {.id = PIP2_CMD_SET_PARAMETER, + .response_len = PIP2_EXTRA_BYTES_NUM + 1, + .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, + .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, + {.id = PIP2_CMD_GET_PARAMETER, + .response_len = PIP2_EXTRA_BYTES_NUM + 7, + .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, + .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, + {.id = PIP2_CMD_SET_DDI_REG, + .response_len = PIP2_EXTRA_BYTES_NUM + 1, + .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, + .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, + {.id = PIP2_CMD_GET_DDI_REG, + .response_len = PIP2_EXTRA_BYTES_NUM + 249, + .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, + .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX}, + {.id = PIP2_CMD_ID_END, + .response_len = 255, + .response_time_min = PIP2_RESP_DEFAULT_TIME_MIN, + .response_time_max = PIP2_RESP_DEFAULT_TIME_MAX} +}; + +/******************************************************************************* + * FUNCTION: pt_pip2_get_cmd_response_len + * + * SUMMARY: Gets the expected response length based on the command ID + * + * RETURN: Expected response length + * + * PARAMETERS: + * id - Command ID (-1 means input ID is not in list of PIP2 command) + ******************************************************************************/ +static int pt_pip2_get_cmd_response_len(u8 id) +{ + const struct pip2_cmd_response_structure *p = pip2_cmd_response; + + while ((p->id != id) && (p->id != PIP2_CMD_ID_END)) + p++; + + if (p->id != PIP2_CMD_ID_END) + return p->response_len; + else + return -EPERM; +} + +/******************************************************************************* + * FUNCTION: pt_pip2_get_cmd_resp_time_min + * + * SUMMARY: Gets the minimum response time (the interval between PIP2 command + * finishes sending and INT pin falls) based on the command ID + * + * RETURN: Estimated minimum response time in microsecond + * + * PARAMETERS: + * id - Command ID + ******************************************************************************/ +static u32 pt_pip2_get_cmd_resp_time_min(u8 id) +{ + const struct pip2_cmd_response_structure *p = pip2_cmd_response; + + while ((p->id != id) && (p->id != PIP2_CMD_ID_END)) + p++; + + if (p->id != PIP2_CMD_ID_END) + return p->response_time_min; + else + return PIP2_RESP_DEFAULT_TIME_MIN; +} + +/******************************************************************************* + * FUNCTION: pt_pip2_get_cmd_resp_time_max + * + * SUMMARY: Gets the maximum response time (the interval between PIP2 command + * finishes sending and INT pin falls) based on the command ID + * + * RETURN: Estimated maximum response time in microsecond + * + * PARAMETERS: + * id - Command ID + ******************************************************************************/ +static u32 pt_pip2_get_cmd_resp_time_max(u8 id) +{ + const struct pip2_cmd_response_structure *p = pip2_cmd_response; + + while ((p->id != id) && (p->id != PIP2_CMD_ID_END)) + p++; + + if (p->id != PIP2_CMD_ID_END) + return p->response_time_max; + else + return PIP2_RESP_DEFAULT_TIME_MAX; +} + +/******************************************************************************* + * FUNCTION: pt_pip2_validate_response + * + * SUMMARY: Validate the response of PIP2 command. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * *pip2_cmd - pointer to PIP2 command to send + * *read_buf - pointer to response buffer + * actual_read_len - actual read length of the response + ******************************************************************************/ +static int pt_pip2_validate_response(struct pt_core_data *cd, + struct pip2_cmd_structure *pip2_cmd, u8 *read_buf, + u16 actual_read_len) +{ + int rc = 0; + u8 response_seq = 0; + u8 reserved_bits = 0; + u8 cmd_id = 0; + u8 response_bit = 0; + unsigned short calc_crc = 0; + unsigned short resp_crc = 0; + + /* Verify the length of response buffer */ + if (actual_read_len < PT_MIN_PIP2_PACKET_SIZE) { + pt_debug(cd->dev, DL_ERROR, + "%s cmd[0x%02X] read length ERR: read_len = %d\n", + __func__, pip2_cmd->id, actual_read_len); + rc = -EINVAL; + goto exit; + } + + /* Verify the CRC */ + calc_crc = crc_ccitt_calculate(read_buf, actual_read_len - 2); + resp_crc = read_buf[actual_read_len - 2] << 8; + resp_crc |= read_buf[actual_read_len - 1]; + if (resp_crc != calc_crc) { + pt_debug(cd->dev, DL_ERROR, + "%s: cmd[0x%02X] CRC ERR: calc=0x%04X rsp=0x%04X\n", + __func__, pip2_cmd->id, calc_crc, resp_crc); +#ifdef TTDL_DIAGNOSTICS + cd->pip2_crc_error_count++; +#endif /* TTDL_DIAGNOSTICS */ + rc = -EINVAL; + goto exit; + } + + /* Verify the response bit is set */ + response_bit = read_buf[PIP2_RESP_REPORT_ID_OFFSET] & 0x80; + if (!response_bit) { + pt_debug(cd->dev, DL_ERROR, + "%s cmd[0x%02X] response bit ERR: response_bit = %d\n", + __func__, pip2_cmd->id, response_bit); + rc = -EINVAL; + goto exit; + } + + /* Verify the command ID matches from command to response */ + cmd_id = read_buf[PIP2_RESP_REPORT_ID_OFFSET] & 0x7F; + if (cmd_id != pip2_cmd->id) { + pt_debug(cd->dev, DL_ERROR, + "%s cmd[0x%02X] command ID ERR: cmd_id = 0x%02X\n", + __func__, pip2_cmd->id, cmd_id); + rc = -EINVAL; + goto exit; + } + + /* Verify the SEQ number matches from command to response */ + response_seq = read_buf[PIP2_RESP_SEQUENCE_OFFSET] & 0x0F; + if ((pip2_cmd->seq & 0x0F) != response_seq) { + pt_debug(cd->dev, DL_ERROR, + "%s cmd[0x%02X] send_seq = 0x%02X, resp_seq = 0x%02X\n", + __func__, pip2_cmd->id, + pip2_cmd->seq, response_seq); + rc = -EINVAL; + goto exit; + } + + /* Verify the reserved bits are 0 */ + reserved_bits = read_buf[PIP2_RESP_SEQUENCE_OFFSET] & 0xF0; + if (reserved_bits) + pt_debug(cd->dev, DL_WARN, + "%s cmd[0x%02X] reserved_bits = 0x%02X\n", + __func__, pip2_cmd->id, reserved_bits); + +exit: + if (rc) + pt_pr_buf(cd->dev, DL_WARN, cd->input_buf, actual_read_len, + "PIP RSP:"); + return rc; +} + + +/******************************************************************************* + * FUNCTION: pt_hid_output_validate_bl_response + * + * SUMMARY: Validate the response of bootloader command. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * *hid_output - pointer to hid output data structure + ******************************************************************************/ +static int pt_hid_output_validate_bl_response( + struct pt_core_data *cd, + struct pt_hid_output *hid_output) +{ + u16 size; + u16 crc; + u8 status; + + size = get_unaligned_le16(&cd->response_buf[0]); + + if (hid_output->reset_expected && !size) + return 0; + + if (cd->response_buf[PIP1_RESP_REPORT_ID_OFFSET] + != PT_PIP_BL_RESPONSE_REPORT_ID) { + pt_debug(cd->dev, DL_ERROR, + "%s: BL output response, wrong report_id\n", __func__); + return -EPROTO; + } + + if (cd->response_buf[4] != PIP1_BL_SOP) { + pt_debug(cd->dev, DL_ERROR, + "%s: BL output response, wrong SOP\n", __func__); + return -EPROTO; + } + + if (cd->response_buf[size - 1] != PIP1_BL_EOP) { + pt_debug(cd->dev, DL_ERROR, + "%s: BL output response, wrong EOP\n", __func__); + return -EPROTO; + } + + crc = _pt_compute_crc(&cd->response_buf[4], size - 7); + if (cd->response_buf[size - 3] != LOW_BYTE(crc) + || cd->response_buf[size - 2] != HI_BYTE(crc)) { + pt_debug(cd->dev, DL_ERROR, + "%s: BL output response, wrong CRC 0x%X\n", + __func__, crc); + return -EPROTO; + } + + status = cd->response_buf[5]; + if (status) { + pt_debug(cd->dev, DL_ERROR, + "%s: BL output response, ERROR:%d\n", + __func__, status); + return -EPROTO; + } + + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_hid_output_validate_app_response + * + * SUMMARY: Validate the response of application command. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * *hid_output - pointer to hid output data structure + ******************************************************************************/ +static int pt_hid_output_validate_app_response( + struct pt_core_data *cd, + struct pt_hid_output *hid_output) +{ + int command_code; + u16 size; + + size = get_unaligned_le16(&cd->response_buf[0]); + + if (hid_output->reset_expected && !size) + return 0; + + if (cd->response_buf[PIP1_RESP_REPORT_ID_OFFSET] + != PT_PIP_NON_HID_RESPONSE_ID) { + pt_debug(cd->dev, DL_ERROR, + "%s: APP output response, wrong report_id\n", __func__); + return -EPROTO; + } + + command_code = cd->response_buf[PIP1_RESP_COMMAND_ID_OFFSET] + & PIP1_RESP_COMMAND_ID_MASK; + if (command_code != hid_output->command_code) { + pt_debug(cd->dev, DL_ERROR, + "%s: APP output response, wrong command_code:%X\n", + __func__, command_code); + return -EPROTO; + } + + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_check_set_parameter + * + * SUMMARY: Check command input and response for Set Parameter command.And + * store the parameter to the list for resume work if pass the check. + * + * PARAMETERS: + * *cd - pointer to core data + * *hid_output - pointer to hid output data structure + * raw - flag to show if output cmd is user cmd(1:user cmd) + ******************************************************************************/ +static void pt_check_set_parameter(struct pt_core_data *cd, + struct pt_hid_output *hid_output, bool raw) +{ + u8 *param_buf; + u32 param_value = 0; + u8 param_size; + u8 param_id; + int i = 0; + + if (!(cd->cpdata->flags & PT_CORE_FLAG_RESTORE_PARAMETERS)) + return; + + /* Check command input for Set Parameter command */ + if (raw && hid_output->length >= 10 && hid_output->length <= 13 + && !memcmp(&hid_output->write_buf[0], + &cd->hid_desc.output_register, + sizeof(cd->hid_desc.output_register)) + && hid_output->write_buf[4] == + PT_PIP_NON_HID_COMMAND_ID + && hid_output->write_buf[6] == + PIP1_CMD_ID_SET_PARAM) + param_buf = &hid_output->write_buf[7]; + else if (!raw && hid_output->cmd_type == PIP1_CMD_TYPE_FW + && hid_output->command_code == PIP1_CMD_ID_SET_PARAM + && hid_output->write_length >= 3 + && hid_output->write_length <= 6) + param_buf = &hid_output->write_buf[0]; + else + return; + + /* Get parameter ID, size and value */ + param_id = param_buf[0]; + param_size = param_buf[1]; + if (param_size > 4) { + pt_debug(cd->dev, DL_ERROR, + "%s: Invalid parameter size\n", __func__); + return; + } + + param_buf = ¶m_buf[2]; + while (i < param_size) + param_value += *(param_buf++) << (8 * i++); + + /* Check command response for Set Parameter command */ + if (cd->response_buf[2] != PT_PIP_NON_HID_RESPONSE_ID + || (cd->response_buf[4] & + PIP1_RESP_COMMAND_ID_MASK) != + PIP1_CMD_ID_SET_PARAM + || cd->response_buf[5] != param_id + || cd->response_buf[6] != param_size) { + pt_debug(cd->dev, DL_ERROR, + "%s: Set Parameter command not successful\n", + __func__); + return; + } + + pt_add_parameter(cd, param_id, param_value, param_size); +} + +/******************************************************************************* + * FUNCTION: pt_check_command + * + * SUMMARY: Check the output command. The function pt_check_set_parameter() is + * called here to check output command and store parameter to the list. + * + * PARAMETERS: + * *cd - pointer to core data + * *hid_output - pointer to hid output data structure + * raw - flag to show if output cmd is user cmd(1:user cmd) + ******************************************************************************/ +static void pt_check_command(struct pt_core_data *cd, + struct pt_hid_output *hid_output, bool raw) +{ + pt_check_set_parameter(cd, hid_output, raw); +} + +/******************************************************************************* + * FUNCTION: pt_hid_output_validate_response + * + * SUMMARY: Validate the response of application or bootloader command. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * *hid_output - pointer to hid output data structure + ******************************************************************************/ +static int pt_hid_output_validate_response(struct pt_core_data *cd, + struct pt_hid_output *hid_output) +{ + if (hid_output->cmd_type == PIP1_CMD_TYPE_BL) + return pt_hid_output_validate_bl_response(cd, hid_output); + + return pt_hid_output_validate_app_response(cd, hid_output); + +} + +/******************************************************************************* + * FUNCTION: pt_hid_send_output_user_ + * + * SUMMARY: Blindly send user data to the DUT. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * *hid_output - pointer to the command to send + ******************************************************************************/ +static int pt_hid_send_output_user_(struct pt_core_data *cd, + struct pt_hid_output *hid_output) +{ + int rc = 0; + int cmd; + + if (!hid_output->length || !hid_output->write_buf) + return -EINVAL; + + if (cd->pip2_prot_active) { + cmd = hid_output->write_buf[PIP2_CMD_COMMAND_ID_OFFSET]; + cmd &= PIP2_CMD_COMMAND_ID_MASK; + } else + cmd = hid_output->write_buf[PIP1_CMD_COMMAND_ID_OFFSET]; + + pt_debug(cd->dev, DL_INFO, + ">>> %s: Write Buffer Size[%d] Cmd[0x%02X]\n", + __func__, hid_output->length, cmd); + + pt_pr_buf(cd->dev, DL_DEBUG, hid_output->write_buf, + hid_output->length, ">>> User CMD"); + + rc = pt_adap_write_read_specific(cd, hid_output->length, + hid_output->write_buf, NULL); + if (rc) + pt_debug(cd->dev, DL_ERROR, + "%s: Fail pt_adap_transfer\n", __func__); + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_hid_send_output_user_and_wait_ + * + * SUMMARY: Blindly send user data to the DUT and wait for the response. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * *hid_output - pointer to the command to send + ******************************************************************************/ +static int pt_hid_send_output_user_and_wait_(struct pt_core_data *cd, + struct pt_hid_output *hid_output) +{ + int rc = 0; + int t; + + mutex_lock(&cd->system_lock); + cd->hid_cmd_state = PIP1_CMD_ID_USER_CMD + 1; + mutex_unlock(&cd->system_lock); + + rc = pt_hid_send_output_user_(cd, hid_output); + if (rc) + goto error; + + t = wait_event_timeout(cd->wait_q, (cd->hid_cmd_state == 0), + msecs_to_jiffies(cd->pip_cmd_timeout)); + if (IS_TMO(t)) { +#ifdef TTDL_DIAGNOSTICS + cd->bus_transmit_error_count++; + pt_toggle_err_gpio(cd, PT_ERR_GPIO_I2C_TRANS); +#endif /* TTDL_DIAGNOSTICS */ + pt_debug(cd->dev, DL_ERROR, + "%s: HID output cmd execution timed out\n", + __func__); + rc = -ETIME; + goto error; + } + pt_check_command(cd, hid_output, true); + goto exit; + +error: + mutex_lock(&cd->system_lock); + cd->hid_cmd_state = 0; + mutex_unlock(&cd->system_lock); + +exit: + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_check_irq_asserted + * + * SUMMARY: Checks if the IRQ GPIO is asserted or not. There are times when + * the FW can hold the INT line low ~150us after the read is complete. + * NOTE: if irq_stat is not defined this function will return false + * + * RETURN: + * true = IRQ asserted + * false = IRQ not asserted + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static bool pt_check_irq_asserted(struct pt_core_data *cd) +{ +#ifdef ENABLE_WORKAROUND_FOR_GLITCH_AFTER_BL_LAUNCH_APP + /* + * Workaround for FW defect, CDT165308 + * bl_launch app creates a glitch in IRQ line + */ + if (cd->hid_cmd_state == PIP1_BL_CMD_ID_LAUNCH_APP + 1 + && cd->cpdata->irq_stat) { + /* + * in X1S panel and GC1546 panel, the width for the INT + * glitch is about 4us,the normal INT width of response + * will last more than 200us, so use 10us delay + * for distinguish the glitch the normal INT is enough. + */ + udelay(10); + } +#endif + if (cd->cpdata->irq_stat) { + if (cd->cpdata->irq_stat(cd->cpdata, cd->dev) + == PT_IRQ_ASSERTED_VALUE) { + /* Debounce to allow FW to release INT */ + usleep_range(100, 200); + } + if (cd->cpdata->irq_stat(cd->cpdata, cd->dev) + == PT_IRQ_ASSERTED_VALUE) + return true; + else + return false; + } + return true; +} + +/******************************************************************************* + * FUNCTION: pt_flush_bus + * + * SUMMARY: Force flushing the bus by reading len bytes or forced 255 bytes + * Used if IRQ is found to be stuck low + * + * RETURN: Length of bytes read from bus + * + * PARAMETERS: + * *cd - pointer to core data + * flush_type - type of flush + * - PT_FLUSH_BUS_BASED_ON_LEN (two reads) + * - PT_FLUSH_BUS_FULL_256_READ + * *read_buf - pointer to store read data + ******************************************************************************/ +static ssize_t pt_flush_bus(struct pt_core_data *cd, + u8 flush_type, u8 *read_buf) +{ + u8 buf[PT_MAX_PIP2_MSG_SIZE]; + u16 pip_len; + int bytes_read; + int rc = 0; + + if (flush_type == PT_FLUSH_BUS_BASED_ON_LEN) { + rc = pt_adap_read_default(cd, buf, 2); + if (rc) { + bytes_read = 0; + goto exit; + } + + pip_len = get_unaligned_le16(&buf[0]); + + if (pip_len == 2 || pip_len >= PT_PIP_1P7_EMPTY_BUF) { +#ifdef TTDL_DIAGNOSTICS + pt_toggle_err_gpio(cd, PT_ERR_GPIO_EMPTY_PACKET); +#endif + bytes_read = 2; + pt_debug(cd->dev, DL_INFO, + "%s: Empty buf detected - len=0x%04X\n", + __func__, pip_len); + } else if (pip_len == 0) { + bytes_read = 0; + pt_debug(cd->dev, DL_INFO, + "%s: Sentinel detected\n", __func__); + } else if (pip_len > PT_MAX_PIP2_MSG_SIZE) { + pt_debug(cd->dev, DL_ERROR, + "%s: Illegal len=0x%04x, force %d byte read\n", + __func__, pip_len, PT_MAX_PIP2_MSG_SIZE); + rc = pt_adap_read_default(cd, buf, + PT_MAX_PIP2_MSG_SIZE); + if (!rc) + bytes_read = PT_MAX_PIP2_MSG_SIZE; + else + bytes_read = 0; + } else { + pt_debug(cd->dev, DL_INFO, + "%s: Flush read of %d bytes...\n", + __func__, pip_len); + + rc = pt_adap_read_default(cd, buf, pip_len); + if (!rc) + bytes_read = pip_len; + else + bytes_read = 0; + } + } else { + pt_debug(cd->dev, DL_INFO, + "%s: Forced flush of max %d bytes...\n", + __func__, PT_MAX_PIP2_MSG_SIZE); + rc = pt_adap_read_default(cd, buf, PT_MAX_PIP2_MSG_SIZE); + if (!rc) + bytes_read = PT_MAX_PIP2_MSG_SIZE; + else + bytes_read = 0; + } + + if (read_buf && (bytes_read > 3)) + memcpy(read_buf, buf, bytes_read); + +exit: + return bytes_read; +} + +/******************************************************************************* + * FUNCTION: pt_flush_bus_if_irq_asserted + * + * SUMMARY: This function will flush the active bus if the INT is found to be + * asserted. + * + * RETURN: bytes cleared from bus + * + * PARAMETERS: + * *cd - pointer the core data structure + * flush_type - type of flush + * - PT_FLUSH_BUS_BASED_ON_LEN + * - PT_FLUSH_BUS_FULL_256_READ + ******************************************************************************/ +static int pt_flush_bus_if_irq_asserted(struct pt_core_data *cd, u8 flush_type) +{ + int count = 0; + int bytes_read = 0; + + while (pt_check_irq_asserted(cd) && count < 5) { + count++; + bytes_read = pt_flush_bus(cd, flush_type, NULL); + if (bytes_read) { + pt_debug(cd->dev, DL_WARN, + "%s: Cleared %d bytes off bus\n", + __func__, bytes_read); + } + } + if (pt_check_irq_asserted(cd)) { + pt_debug(cd->dev, DL_ERROR, + "%s: IRQ still asserted, %d bytes read\n", + __func__, bytes_read); + } else { + pt_debug(cd->dev, DL_INFO, + "%s: IRQ cleared, %d bytes read\n", + __func__, bytes_read); + } + return bytes_read; +} + +/******************************************************************************* + * FUNCTION: pt_hid_send_output_ + * + * SUMMARY: Send a touch application command to the DUT + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * *hid_output - pointer to the command to send + ******************************************************************************/ +static int pt_hid_send_output_(struct pt_core_data *cd, + struct pt_hid_output *hid_output) +{ + int rc = 0; + u8 *cmd; + u16 length; + u16 crc; + u8 report_id; + u8 cmd_offset = 0; + u8 cmd_allocated = 0; + + switch (hid_output->cmd_type) { + case PIP1_CMD_TYPE_FW: + report_id = PT_PIP_NON_HID_COMMAND_ID; + length = 5; + break; + case PIP1_CMD_TYPE_BL: + report_id = PT_PIP_BL_COMMAND_REPORT_ID; + length = 11 /* 5 + SOP + LEN(2) + CRC(2) + EOP */; + break; + default: + return -EINVAL; + } + + length += hid_output->write_length; + + if (length + 2 > PT_PREALLOCATED_CMD_BUFFER) { + cmd = kzalloc(length + 2, GFP_KERNEL); + if (!cmd) + return -ENOMEM; + cmd_allocated = 1; + } else { + cmd = cd->cmd_buf; + } + + /* Set Output register */ + memcpy(&cmd[cmd_offset], &cd->hid_desc.output_register, + sizeof(cd->hid_desc.output_register)); + cmd_offset += sizeof(cd->hid_desc.output_register); + + cmd[cmd_offset++] = LOW_BYTE(length); + cmd[cmd_offset++] = HI_BYTE(length); + cmd[cmd_offset++] = report_id; + cmd[cmd_offset++] = 0x0; /* reserved */ + if (hid_output->cmd_type == PIP1_CMD_TYPE_BL) + cmd[cmd_offset++] = PIP1_BL_SOP; + cmd[cmd_offset++] = hid_output->command_code; + + /* Set Data Length for bootloader */ + if (hid_output->cmd_type == PIP1_CMD_TYPE_BL) { + cmd[cmd_offset++] = LOW_BYTE(hid_output->write_length); + cmd[cmd_offset++] = HI_BYTE(hid_output->write_length); + } + /* Set Data */ + if (hid_output->write_length && hid_output->write_buf) { + memcpy(&cmd[cmd_offset], hid_output->write_buf, + hid_output->write_length); + cmd_offset += hid_output->write_length; + } + if (hid_output->cmd_type == PIP1_CMD_TYPE_BL) { + crc = _pt_compute_crc(&cmd[6], + hid_output->write_length + 4); + cmd[cmd_offset++] = LOW_BYTE(crc); + cmd[cmd_offset++] = HI_BYTE(crc); + cmd[cmd_offset++] = PIP1_BL_EOP; + } + + pt_debug(cd->dev, DL_INFO, + ">>> %s: Write Buffer Size[%d] Cmd[0x%02X]\n", + __func__, length + 2, hid_output->command_code); + + pt_pr_buf(cd->dev, DL_DEBUG, cmd, length + 2, ">>> CMD"); + rc = pt_adap_write_read_specific(cd, length + 2, cmd, NULL); + + if (rc) + pt_debug(cd->dev, DL_ERROR, + "%s: Fail pt_adap_transfer rc=%d\n", __func__, rc); + + if (cmd_allocated) + kfree(cmd); + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_pip1_send_output_and_wait_ + * + * SUMMARY: Send valid PIP1 command to the DUT and wait for the response. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * *hid_output - pointer to the command to send + ******************************************************************************/ +static int pt_pip1_send_output_and_wait_(struct pt_core_data *cd, + struct pt_hid_output *hid_output) +{ + int rc = 0; + int t; + u16 timeout_ms; + + mutex_lock(&cd->system_lock); + cd->hid_cmd_state = hid_output->command_code + 1; + mutex_unlock(&cd->system_lock); + + if (hid_output->timeout_ms) + timeout_ms = hid_output->timeout_ms; + else + timeout_ms = PT_PIP1_CMD_DEFAULT_TIMEOUT; + + rc = pt_hid_send_output_(cd, hid_output); + if (rc) + goto error; + + t = wait_event_timeout(cd->wait_q, (cd->hid_cmd_state == 0), + msecs_to_jiffies(timeout_ms)); + if (IS_TMO(t)) { +#ifdef TTDL_DIAGNOSTICS + cd->bus_transmit_error_count++; + pt_toggle_err_gpio(cd, PT_ERR_GPIO_I2C_TRANS); +#endif /* TTDL_DIAGNOSTICS */ + pt_debug(cd->dev, DL_ERROR, + "%s: HID output cmd execution timed out (%dms)\n", + __func__, timeout_ms); + rc = -ETIME; + goto error; + } + + if (!hid_output->novalidate) + rc = pt_hid_output_validate_response(cd, hid_output); + + pt_check_command(cd, hid_output, false); + goto exit; + +error: + mutex_lock(&cd->system_lock); + cd->hid_cmd_state = 0; + mutex_unlock(&cd->system_lock); +exit: + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_hid_output_user_cmd_ + * + * SUMMARY: Load the write buffer into a HID structure and send it as a HID cmd + * to the DUT waiting for the response and loading it into the read buffer + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * read_len - expected read length of the response + * *read_buf - pointer to where the response will be loaded + * write_len - length of the write buffer + * *write_buf - pointer to the write buffer + * *actual_read_len - pointer to the actual amount of data read back + ******************************************************************************/ +static int pt_hid_output_user_cmd_(struct pt_core_data *cd, + u16 read_len, u8 *read_buf, u16 write_len, u8 *write_buf, + u16 *actual_read_len) +{ + int rc = 0; + u16 size; + struct pt_hid_output hid_output = { + .length = write_len, + .write_buf = write_buf, + }; +#ifdef TTHE_TUNER_SUPPORT + if (!cd->pip2_send_user_cmd) { + int command_code = 0; + int len; + + /* Print up to cmd ID */ + len = PIP1_CMD_COMMAND_ID_OFFSET + 1; + if (write_len < len) + len = write_len; + else + command_code = write_buf[PIP1_CMD_COMMAND_ID_OFFSET] + & PIP1_CMD_COMMAND_ID_MASK; + + /* Don't print EXEC_PANEL_SCAN & RETRIEVE_PANEL_SCAN commands */ + if (command_code != PIP1_CMD_ID_EXEC_PANEL_SCAN && + command_code != PIP1_CMD_ID_RETRIEVE_PANEL_SCAN) + tthe_print(cd, write_buf, len, "CMD="); + } +#endif + rc = pt_hid_send_output_user_and_wait_(cd, &hid_output); + if (rc) + return rc; + + /* Get the response size from the first 2 bytes in the response */ + size = get_unaligned_le16(&cd->response_buf[0]); + + /* Ensure size is not greater than max buffer size */ + if (size > PT_MAX_PIP2_MSG_SIZE) + size = PT_MAX_PIP2_MSG_SIZE; + + /* Minimum size to read is the 2 byte len field */ + if (size == 0) + size = 2; + + if (size > read_len) { + pt_debug(cd->dev, DL_ERROR, + "%s: PIP2 len field=%d, requested read_len=%d\n", + __func__, size, read_len); + *actual_read_len = 0; + return -EIO; + } + + memcpy(read_buf, cd->response_buf, size); + *actual_read_len = size; + + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_hid_output_user_cmd + * + * SUMMARY: Protected call to pt_hid_output_user_cmd_ by exclusive access to + * the DUT. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * read_len - length of data to read + * *read_buf - pointer to store read data + * write_len - length of data to write + * *write_buf - pointer to buffer to write + * *actual_read_len - pointer to store data length actually read + ******************************************************************************/ +static int pt_hid_output_user_cmd(struct pt_core_data *cd, + u16 read_len, u8 *read_buf, u16 write_len, u8 *write_buf, + u16 *actual_read_len) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = pt_hid_output_user_cmd_(cd, read_len, read_buf, + write_len, write_buf, actual_read_len); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_request_pip2_send_cmd + * + * SUMMARY: Writes a PIP2 command packet to DUT, then waits for the + * interrupt and reads response data to read_buf + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * protect - flag to run in protected mode + * id - ID of PIP command + * *data - pointer to PIP data payload + * report_body_len - report length + * *read_buf - pointer to response buffer + * *actual_read_len - pointer to response buffer length + ******************************************************************************/ +static int _pt_request_pip2_send_cmd(struct device *dev, + int protect, u8 id, u8 *data, u16 report_body_len, u8 *read_buf, + u16 *actual_read_len) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pip2_cmd_structure pip2_cmd; + int rc = 0; + u16 i = 0; + u16 j = 0; + u16 write_len; + u8 *write_buf = NULL; + u16 read_len; + u8 extra_bytes; + + if (report_body_len > 247) + return -EPROTO; + + memset(&pip2_cmd, 0, sizeof(pip2_cmd)); + /* Hard coded register for PIP2.x */ + pip2_cmd.reg[0] = 0x01; + pip2_cmd.reg[1] = 0x01; + + /* + * For PIP2.1+ the length field value includes itself: + * ADD 6: 2 (LEN) + 1 (SEQ) + 1 (REPORT ID) + 2 (CRC) + * + * The overall write length must include only the register: + * ADD 2: 2 (Register) + */ + extra_bytes = 6; + write_len = 2; + + /* PIP2 the CMD ID is a 7bit field */ + if (id > PIP2_CMD_ID_END) { + pt_debug(dev, DL_WARN, "%s: Invalid PIP2 CMD ID 0x%02X\n", + __func__, id); + rc = -EINVAL; + goto exit; + } + + pip2_cmd.len = report_body_len + extra_bytes; + pip2_cmd.id = id & PIP2_CMD_COMMAND_ID_MASK; + pip2_cmd.seq = pt_pip2_get_next_cmd_seq(cd); + pip2_cmd.data = data; + + pt_pip2_cmd_calculate_crc(&pip2_cmd, extra_bytes); + + /* Add the command length to the extra bytes based on PIP version */ + write_len += pip2_cmd.len; + + pt_debug(dev, DL_INFO, "%s Length Field: %d, Write Len: %d", + __func__, pip2_cmd.len, write_len); + + write_buf = kzalloc(write_len, GFP_KERNEL); + + if (write_buf == NULL) { + rc = -ENOMEM; + goto exit; + } + + write_buf[i++] = pip2_cmd.reg[0]; + write_buf[i++] = pip2_cmd.reg[1]; + write_buf[i++] = pip2_cmd.len & 0xff; + write_buf[i++] = (pip2_cmd.len & 0xff00) >> 8; + write_buf[i++] = pip2_cmd.seq; + write_buf[i++] = pip2_cmd.id; + + for (j = i; j < i + pip2_cmd.len - extra_bytes; j++) + write_buf[j] = pip2_cmd.data[j-i]; + write_buf[j++] = pip2_cmd.crc[0]; + write_buf[j++] = pip2_cmd.crc[1]; + + read_len = pt_pip2_get_cmd_response_len(pip2_cmd.id); + if (read_len < 0) + read_len = 255; + + pt_debug(dev, DL_INFO, + "%s cmd_id[0x%02X] expected response length:%d ", + __func__, pip2_cmd.id, read_len); + + /* + * All PIP2 commands come through this function. + * Set flag for PIP2.x interface to allow response parsing to know + * how to decode the protocol header. + */ + mutex_lock(&cd->system_lock); + cd->pip2_prot_active = true; + cd->pip2_send_user_cmd = true; + mutex_unlock(&cd->system_lock); + + if (protect == PT_CORE_CMD_PROTECTED) + rc = pt_hid_output_user_cmd(cd, read_len, read_buf, + write_len, write_buf, actual_read_len); + else { + rc = pt_hid_output_user_cmd_(cd, read_len, read_buf, + write_len, write_buf, actual_read_len); + } + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: nonhid_cmd->user_cmd() Error = %d\n", + __func__, rc); + goto exit; + } + rc = pt_pip2_validate_response(cd, &pip2_cmd, read_buf, + *actual_read_len); +exit: + mutex_lock(&cd->system_lock); + cd->pip2_prot_active = false; + cd->pip2_send_user_cmd = false; + mutex_unlock(&cd->system_lock); + kfree(write_buf); + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_pip2_send_cmd_no_int + * + * SUMMARY: Writes a PIP2 command packet to DUT, then poll the response and + * reads response data to read_buf if response is available. + * + * NOTE: + * Interrupt MUST be disabled before to call this function. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * protect - flag to run in protected mode + * id - ID of PIP command + * *data - pointer to PIP data payload + * report_body_len - report length + * *read_buf - pointer to response buffer + * *actual_read_len - pointer to response buffer length + ******************************************************************************/ +static int _pt_pip2_send_cmd_no_int(struct device *dev, + int protect, u8 id, u8 *data, u16 report_body_len, u8 *read_buf, + u16 *actual_read_len) +{ + int max_retry = 0; + int retry = 0; + int rc = 0; + u16 i = 0; + u16 j = 0; + u16 write_len; + u8 *write_buf = NULL; + u16 read_len; + u16 size = 0; + u8 response_seq = 0; + u8 extra_bytes; + u32 retry_interval = 0; + u32 retry_total_time = 0; + u32 resp_time_min = pt_pip2_get_cmd_resp_time_min(id); + u32 resp_time_max = pt_pip2_get_cmd_resp_time_max(id); + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pip2_cmd_structure pip2_cmd; + + if (report_body_len > 247) + return -EPROTO; + + if (protect == PT_CORE_CMD_PROTECTED) { + rc = request_exclusive(cd, + cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + } + + memset(&pip2_cmd, 0, sizeof(pip2_cmd)); + + /* Hard coded register for PIP2.x */ + pip2_cmd.reg[0] = 0x01; + pip2_cmd.reg[1] = 0x01; + + /* + * For PIP2.1+ the length field value includes itself: + * ADD 6: 2 (LEN) + 1 (SEQ) + 1 (REPORT ID) + 2 (CRC) + * + * The overall write length must include only the register: + * ADD 2: 2 (Register) + */ + extra_bytes = 6; + write_len = 2; + + pip2_cmd.len = report_body_len + extra_bytes; + pip2_cmd.id = id; + pip2_cmd.seq = pt_pip2_get_next_cmd_seq(cd); + pip2_cmd.data = data; + pt_pip2_cmd_calculate_crc(&pip2_cmd, extra_bytes); + + /* Add the command length to the extra bytes based on PIP version */ + write_len += pip2_cmd.len; + + write_buf = kzalloc(write_len, GFP_KERNEL); + if (write_buf == NULL) { + rc = -ENOMEM; + goto exit; + } + + write_buf[i++] = pip2_cmd.reg[0]; + write_buf[i++] = pip2_cmd.reg[1]; + write_buf[i++] = pip2_cmd.len & 0xff; + write_buf[i++] = (pip2_cmd.len & 0xff00) >> 8; + write_buf[i++] = pip2_cmd.seq; + write_buf[i++] = pip2_cmd.id; + + for (j = i; j < i + pip2_cmd.len - extra_bytes; j++) + write_buf[j] = pip2_cmd.data[j-i]; + write_buf[j++] = pip2_cmd.crc[0]; + write_buf[j++] = pip2_cmd.crc[1]; + + read_len = pt_pip2_get_cmd_response_len(pip2_cmd.id); + if (read_len < 0) + read_len = 255; + pt_debug(dev, DL_INFO, + "%s: ATM - cmd_id[0x%02X] expected response length:%d ", + __func__, pip2_cmd.id, read_len); + + pt_pr_buf(cd->dev, DL_DEBUG, write_buf, write_len, ">>> NO_INT CMD"); + + rc = pt_adap_write_read_specific(cd, write_len, write_buf, NULL); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: SPI write Error = %d\n", + __func__, rc); + goto exit; + } + +#ifdef PT_POLL_RESP_BY_BUS + /* + * Frequent bus read can increase system load obviously. The expected + * first bus read should be valid and timely. The tollerance for + * usleep_range should be limited. The minimum response delay (between + * command finishes sending and INT pin falls) is less than 50 + * microseconds. So the 10 microseconds should be maximum tollerance + * with the consideration that the unit to calculate the response delay + * is 10 microseconds and more precise is not necessary. Every + * additional 10 microseconds only contribute less than 3 milliseconds + * for whole BL. + */ + usleep_range(resp_time_min, resp_time_min+10); + max_retry = resp_time_max / POLL_RETRY_DEFAULT_INTERVAL; + while ((retry < max_retry) && (retry_total_time < resp_time_max)) { + rc = pt_adap_read_default(cd, read_buf, read_len); + if (rc) { + pt_debug(dev, DL_ERROR, "%s: SPI read Error = %d\n", + __func__, rc); + break; + } + response_seq = read_buf[PIP2_RESP_SEQUENCE_OFFSET]; + size = get_unaligned_le16(&read_buf[0]); + if ((size <= read_len) && + (size >= PIP2_EXTRA_BYTES_NUM) && + (pip2_cmd.seq & 0x07) == (response_seq & 0x07)) { + break; + } + + /* + * To reduce the bus and system load, increase the sleep + * step gradually: + * 1 ~ 19 : step=50 us, sleep_us=[50, 100, 150, 200, ..950] + * 20 ~ 39 : step=1000 us, sleep_us=[1950, 2950, ...20950] + * 40 ~ MAX: step=50 ms, sleep_ms=[71, 121, 191,..] + */ + retry++; + if (retry < 20) { + retry_interval += POLL_RETRY_DEFAULT_INTERVAL; + usleep_range(retry_interval, + retry_interval + POLL_RETRY_DEFAULT_INTERVAL); + } else if (retry < 40) { + retry_interval += 1000; + usleep_range(retry_interval, retry_interval + 1000); + } else { + retry_interval += 50000; + msleep(retry_interval/1000); + } + retry_total_time += retry_interval; + } +#else + /* + * Frequent GPIO read will not increase CPU/system load heavily if the + * interval is longer than 10 us, so it is safe to poll GPIO with a + * fixed interval: 20 us. + */ + usleep_range(resp_time_min, resp_time_min+10); + max_retry = resp_time_max / POLL_RETRY_DEFAULT_INTERVAL; + while ((retry < max_retry) && (retry_total_time < resp_time_max)) { + if (!gpio_get_value(cd->cpdata->irq_gpio)) { + rc = pt_adap_read_default(cd, read_buf, read_len); + size = get_unaligned_le16(&read_buf[0]); + if (rc) + pt_debug(dev, DL_ERROR, + "%s: SPI read Error = %d\n", + __func__, rc); + else if (size > read_len) { + pt_debug(cd->dev, DL_ERROR, + "%s: PIP2 len field=%d, requested read_len=%d\n", + __func__, size, read_len); + rc = -EIO; + } + break; + } + /* + * Poll GPIO with fixed interval 20 us, and tollerance is + * limited to 10 us to speed up the process. + */ + retry_interval = POLL_RETRY_DEFAULT_INTERVAL; + usleep_range(retry_interval, retry_interval+10); + retry_total_time += retry_interval; + } +#endif + + *actual_read_len = size; + + if (rc || (retry >= max_retry) || (retry_total_time >= resp_time_max)) { + pt_debug(dev, DL_ERROR, + "%s cmd[0x%02X] timed out, send_seq=0x%02X, resp_seq=0x%02X\n", + __func__, pip2_cmd.id, pip2_cmd.seq, response_seq); + *actual_read_len = 0; + rc = -EINVAL; + } + + pt_pr_buf(cd->dev, DL_DEBUG, read_buf, *actual_read_len, + "<<< NO_INT Read"); + +exit: + kfree(write_buf); + if (protect == PT_CORE_CMD_PROTECTED) { + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + } + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_pip_null_ + * + * SUMMARY: Send the PIP "ping"(0x00) command to the DUT and wait for response. + * This function is used by watchdog to check if the fw corrupts. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static int pt_pip_null_(struct pt_core_data *cd) +{ + struct pt_hid_output hid_output = { + CREATE_PIP1_FW_CMD(PIP1_CMD_ID_NULL), + }; + return pt_pip1_send_output_and_wait_(cd, &hid_output); +} + +/******************************************************************************* + * FUNCTION: pt_pip_null + * + * SUMMARY: Wrapper function for pt_pip_null_ that guarantees exclusive access. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static int pt_pip_null(struct pt_core_data *cd) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = pt_pip_null_(cd); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + + return rc; +} + +static void pt_stop_wd_timer(struct pt_core_data *cd); +/******************************************************************************* + * FUNCTION: pt_pip_start_bootloader_ + * + * SUMMARY: Sends the HID command start_bootloader [PIP cmd 0x01] to the DUT + * + * NOTE: The WD MUST be stopped/restarted by the calling Function. Having + * the WD active could cause this function to fail! + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static int pt_pip_start_bootloader_(struct pt_core_data *cd) +{ + int rc = 0; + + struct pt_hid_output hid_output = { + CREATE_PIP1_FW_CMD(PIP1_CMD_ID_START_BOOTLOADER), + .timeout_ms = PT_PIP1_START_BOOTLOADER_TIMEOUT, + .reset_expected = 1, + }; + if (cd->watchdog_enabled) { + pt_debug(cd->dev, DL_WARN, + "%s: watchdog isn't stopped before enter bl\n", + __func__); + goto exit; + } + + /* Reset startup status after entering BL, new DUT enum required */ + cd->startup_status = STARTUP_STATUS_START; + pt_debug(cd->dev, DL_DEBUG, "%s: Startup Status Reset\n", __func__); + rc = pt_pip1_send_output_and_wait_(cd, &hid_output); + if (rc) { + pt_debug(cd->dev, DL_ERROR, + "%s: Start BL PIP cmd failed. rc = %d\n", + __func__, rc); + } + +exit: + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_pip_start_bootloader + * + * SUMMARY: Protected function to force DUT to enter the BL + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data structure + ******************************************************************************/ +static int pt_pip_start_bootloader(struct pt_core_data *cd) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = pt_pip_start_bootloader_(cd); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_request_pip_start_bl + * + * SUMMARY: Function pointer included in core_nonhid_cmds to allow other + * modules to request the DUT to enter the BL + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * protect - flag to run in protected mode + ******************************************************************************/ +static int _pt_request_pip_start_bl(struct device *dev, int protect) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return pt_pip_start_bootloader(cd); + + return pt_pip_start_bootloader_(cd); +} + +/******************************************************************************* + * FUNCTION: pt_pip2_ver_load_ttdata + * + * SUMMARY: Function to load the Version information from the PIP2 VERSION + * command into the core data struct. + * + * RETURN: n/a + * + * PARAMETERS: + * *cd - pointer to core data structure + * len - Length of data in response_buf + ******************************************************************************/ +static void pt_pip2_ver_load_ttdata(struct pt_core_data *cd, u16 len) +{ + struct pt_ttdata *ttdata = &cd->sysinfo.ttdata; + struct pt_pip2_version_full *full_ver; + struct pt_pip2_version *ver; + + /* + * The PIP2 VERSION command can return different lengths of data. + * The additional LOT fields are included when the packet + * size is >= 29 bytes. Older FW sends a reduced packet size. + * NOTE: + * - The FW would swap the BL and FW versions when reporting + * the small packet. + * - Sub Lot bytes 16 and 17 are reserved. + */ + if (len >= 0x1D) { + full_ver = (struct pt_pip2_version_full *) + &cd->response_buf[PIP2_RESP_STATUS_OFFSET]; + + ttdata->pip_ver_major = full_ver->pip2_version_msb; + ttdata->pip_ver_minor = full_ver->pip2_version_lsb; + ttdata->bl_ver_major = full_ver->bl_version_msb; + ttdata->bl_ver_minor = full_ver->bl_version_lsb; + ttdata->fw_ver_major = full_ver->fw_version_msb; + ttdata->fw_ver_minor = full_ver->fw_version_lsb; + /* + * BL PIP 2.02 and greater the version fields are + * swapped + */ + if (ttdata->pip_ver_major >= 2 && ttdata->pip_ver_minor >= 2) { + ttdata->chip_rev = + get_unaligned_le16(&full_ver->chip_rev); + ttdata->chip_id = + get_unaligned_le16(&full_ver->chip_id); + } else { + ttdata->chip_rev = + get_unaligned_le16(&full_ver->chip_id); + ttdata->chip_id = + get_unaligned_le16(&full_ver->chip_rev); + } + memcpy(ttdata->uid, full_ver->uid, PT_UID_SIZE); + + pt_pr_buf(cd->dev, DL_INFO, (u8 *)full_ver, + sizeof(struct pt_pip2_version_full), + "PIP2 VERSION FULL"); + } else { + ver = (struct pt_pip2_version *) + &cd->response_buf[PIP2_RESP_STATUS_OFFSET]; + + ttdata->pip_ver_major = ver->pip2_version_msb; + ttdata->pip_ver_minor = ver->pip2_version_lsb; + ttdata->bl_ver_major = ver->bl_version_msb; + ttdata->bl_ver_minor = ver->bl_version_lsb; + ttdata->fw_ver_major = ver->fw_version_msb; + ttdata->fw_ver_minor = ver->fw_version_lsb; + ttdata->chip_rev = get_unaligned_le16(&ver->chip_rev); + ttdata->chip_id = get_unaligned_le16(&ver->chip_id); + + pt_pr_buf(cd->dev, DL_INFO, (u8 *)ver, + sizeof(struct pt_pip2_version), "PIP2 VERSION"); + } +} + +/******************************************************************************* + * FUNCTION: pt_si_get_ttdata + * + * SUMMARY: Function to load the version information from the system information + * PIP command into the core data struct. + * + * RETURN: n/a + * + * PARAMETERS: + * *cd - pointer to core data structure + ******************************************************************************/ +static void pt_si_get_ttdata(struct pt_core_data *cd) +{ + struct pt_ttdata *ttdata = &cd->sysinfo.ttdata; + struct pt_ttdata_dev *ttdata_dev = + (struct pt_ttdata_dev *) + &cd->response_buf[PIP1_SYSINFO_TTDATA_OFFSET]; + + ttdata->pip_ver_major = ttdata_dev->pip_ver_major; + ttdata->pip_ver_minor = ttdata_dev->pip_ver_minor; + ttdata->bl_ver_major = ttdata_dev->bl_ver_major; + ttdata->bl_ver_minor = ttdata_dev->bl_ver_minor; + ttdata->fw_ver_major = ttdata_dev->fw_ver_major; + ttdata->fw_ver_minor = ttdata_dev->fw_ver_minor; + + ttdata->fw_pid = get_unaligned_le16(&ttdata_dev->fw_pid); + ttdata->fw_ver_conf = get_unaligned_le16(&ttdata_dev->fw_ver_conf); + ttdata->post_code = get_unaligned_le16(&ttdata_dev->post_code); + ttdata->revctrl = get_unaligned_le32(&ttdata_dev->revctrl); + ttdata->jtag_id_l = get_unaligned_le16(&ttdata_dev->jtag_si_id_l); + ttdata->jtag_id_h = get_unaligned_le16(&ttdata_dev->jtag_si_id_h); + + memcpy(ttdata->mfg_id, ttdata_dev->mfg_id, PT_NUM_MFGID); + + pt_pr_buf(cd->dev, DL_INFO, (u8 *)ttdata_dev, + sizeof(struct pt_ttdata_dev), "sysinfo_ttdata"); +} + +/******************************************************************************* + * FUNCTION: pt_si_get_sensing_conf_data + * + * SUMMARY: Function to load the sensing information from the system information + * PIP command into the core data struct. + * + * RETURN: n/a + * + * PARAMETERS: + * *cd - pointer to core data structure + ******************************************************************************/ +static void pt_si_get_sensing_conf_data(struct pt_core_data *cd) +{ + struct pt_sensing_conf_data *scd = &cd->sysinfo.sensing_conf_data; + struct pt_sensing_conf_data_dev *scd_dev = + (struct pt_sensing_conf_data_dev *) + &cd->response_buf[PIP1_SYSINFO_SENSING_OFFSET]; + + scd->electrodes_x = scd_dev->electrodes_x; + scd->electrodes_y = scd_dev->electrodes_y; + scd->origin_x = scd_dev->origin_x; + scd->origin_y = scd_dev->origin_y; + + /* PIP 1.4 (001-82649 *Q) add X_IS_TX bit in X_ORG */ + if (scd->origin_x & 0x02) { + scd->tx_num = scd->electrodes_x; + scd->rx_num = scd->electrodes_y; + } else { + scd->tx_num = scd->electrodes_y; + scd->rx_num = scd->electrodes_x; + } + /* + * When the Panel ID is coming from an XY pin and not a dedicated + * GPIO, store the PID in pid_for_loader. This cannot be done for all + * other DUTs as the loader will use cd->pid_for_loader to generate + * the bin file name but will ignore it if pid_for_loader is still + * set to PANEL_ID_NOT_ENABLED + */ + if (cd->panel_id_support & + (PT_PANEL_ID_BY_BL | PT_PANEL_ID_BY_SYS_INFO)) { + mutex_lock(&cd->system_lock); + cd->pid_for_loader = scd_dev->panel_id; + mutex_unlock(&cd->system_lock); + } + + scd->panel_id = scd_dev->panel_id; + scd->btn = scd_dev->btn; + scd->scan_mode = scd_dev->scan_mode; + scd->max_tch = scd_dev->max_num_of_tch_per_refresh_cycle; + + scd->res_x = get_unaligned_le16(&scd_dev->res_x); + scd->res_y = get_unaligned_le16(&scd_dev->res_y); + scd->max_z = get_unaligned_le16(&scd_dev->max_z); + scd->len_x = get_unaligned_le16(&scd_dev->len_x); + scd->len_y = get_unaligned_le16(&scd_dev->len_y); + + pt_pr_buf(cd->dev, DL_INFO, (u8 *)scd_dev, + sizeof(struct pt_sensing_conf_data_dev), + "sensing_conf_data"); +} + +/******************************************************************************* + * FUNCTION: pt_si_setup + * + * SUMMARY: Setup the xy_data and xy_mode by allocating the needed memory + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data structure + ******************************************************************************/ +static int pt_si_setup(struct pt_core_data *cd) +{ + struct pt_sysinfo *si = &cd->sysinfo; + int max_tch = si->sensing_conf_data.max_tch; + + if (!si->xy_data) + si->xy_data = kzalloc(max_tch * si->desc.tch_record_size, + GFP_KERNEL); + if (!si->xy_data) + return -ENOMEM; + + if (!si->xy_mode) + si->xy_mode = kzalloc(si->desc.tch_header_size, GFP_KERNEL); + if (!si->xy_mode) { + kfree(si->xy_data); + return -ENOMEM; + } + + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_si_get_btn_data + * + * SUMMARY: Setup the core data button information based on the response of the + * System Information PIP command. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data structure + ******************************************************************************/ +static int pt_si_get_btn_data(struct pt_core_data *cd) +{ + struct pt_sysinfo *si = &cd->sysinfo; + int num_btns = 0; + int num_defined_keys; + u16 *key_table; + int btn; + int i; + int rc = 0; + unsigned int btns = cd->response_buf[PIP1_SYSINFO_BTN_OFFSET] + & PIP1_SYSINFO_BTN_MASK; + size_t btn_keys_size; + + pt_debug(cd->dev, DL_INFO, "%s: get btn data\n", __func__); + + for (i = 0; i < PIP1_SYSINFO_MAX_BTN; i++) { + if (btns & (1 << i)) + num_btns++; + } + si->num_btns = num_btns; + + if (num_btns) { + btn_keys_size = num_btns * sizeof(struct pt_btn); + if (!si->btn) + si->btn = kzalloc(btn_keys_size, GFP_KERNEL); + if (!si->btn) + return -ENOMEM; + + if (cd->cpdata->sett[PT_IC_GRPNUM_BTN_KEYS] == NULL) + num_defined_keys = 0; + else if (cd->cpdata->sett[PT_IC_GRPNUM_BTN_KEYS]->data == NULL) + num_defined_keys = 0; + else + num_defined_keys = cd->cpdata->sett + [PT_IC_GRPNUM_BTN_KEYS]->size; + + for (btn = 0; btn < num_btns && btn < num_defined_keys; btn++) { + key_table = (u16 *)cd->cpdata->sett + [PT_IC_GRPNUM_BTN_KEYS]->data; + si->btn[btn].key_code = key_table[btn]; + si->btn[btn].enabled = true; + } + for (; btn < num_btns; btn++) { + si->btn[btn].key_code = KEY_RESERVED; + si->btn[btn].enabled = true; + } + + return rc; + } + + kfree(si->btn); + si->btn = NULL; + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_si_put_log_data + * + * SUMMARY: Prints all sys info data to kmsg log + * + * RETURN: n/a + * + * PARAMETERS: + * *cd - pointer to core data structure + ******************************************************************************/ +static void pt_si_put_log_data(struct pt_core_data *cd) +{ + struct pt_sysinfo *si = &cd->sysinfo; + struct pt_ttdata *ttdata = &si->ttdata; + struct pt_sensing_conf_data *scd = &si->sensing_conf_data; + int i; + + pt_debug(cd->dev, DL_DEBUG, "%s: pip_ver_major = 0x%02X (%d)\n", + __func__, ttdata->pip_ver_major, ttdata->pip_ver_major); + pt_debug(cd->dev, DL_DEBUG, "%s: pip_ver_minor = 0x%02X (%d)\n", + __func__, ttdata->pip_ver_minor, ttdata->pip_ver_minor); + pt_debug(cd->dev, DL_DEBUG, "%s: fw_pid = 0x%04X (%d)\n", + __func__, ttdata->fw_pid, ttdata->fw_pid); + pt_debug(cd->dev, DL_DEBUG, "%s: fw_ver_major = 0x%02X (%d)\n", + __func__, ttdata->fw_ver_major, ttdata->fw_ver_major); + pt_debug(cd->dev, DL_DEBUG, "%s: fw_ver_minor = 0x%02X (%d)\n", + __func__, ttdata->fw_ver_minor, ttdata->fw_ver_minor); + pt_debug(cd->dev, DL_DEBUG, "%s: revctrl = 0x%08X (%d)\n", + __func__, ttdata->revctrl, ttdata->revctrl); + pt_debug(cd->dev, DL_DEBUG, "%s: fw_ver_conf = 0x%04X (%d)\n", + __func__, ttdata->fw_ver_conf, ttdata->fw_ver_conf); + pt_debug(cd->dev, DL_DEBUG, "%s: bl_ver_major = 0x%02X (%d)\n", + __func__, ttdata->bl_ver_major, ttdata->bl_ver_major); + pt_debug(cd->dev, DL_DEBUG, "%s: bl_ver_minor = 0x%02X (%d)\n", + __func__, ttdata->bl_ver_minor, ttdata->bl_ver_minor); + pt_debug(cd->dev, DL_DEBUG, "%s: jtag_id_h = 0x%04X (%d)\n", + __func__, ttdata->jtag_id_h, ttdata->jtag_id_h); + pt_debug(cd->dev, DL_DEBUG, "%s: jtag_id_l = 0x%04X (%d)\n", + __func__, ttdata->jtag_id_l, ttdata->jtag_id_l); + + for (i = 0; i < PT_NUM_MFGID; i++) + pt_debug(cd->dev, DL_DEBUG, + "%s: mfg_id[%d] = 0x%02X (%d)\n", + __func__, i, ttdata->mfg_id[i], + ttdata->mfg_id[i]); + + pt_debug(cd->dev, DL_DEBUG, "%s: post_code = 0x%04X (%d)\n", + __func__, ttdata->post_code, ttdata->post_code); + pt_debug(cd->dev, DL_DEBUG, "%s: electrodes_x = 0x%02X (%d)\n", + __func__, scd->electrodes_x, scd->electrodes_x); + pt_debug(cd->dev, DL_DEBUG, "%s: electrodes_y = 0x%02X (%d)\n", + __func__, scd->electrodes_y, scd->electrodes_y); + pt_debug(cd->dev, DL_DEBUG, "%s: len_x = 0x%04X (%d)\n", + __func__, scd->len_x, scd->len_x); + pt_debug(cd->dev, DL_DEBUG, "%s: len_y = 0x%04X (%d)\n", + __func__, scd->len_y, scd->len_y); + pt_debug(cd->dev, DL_DEBUG, "%s: res_x = 0x%04X (%d)\n", + __func__, scd->res_x, scd->res_x); + pt_debug(cd->dev, DL_DEBUG, "%s: res_y = 0x%04X (%d)\n", + __func__, scd->res_y, scd->res_y); + pt_debug(cd->dev, DL_DEBUG, "%s: max_z = 0x%04X (%d)\n", + __func__, scd->max_z, scd->max_z); + pt_debug(cd->dev, DL_DEBUG, "%s: origin_x = 0x%02X (%d)\n", + __func__, scd->origin_x, scd->origin_x); + pt_debug(cd->dev, DL_DEBUG, "%s: origin_y = 0x%02X (%d)\n", + __func__, scd->origin_y, scd->origin_y); + pt_debug(cd->dev, DL_DEBUG, "%s: panel_id = 0x%02X (%d)\n", + __func__, scd->panel_id, scd->panel_id); + pt_debug(cd->dev, DL_DEBUG, "%s: btn =0x%02X (%d)\n", + __func__, scd->btn, scd->btn); + pt_debug(cd->dev, DL_DEBUG, "%s: scan_mode = 0x%02X (%d)\n", + __func__, scd->scan_mode, scd->scan_mode); + pt_debug(cd->dev, DL_DEBUG, + "%s: max_num_of_tch_per_refresh_cycle = 0x%02X (%d)\n", + __func__, scd->max_tch, scd->max_tch); + pt_debug(cd->dev, DL_DEBUG, "%s: xy_mode = %p\n", + __func__, si->xy_mode); + pt_debug(cd->dev, DL_DEBUG, "%s: xy_data = %p\n", + __func__, si->xy_data); +} + +/******************************************************************************* + * FUNCTION: pt_get_sysinfo_regs + * + * SUMMARY: Setup all the core data System information based on the response + * of the System Information PIP command. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data structure + ******************************************************************************/ +static int pt_get_sysinfo_regs(struct pt_core_data *cd) +{ + struct pt_sysinfo *si = &cd->sysinfo; + int rc; + + rc = pt_si_get_btn_data(cd); + if (rc < 0) + return rc; + + pt_si_get_ttdata(cd); + + pt_si_get_sensing_conf_data(cd); + + pt_si_setup(cd); + + pt_si_put_log_data(cd); + + si->ready = true; + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_free_si_ptrs + * + * SUMMARY: Frees all memory associated with the System Information within + * core data + * + * RETURN: n/a + * + * PARAMETERS: + * *cd - pointer to core data structure + ******************************************************************************/ +static void pt_free_si_ptrs(struct pt_core_data *cd) +{ + struct pt_sysinfo *si = &cd->sysinfo; + + kfree(si->btn); + kfree(si->xy_mode); + kfree(si->xy_data); +} + +/******************************************************************************* + * FUNCTION: pt_hid_output_get_sysinfo_ + * + * SUMMARY: Sends the PIP Get SYS INFO command to the DUT and waits for the + * response. + * + * RETURN:: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data structure + ******************************************************************************/ +static int pt_hid_output_get_sysinfo_(struct pt_core_data *cd) +{ + int rc = 0; + struct pt_hid_output hid_output = { + CREATE_PIP1_FW_CMD(PIP1_CMD_ID_GET_SYSINFO), + .timeout_ms = PT_PIP1_CMD_GET_SYSINFO_TIMEOUT, + }; + + rc = pt_pip1_send_output_and_wait_(cd, &hid_output); + if (rc) + return rc; + + /* Parse the sysinfo data */ + rc = pt_get_sysinfo_regs(cd); + if (rc) + pt_free_si_ptrs(cd); + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_hid_output_get_sysinfo + * + * SUMMARY: Protected call to pt_hid_output_get_sysinfo_ + * + * RETURN:: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data structure + ******************************************************************************/ +static int pt_hid_output_get_sysinfo(struct pt_core_data *cd) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = pt_hid_output_get_sysinfo_(cd); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_pip_suspend_scanning_ + * + * SUMMARY: Sends the PIP Suspend Scanning command to the DUT + * + * RETURN:: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data structure + ******************************************************************************/ +static int pt_pip_suspend_scanning_(struct pt_core_data *cd) +{ + int rc = 0; + struct pt_hid_output hid_output = { + CREATE_PIP1_FW_CMD(PIP1_CMD_ID_SUSPEND_SCANNING), + }; + + rc = pt_pip1_send_output_and_wait_(cd, &hid_output); + if (rc) { + pt_debug(cd->dev, DL_ERROR, + "%s: Suspend Scan PIP cmd failed. rc = %d\n", + __func__, rc); + } + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_pip_suspend_scanning + * + * SUMMARY: Protected wrapper for calling pt_hid_output_suspend_scanning_ + * + * RETURN:: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data structure + ******************************************************************************/ +static int pt_pip_suspend_scanning(struct pt_core_data *cd) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = pt_pip_suspend_scanning_(cd); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_request_pip_suspend_scanning + * + * SUMMARY: Function pointer included in core_nonhid_cmd struct for external + * calls to the protected or unprotected call to pt_pip_suspend_scanning + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * protect - 0 = call non-protected function + * 1 = call protected function + ******************************************************************************/ +static int _pt_request_pip_suspend_scanning(struct device *dev, + int protect) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return pt_pip_suspend_scanning(cd); + + return pt_pip_suspend_scanning_(cd); +} + +/******************************************************************************* + * FUNCTION: pt_pip_resume_scanning_ + * + * SUMMARY: Sends the PIP Resume Scanning command to the DUT + * + * RETURN:: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data structure + ******************************************************************************/ +static int pt_pip_resume_scanning_(struct pt_core_data *cd) +{ + int rc = 0; + struct pt_hid_output hid_output = { + CREATE_PIP1_FW_CMD(PIP1_CMD_ID_RESUME_SCANNING), + }; + + rc = pt_pip1_send_output_and_wait_(cd, &hid_output); + if (rc) { + pt_debug(cd->dev, DL_ERROR, + "%s: Resume Scan PIP cmd failed. rc = %d\n", + __func__, rc); + } + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_pip_resume_scanning + * + * SUMMARY: Protected wrapper for calling pt_pip_resume_scanning_ + * + * RETURN:: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data structure + ******************************************************************************/ +static int pt_pip_resume_scanning(struct pt_core_data *cd) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = pt_pip_resume_scanning_(cd); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_request_pip_resume_scanning + * + * SUMMARY: Function pointer included in core_nonhid_cmd struct for external + * calls to the protected or unprotected call to pt_pip_resume_scanning + * + * RETURN:: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * protect - 0 = call non-protected function + * 1 = call protected function + ******************************************************************************/ +static int _pt_request_pip_resume_scanning(struct device *dev, + int protect) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return pt_pip_resume_scanning(cd); + + return pt_pip_resume_scanning_(cd); +} + +/******************************************************************************* + * FUNCTION: pt_pip_get_param_ + * + * SUMMARY: Sends a PIP command 0x05 Get Parameter to the DUT and returns + * the 32bit parameter value + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * param_id - parameter ID to retrieve + * *value - value of DUT parameter + ******************************************************************************/ +static int pt_pip_get_param_(struct pt_core_data *cd, + u8 param_id, u32 *value) +{ + int write_length = 1; + u8 param[1] = { param_id }; + u8 read_param_id; + int param_size; + u8 *ptr; + int rc = 0; + int i; + struct pt_hid_output hid_output = { + CREATE_PIP1_FW_CMD(PIP1_CMD_ID_GET_PARAM), + .write_length = write_length, + .write_buf = param, + }; + + rc = pt_pip1_send_output_and_wait_(cd, &hid_output); + if (rc) + return rc; + + read_param_id = cd->response_buf[5]; + if (read_param_id != param_id) + return -EPROTO; + + param_size = cd->response_buf[6]; + ptr = &cd->response_buf[7]; + *value = 0; + for (i = 0; i < param_size; i++) + *value += ptr[i] << (i * 8); + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_pip_get_param + * + * SUMMARY: Protected call to pt_hid_output_get_param_ by a request exclusive + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * param_id - parameter ID to retrieve + * *value - value of DUT parameter + ******************************************************************************/ +static int pt_pip_get_param(struct pt_core_data *cd, + u8 param_id, u32 *value) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = pt_pip_get_param_(cd, param_id, value); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_request_pip_get_param + * + * SUMMARY: Function pointer included in core_nonhid_cmd struct for external + * calls to the protected or unprotected call to pt_pip_get_param + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * protect - flag to call protected or non protected function + * param_id - parameter ID to retrieve + * *value - value of DUT parameter + ******************************************************************************/ +int _pt_request_pip_get_param(struct device *dev, + int protect, u8 param_id, u32 *value) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return pt_pip_get_param(cd, param_id, value); + + return pt_pip_get_param_(cd, param_id, value); +} + +/******************************************************************************* + * FUNCTION: pt_pip_set_param_ + * + * SUMMARY: Sends a PIP command 0x06 Set Parameter to the DUT writing the + * passed in value to flash + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * param_id - parameter ID to set + * value - value to write + * size - size to write + ******************************************************************************/ +static int pt_pip_set_param_(struct pt_core_data *cd, + u8 param_id, u32 value, u8 size) +{ + u8 write_buf[6]; + u8 *ptr = &write_buf[2]; + int rc = 0; + int i; + struct pt_hid_output hid_output = { + CREATE_PIP1_FW_CMD(PIP1_CMD_ID_SET_PARAM), + .write_buf = write_buf, + }; + write_buf[0] = param_id; + write_buf[1] = size; + for (i = 0; i < size; i++) { + ptr[i] = value & 0xFF; + value = value >> 8; + } + + hid_output.write_length = 2 + size; + + rc = pt_pip1_send_output_and_wait_(cd, &hid_output); + if (rc) + return rc; + + if (param_id != cd->response_buf[5] || size != cd->response_buf[6]) + return -EPROTO; + + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_pip_set_param + * + * SUMMARY: Protected call to pt_hid_output_set_param_ by a request exclusive + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * param_id - parameter ID to set + * value - value to write + * size - size to write + ******************************************************************************/ +static int pt_pip_set_param(struct pt_core_data *cd, + u8 param_id, u32 value, u8 size) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = pt_pip_set_param_(cd, param_id, value, size); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_request_pip_set_param + * + * SUMMARY: Function pointer included in core_nonhid_cmd struct for external + * calls to the protected or unprotected call to pt_pip_set_param + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * protect - flag to call protected or non-protected + * param_id - parameter ID to set + * value - value to write + * size - size to write + ******************************************************************************/ +int _pt_request_pip_set_param(struct device *dev, int protect, + u8 param_id, u32 value, u8 size) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return pt_pip_set_param(cd, param_id, value, size); + + return pt_pip_set_param_(cd, param_id, value, size); +} + +/******************************************************************************* + * FUNCTION: _pt_pip_enter_easywake_state_ + * + * SUMMARY: Sends a PIP command 0x09 Enter EasyWake State to the DUT + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * data - easywake guesture (Only used for PIP1.6 and earlier) + * *return_data - return status if easywake was entered + ******************************************************************************/ +static int pt_hid_output_enter_easywake_state_( + struct pt_core_data *cd, u8 data, u8 *return_data) +{ + int write_length = 1; + u8 param[1] = { data }; + int rc = 0; + struct pt_hid_output hid_output = { + CREATE_PIP1_FW_CMD(PIP1_CMD_ID_ENTER_EASYWAKE_STATE), + .write_length = write_length, + .write_buf = param, + }; + rc = pt_pip1_send_output_and_wait_(cd, &hid_output); + if (rc) + return rc; + + *return_data = cd->response_buf[5]; + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_pip_verify_config_block_crc_ + * + * SUMMARY: Sends the PIP "Verify Data Block CRC" (0x20) command to the DUT + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer the core data structure + * ebid - enumerated block ID + * *status - PIP command status + * calculated_crc - calculated CRC + * stored_crc - stored CRC in config area + ******************************************************************************/ +static int pt_pip_verify_config_block_crc_( + struct pt_core_data *cd, u8 ebid, u8 *status, + u16 *calculated_crc, u16 *stored_crc) +{ + int write_length = 1; + u8 param[1] = { ebid }; + u8 *ptr; + int rc = 0; + struct pt_hid_output hid_output = { + CREATE_PIP1_FW_CMD(PIP1_CMD_ID_VERIFY_CONFIG_BLOCK_CRC), + .write_length = write_length, + .write_buf = param, + }; + rc = pt_pip1_send_output_and_wait_(cd, &hid_output); + if (rc) + return rc; + + ptr = &cd->response_buf[5]; + *status = ptr[0]; + *calculated_crc = get_unaligned_le16(&ptr[1]); + *stored_crc = get_unaligned_le16(&ptr[3]); + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_pip_verify_config_block_crc + * + * SUMMARY: Protected call to pt_hid_output_verify_config_block_crc_() within + * an exclusive access to the DUT. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer the core data structure + * ebid - enumerated block ID + * *status - PIP command status + * calculated_crc - calculated CRC + * stored_crc - stored CRC in config area + ******************************************************************************/ +static int pt_pip_verify_config_block_crc( + struct pt_core_data *cd, u8 ebid, u8 *status, + u16 *calculated_crc, u16 *stored_crc) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = pt_pip_verify_config_block_crc_(cd, ebid, status, + calculated_crc, stored_crc); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_request_pip_verify_config_block_crc + * + * SUMMARY: Function pointer included in core_nonhid_cmd struct for external + * calls to the protected or unprotected call to + * pt_pip_verify_config_block_crc_ + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * protect - flag to call protected or non-protected + * ebid - enumerated block ID + * *status - PIP command status + * calculated_crc - calculated CRC + * stored_crc - stored CRC in config area + ******************************************************************************/ +static int _pt_request_pip_verify_config_block_crc( + struct device *dev, int protect, u8 ebid, u8 *status, + u16 *calculated_crc, u16 *stored_crc) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return pt_pip_verify_config_block_crc(cd, ebid, + status, calculated_crc, stored_crc); + + return pt_pip_verify_config_block_crc_(cd, ebid, + status, calculated_crc, stored_crc); +} + +/******************************************************************************* + * FUNCTION: pt_pip_get_config_row_size_ + * + * SUMMARY: Sends the PIP "Get Data Row Size" (0x21) command to the DUT + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * protect - flag to call protected or non-protected + * *row_size - pointer to store the retrieved row size + ******************************************************************************/ +static int pt_pip_get_config_row_size_(struct pt_core_data *cd, + u16 *row_size) +{ + int rc = 0; + struct pt_hid_output hid_output = { + CREATE_PIP1_FW_CMD(PIP1_CMD_ID_GET_CONFIG_ROW_SIZE), + }; + + rc = pt_pip1_send_output_and_wait_(cd, &hid_output); + if (rc) + return rc; + + *row_size = get_unaligned_le16(&cd->response_buf[5]); + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_pip_get_config_row_size + * + * SUMMARY: Protected call to pt_hid_output_get_config_row_size_ within + * an exclusive access to the DUT. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * protect - flag to call protected or non-protected + * *row_size - pointer to store the retrieved row size + ******************************************************************************/ +static int pt_pip_get_config_row_size(struct pt_core_data *cd, + u16 *row_size) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = pt_pip_get_config_row_size_(cd, row_size); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_request_pip_get_config_row_size + * + * SUMMARY: Function pointer included in core_nonhid_cmd struct for external + * calls to the protected or unprotected call to + * pt_pip_get_config_row_size_ + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * protect - flag to call protected or non-protected + * *row_size - pointer to store the retrieved row size + ******************************************************************************/ +static int _pt_request_pip_get_config_row_size(struct device *dev, + int protect, u16 *row_size) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return pt_pip_get_config_row_size(cd, row_size); + + return pt_pip_get_config_row_size_(cd, row_size); +} + +/******************************************************************************* + * FUNCTION: pt_pip1_read_data_block_ + * + * SUMMARY: Sends the PIP "Read Data Block" (0x22) command to the DUT and print + * output data to the "read_buf" and update "crc". + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * row_number - row number + * length - length of data to read + * ebid - block id + * *actual_read_len - Actual data length read + * *read_buf - pointer to the buffer to store read data + * read_buf_size - size of read_buf + * *crc - pointer to store CRC of row data + ******************************************************************************/ +static int pt_pip1_read_data_block_(struct pt_core_data *cd, + u16 row_number, u16 length, u8 ebid, u16 *actual_read_len, + u8 *read_buf, u16 read_buf_size, u16 *crc) +{ + int read_ebid; + int status; + int rc = 0; + int write_length = 5; + u8 write_buf[5]; + u8 cmd_offset = 0; + u16 calc_crc; + struct pt_hid_output hid_output = { + CREATE_PIP1_FW_CMD(PIP1_CMD_ID_READ_DATA_BLOCK), + .write_length = write_length, + .write_buf = write_buf, + }; + + write_buf[cmd_offset++] = LOW_BYTE(row_number); + write_buf[cmd_offset++] = HI_BYTE(row_number); + write_buf[cmd_offset++] = LOW_BYTE(length); + write_buf[cmd_offset++] = HI_BYTE(length); + write_buf[cmd_offset++] = ebid; + + rc = pt_pip1_send_output_and_wait_(cd, &hid_output); + if (rc) + return rc; + + status = cd->response_buf[5]; + if (status) + return status; + + read_ebid = cd->response_buf[6]; + if ((read_ebid != ebid) || (cd->response_buf[9] != 0)) + return -EPROTO; + + *actual_read_len = get_unaligned_le16(&cd->response_buf[7]); + if (length == 0 || *actual_read_len == 0) + return 0; + + if (read_buf_size >= *actual_read_len && + *actual_read_len < PT_MAX_PIP2_MSG_SIZE) + memcpy(read_buf, &cd->response_buf[10], *actual_read_len); + else + return -EPROTO; + + *crc = get_unaligned_le16(&cd->response_buf[*actual_read_len + 10]); + + /* Validate Row Data CRC */ + calc_crc = _pt_compute_crc(read_buf, *actual_read_len); + if (*crc == calc_crc) { + return 0; + } else { + pt_debug(cd->dev, DL_ERROR, + "%s: CRC Mismatch packet=0x%04X calc=0x%04X\n", + __func__, *crc, calc_crc); + return -EPROTO; + } +} + +/******************************************************************************* + * FUNCTION: _pt_request_pip_read_data_block + * + * SUMMARY: Function pointer included in core_nonhid_cmd struct for external + * calls to pt_pip1_read_data_block_ + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * row_number - row number + * length - length of data to read + * ebid - block id + * *actual_read_len - Actual data length read + * *read_buf - pointer to the buffer to store read data + * *crc - pointer to store CRC of row data + ******************************************************************************/ +static int _pt_request_pip_read_data_block(struct device *dev, + u16 row_number, u16 length, u8 ebid, u16 *actual_read_len, + u8 *read_buf, u16 read_buf_size, u16 *crc) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + return pt_pip1_read_data_block_(cd, row_number, length, + ebid, actual_read_len, read_buf, read_buf_size, crc); +} + +/******************************************************************************* + * FUNCTION: pt_pip1_write_data_block_ + * + * SUMMARY: Sends the PIP "Write Data Block" (0x23) command to the DUT and + * write data to the data block. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * row_number - row in config block to write to + * write_length - length of data to write + * ebid - enumerated block ID + * *write_buf - pointer to buffer to write + * *security_key - pointer to security key to allow write + * *actual_write_len - pointer to store data length actually written + ******************************************************************************/ +static int pt_pip1_write_data_block_(struct pt_core_data *cd, + u16 row_number, u16 write_length, u8 ebid, u8 *write_buf, + u8 *security_key, u16 *actual_write_len) +{ + /* row_number + write_len + ebid + security_key + crc */ + u16 full_write_length = 2 + 2 + 1 + write_length + 8 + 2; + u8 *full_write_buf; + u8 cmd_offset = 0; + u16 crc; + int status; + int rc = 0; + int read_ebid; + u8 *data; + struct pt_hid_output hid_output = { + CREATE_PIP1_FW_CMD(PIP1_CMD_ID_WRITE_DATA_BLOCK), + .write_length = full_write_length, + .timeout_ms = PT_PIP1_CMD_WRITE_CONF_BLOCK_TIMEOUT, + }; + + if (write_length > PT_CAL_DATA_ROW_SIZE) + return -EINVAL; + + full_write_buf = kzalloc(full_write_length, GFP_KERNEL); + if (!full_write_buf) + return -ENOMEM; + + hid_output.write_buf = full_write_buf; + full_write_buf[cmd_offset++] = LOW_BYTE(row_number); + full_write_buf[cmd_offset++] = HI_BYTE(row_number); + full_write_buf[cmd_offset++] = LOW_BYTE(write_length); + full_write_buf[cmd_offset++] = HI_BYTE(write_length); + full_write_buf[cmd_offset++] = ebid; + data = &full_write_buf[cmd_offset]; + memcpy(data, write_buf, write_length); + cmd_offset += write_length; + memcpy(&full_write_buf[cmd_offset], security_key, 8); + cmd_offset += 8; + crc = _pt_compute_crc(data, write_length); + full_write_buf[cmd_offset++] = LOW_BYTE(crc); + full_write_buf[cmd_offset++] = HI_BYTE(crc); + + rc = pt_pip1_send_output_and_wait_(cd, &hid_output); + if (rc) + goto exit; + + status = cd->response_buf[5]; + if (status) { + rc = -EINVAL; + goto exit; + } + + read_ebid = cd->response_buf[6]; + if (read_ebid != ebid) { + rc = -EPROTO; + goto exit; + } + + *actual_write_len = get_unaligned_le16(&cd->response_buf[7]); + +exit: + kfree(full_write_buf); + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_request_pip_write_data_block + * + * SUMMARY: Function pointer included in core_nonhid_cmd struct for external + * calls to the protected or unprotected call to pt_pip1_write_data_block_ + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * protect - flag to call protected or non-protected + * row_number - row in config block to write to + * write_length - length of data to write + * ebid - enumerated block ID + * *write_buf - pointer to buffer to write + * *security_key - pointer to security key to allow write + * *actual_write_len - pointer to store data length actually written + ******************************************************************************/ +static int _pt_request_pip_write_data_block(struct device *dev, + u16 row_number, u16 write_length, u8 ebid, + u8 *write_buf, u8 *security_key, u16 *actual_write_len) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + return pt_pip1_write_data_block_(cd, row_number, + write_length, ebid, write_buf, security_key, + actual_write_len); +} + +/******************************************************************************* + * FUNCTION: pt_pip_get_data_structure_ + * + * SUMMARY: Sends the PIP "Retrieve Data Structure" (0x24) command to the DUT + * returning a structure of data defined by data_id + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * read_offset - read pointer offset + * read_length - length of data to read + * data_id - data ID to read + * *status - pointer to store the read response status + * *data_format - pointer to store format of data read + * *actual_read_len - pointer to store data length actually read + * *data - pointer to store data read + ******************************************************************************/ +static int pt_pip_get_data_structure_( + struct pt_core_data *cd, u16 read_offset, u16 read_length, + u8 data_id, u8 *status, u8 *data_format, u16 *actual_read_len, + u8 *data) +{ + int rc = 0; + u16 total_read_len = 0; + u16 read_len; + u16 off_buf = 0; + u8 write_buf[5]; + u8 read_data_id; + struct pt_hid_output hid_output = { + CREATE_PIP1_FW_CMD(PIP1_CMD_ID_GET_DATA_STRUCTURE), + .write_length = 5, + .write_buf = write_buf, + }; + +again: + write_buf[0] = LOW_BYTE(read_offset); + write_buf[1] = HI_BYTE(read_offset); + write_buf[2] = LOW_BYTE(read_length); + write_buf[3] = HI_BYTE(read_length); + write_buf[4] = data_id; + + rc = pt_pip1_send_output_and_wait_(cd, &hid_output); + if (rc) + return rc; + + if (cd->response_buf[5] != PT_CMD_STATUS_SUCCESS) + goto set_status; + + read_data_id = cd->response_buf[6]; + if (read_data_id != data_id) + return -EPROTO; + + read_len = get_unaligned_le16(&cd->response_buf[7]); + if (read_len && data) { + memcpy(&data[off_buf], &cd->response_buf[10], read_len); + + total_read_len += read_len; + + if (read_len < read_length) { + read_offset += read_len; + off_buf += read_len; + read_length -= read_len; + goto again; + } + } + + if (data_format) + *data_format = cd->response_buf[9]; + if (actual_read_len) + *actual_read_len = total_read_len; +set_status: + if (status) + *status = cd->response_buf[5]; + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_pip_get_data_structure + * + * SUMMARY: Protected call to pt_hid_output_get_data_structure within + * an exclusive access to the DUT. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * read_offset - read pointer offset + * read_length - length of data to read + * data_id - data ID to read + * *status - pointer to store the read response status + * *data_format - pointer to store format of data read + * *actual_read_len - pointer to store data length actually read + * *data - pointer to store data read + ******************************************************************************/ +static int pt_pip_get_data_structure( + struct pt_core_data *cd, u16 read_offset, u16 read_length, + u8 data_id, u8 *status, u8 *data_format, u16 *actual_read_len, + u8 *data) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = pt_pip_get_data_structure_(cd, read_offset, + read_length, data_id, status, data_format, + actual_read_len, data); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_request_pip_get_data_structure + * + * SUMMARY: Function pointer included in core_nonhid_cmd struct for external + * calls to the protected or unprotected call to + * pt_pip_get_data_structure + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * protect - flag to call protected or non-protected + * read_offset - read pointer offset + * read_length - length of data to read + * data_id - data ID to read + * *status - pointer to store the read response status + * *data_format - pointer to store format of data read + * *actual_read_len - pointer to store data length actually read + * *data - pointer to store data read + ******************************************************************************/ +static int _pt_request_pip_get_data_structure(struct device *dev, + int protect, u16 read_offset, u16 read_length, u8 data_id, + u8 *status, u8 *data_format, u16 *actual_read_len, u8 *data) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return pt_pip_get_data_structure(cd, + read_offset, read_length, data_id, status, + data_format, actual_read_len, data); + + return pt_pip_get_data_structure_(cd, + read_offset, read_length, data_id, status, + data_format, actual_read_len, data); +} + +/******************************************************************************* + * FUNCTION: _pt_manage_local_cal_data + * + * SUMMARY: This function manages storing or restoring a copy of the Firmware + * CALIBRATION data. It stores it in a local static array and can be + * cleared, loaded or used to restore the CAL data back to the running FW. + * The CAL data is read or restored by use of the PIP1 commands: + * - READ_DATA_BLOCK (0x22) + * - WRITE_DATA_BLOCK (0x23) + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * action - One of the following actions: + * - PT_CAL_DATA_SAVE + * - PT_CAL_DATA_RESTORE + * - PT_CAL_DATA_CLEAR + * - PT_CAL_DATA_SIZE + * *size - pointer to the number of bytes transferred + * *crc - pointer to Chip ID CRC that the CAL data was retrieved from + ******************************************************************************/ +static int _pt_manage_local_cal_data(struct device *dev, u8 action, u16 *size, + unsigned short *crc) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_ttdata *ttdata = &cd->sysinfo.ttdata; + unsigned short calc_id_crc = 0; + static u8 *cal_cache_data; + static u16 cal_cache_len; + static unsigned short cal_cache_chip_id; + int rc = 0; + u8 *tmp_data = NULL; + u8 row_number = 0; + u8 prefix[20]; + u16 cal_size = 0; + u16 transfer_size; + u16 act_trans_len = 0; + u16 byte_offset = 0; + u16 cal_blk_size; + u16 total_rows; + u16 remain_bytes; + u16 data_block_crc; + u16 buf_size = 12; + + pt_debug(dev, DL_INFO, "%s: ATM - CAL Cache action=%d\n", + __func__, action); + switch (action) { + case PT_CAL_DATA_SAVE: + /* Read the size of the CAL block and calculate # rows */ + tmp_data = kzalloc(buf_size, GFP_KERNEL); + if (!tmp_data) { + rc = -ENOMEM; + goto exit; + } + /* + * Don't check rc as doing a read size will give a false + * error on the CRC check. + */ + rc = pt_pip1_read_data_block_(cd, row_number, 0, PT_CAL_EBID, + &act_trans_len, tmp_data, buf_size, &data_block_crc); + cal_blk_size = act_trans_len; + kfree(tmp_data); + + pt_debug(dev, DL_INFO, + "%s: CAL Cache size=%d FW CAL Size=%d\n", + __func__, cal_cache_len, cal_blk_size); + + /* Safety net to ensure we didn't read incorrect size */ + if (cal_blk_size > PT_CAL_DATA_MAX_SIZE) { + pt_debug(dev, DL_ERROR, "%s: Alloc struct Failed\n", + __func__); + rc = 1; + goto exit; + } + + /* Panels could have diff CAL sizes, Re-allocate the cache */ + if (cal_blk_size != cal_cache_len) { + kfree(cal_cache_data); + cal_cache_data = kzalloc(cal_blk_size + 2, + GFP_KERNEL); + if (!cal_cache_data) { + rc = -ENOMEM; + goto exit; + } + + pt_debug(dev, DL_INFO, "%s: CAL Cache Allocated\n", + __func__); + } + memset(&cal_cache_data[0], 0, cal_blk_size + 2); + + /* Calculate how many rows [0-n] (PIP Transactions) */ + total_rows = (cal_blk_size / PT_CAL_DATA_ROW_SIZE) - 1; + remain_bytes = cal_blk_size % PT_CAL_DATA_ROW_SIZE; + /* Add row if we have a last partial row */ + if (remain_bytes > 0) + total_rows++; + pt_debug(dev, DL_INFO, + "%s: CAL size=%d rows=[0-%d] partial row bytes=%d\n", + __func__, cal_blk_size, total_rows, remain_bytes); + + /* Read all rows unless an error occurs */ + rc = 0; + while (rc == 0 && row_number <= total_rows) { + act_trans_len = 0; + if (remain_bytes > 0 && row_number == total_rows) + transfer_size = remain_bytes; + else + transfer_size = PT_CAL_DATA_ROW_SIZE; + + rc = pt_pip1_read_data_block_(cd, row_number, + transfer_size, PT_CAL_EBID, + &act_trans_len, + &cal_cache_data[byte_offset], cal_blk_size + 2, + &data_block_crc); + if (rc) { + /* Error occurred, exit loop */ + cal_size = 0; + break; + } + + pt_debug(dev, DL_INFO, + "%s: CAL read rc=%d actual read len=%d\n", + __func__, rc, act_trans_len); + + byte_offset += act_trans_len; + cal_size = byte_offset; + + scnprintf(prefix, sizeof(prefix), "%s[%d]", "CAL DATA ROW", row_number); + pt_pr_buf(dev, DL_INFO, + &cal_cache_data[byte_offset - act_trans_len], + act_trans_len, prefix); + + row_number++; + } + + if (cal_size > 0) { + /* Save a CRC of the chip info the CAL was saved from */ + calc_id_crc = crc_ccitt_calculate( + (u8 *)&ttdata->chip_rev, 4 + PT_UID_SIZE); + cal_cache_chip_id = calc_id_crc; + cal_cache_len = cal_size; + pt_debug(dev, DL_INFO, + "%s: CAL Cache: CRC=0x%04X Total Size=%d\n", + __func__, calc_id_crc, cal_size); + } + + *size = cal_size; + *crc = calc_id_crc; + break; + case PT_CAL_DATA_RESTORE: + cal_size = cal_cache_len; + while ((rc == 0) && (byte_offset < cal_size)) { + if (cal_size - byte_offset > PT_CAL_DATA_ROW_SIZE) + transfer_size = PT_CAL_DATA_ROW_SIZE; + else + transfer_size = cal_size - byte_offset; + + rc = pt_pip1_write_data_block_(cd, row_number, + transfer_size, PT_CAL_EBID, + &cal_cache_data[byte_offset], + (u8 *)pt_data_block_security_key, + &act_trans_len); + + byte_offset += act_trans_len; + pt_debug(dev, DL_INFO, "%s: CAL write byte offset=%d\n", + __func__, byte_offset); + + scnprintf(prefix, sizeof(prefix), "%s[%d]", "CAL DATA ROW", row_number); + pt_pr_buf(dev, DL_INFO, + &cal_cache_data[byte_offset - act_trans_len], + act_trans_len, prefix); + + + if ((byte_offset > cal_size) || + (act_trans_len != transfer_size)) + rc = -EIO; + row_number++; + } + *size = byte_offset; + *crc = cal_cache_chip_id; + break; + case PT_CAL_DATA_CLEAR: + if (cal_cache_data) + memset(&cal_cache_data[0], 0, cal_cache_len); + cal_cache_len = 0; + cal_cache_chip_id = 0; + *size = 0; + *crc = 0; + break; + case PT_CAL_DATA_INFO: + default: + *size = cal_cache_len; + *crc = cal_cache_chip_id; + pt_debug(dev, DL_INFO, + "%s: CAL Cache: CRC=%04X Total Size=%d\n", + __func__, cal_cache_chip_id, + cal_cache_len); + break; + } + +exit: + pt_debug(dev, DL_INFO, + "%s: CAL Cache exit: rc=%d CRC=0x%04X Total Size=%d\n", + __func__, rc, *crc, *size); + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_pip_run_selftest_ + * + * SUMMARY: Sends the PIP "Run Self Test" (0x26) command to the DUT + * to execute a FW built in self test + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * test_id - enumerated test ID to run + * write_idacs_to_flash - flag whether to write new IDACS to flash + * *status - pointer to store the read response status + * *summary_results - pointer to store the results summary + * *results_available - pointer to store if results are available + *****************************************************************************/ +static int pt_pip_run_selftest_( + struct pt_core_data *cd, u8 test_id, + u8 write_idacs_to_flash, u8 *status, u8 *summary_result, + u8 *results_available) +{ + int rc = 0; + u8 write_buf[2]; + struct pt_hid_output hid_output = { + CREATE_PIP1_FW_CMD(PIP1_CMD_ID_RUN_SELF_TEST), + .write_length = 2, + .write_buf = write_buf, + .timeout_ms = PT_PIP1_CMD_RUN_SELF_TEST_TIMEOUT, + }; + + write_buf[0] = test_id; + write_buf[1] = write_idacs_to_flash; + + if (cd->active_dut_generation == DUT_PIP2_CAPABLE) + hid_output.write_length = 1; + + rc = pt_pip1_send_output_and_wait_(cd, &hid_output); + if (rc) + return rc; + + if (status) + *status = cd->response_buf[5]; + if (summary_result) + *summary_result = cd->response_buf[6]; + /* results_available only available before PIP 1.03 */ + if (cd->sysinfo.ready && !IS_PIP_VER_GE(&cd->sysinfo, 1, 3)) { + if (results_available) + *results_available = cd->response_buf[7]; + } + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_pip_run_selftest + * + * SUMMARY: Protected call to pt_hid_output_run_selftest within + * an exclusive access to the DUT. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * test_id - enumerated test ID to run + * write_idacs_to_flash - flag whether to write new IDACS to flash + * *status - pointer to store the read response status + * *summary_results - pointer to store the results summary + * *results_available - pointer to store if results are available + ******************************************************************************/ +static int pt_pip_run_selftest( + struct pt_core_data *cd, u8 test_id, + u8 write_idacs_to_flash, u8 *status, u8 *summary_result, + u8 *results_available) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = pt_pip_run_selftest_(cd, test_id, + write_idacs_to_flash, status, summary_result, + results_available); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_request_pip_run_selftest + * + * SUMMARY: Function pointer included in core_nonhid_cmd struct for external + * calls to the protected or unprotected call to pt_pip_run_selftest + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * protect - flag to call protected or non-protected + * test_id - enumerated test ID to run + * write_idacs_to_flash - flag whether to write new IDACS to flash + * *status - pointer to store the read response status + * *summary_results - pointer to store the results summary + * *results_available - pointer to store if results are available + ******************************************************************************/ +static int _pt_request_pip_run_selftest(struct device *dev, + int protect, u8 test_id, u8 write_idacs_to_flash, u8 *status, + u8 *summary_result, u8 *results_available) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return pt_pip_run_selftest(cd, test_id, + write_idacs_to_flash, status, summary_result, + results_available); + + return pt_pip_run_selftest_(cd, test_id, + write_idacs_to_flash, status, summary_result, + results_available); +} + +/******************************************************************************* + * FUNCTION: _pt_pip_get_selftest_result_ + * + * SUMMARY: Sends the PIP "Get Self Test Results" (0x27) command to the DUT + * to retrieve the self test results from the self test already executed + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * read_offset - read pointer offset + * read_length - length of data to read + * test_id - enumerated test ID to read selftest results from + * *status - pointer to store the read response status + * *actual_read_len - pointer to store data length actually read + * *status - pointer to where the cmd response statas is stored + ******************************************************************************/ +static int pt_pip_get_selftest_result_( + struct pt_core_data *cd, u16 read_offset, u16 read_length, + u8 test_id, u8 *status, u16 *actual_read_len, u8 *data) +{ + int rc = 0; + u16 total_read_len = 0; + u16 read_len; + u16 off_buf = 0; + u8 write_buf[5]; + u8 read_test_id; + bool repeat; + struct pt_hid_output hid_output = { + CREATE_PIP1_FW_CMD(PIP1_CMD_ID_GET_SELF_TEST_RESULT), + .write_length = 5, + .write_buf = write_buf, + }; + + /* + * Do not repeat reading for Auto Shorts test + * when PIP version < 1.3 + */ + repeat = IS_PIP_VER_GE(&cd->sysinfo, 1, 3) + || test_id != PT_ST_ID_AUTOSHORTS; + +again: + write_buf[0] = LOW_BYTE(read_offset); + write_buf[1] = HI_BYTE(read_offset); + write_buf[2] = LOW_BYTE(read_length); + write_buf[3] = HI_BYTE(read_length); + write_buf[4] = test_id; + + rc = pt_pip1_send_output_and_wait_(cd, &hid_output); + if (rc) + return rc; + + if (cd->response_buf[5] != PT_CMD_STATUS_SUCCESS) + goto set_status; + + read_test_id = cd->response_buf[6]; + if (read_test_id != test_id) + return -EPROTO; + + read_len = get_unaligned_le16(&cd->response_buf[7]); + if (read_len && data) { + memcpy(&data[off_buf], &cd->response_buf[10], read_len); + + total_read_len += read_len; + + if (repeat && read_len < read_length) { + read_offset += read_len; + off_buf += read_len; + read_length -= read_len; + goto again; + } + } + + if (actual_read_len) + *actual_read_len = total_read_len; +set_status: + if (status) + *status = cd->response_buf[5]; + + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_pip_get_selftest_result + * + * SUMMARY: Protected call to pt_hid_output_get_selftest_result by exclusive + * access to the DUT + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * read_offset - read pointer offset + * read_length - length of data to read + * test_id - enumerated test ID to read selftest results from + * *status - pointer to store the read response status + * *actual_read_len - pointer to store data length actually read + * *status - pointer to where the cmd response statas is stored + ******************************************************************************/ +static int pt_pip_get_selftest_result( + struct pt_core_data *cd, u16 read_offset, u16 read_length, + u8 test_id, u8 *status, u16 *actual_read_len, u8 *data) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = pt_pip_get_selftest_result_(cd, read_offset, + read_length, test_id, status, actual_read_len, data); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_request_pip_get_selftest_result + * + * SUMMARY: Function pointer included in core_nonhid_cmd struct for external + * calls to the protected or unprotected call to pt_pip_get_selftest_result + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * protect - flag to call protected or non-protected + * read_offset - read pointer offset + * read_length - length of data to read + * test_id - enumerated test ID to read selftest results from + * *status - pointer to store the read response status + * *actual_read_len - pointer to store data length actually read + * *data - pointer to where the data read is stored + ******************************************************************************/ +static int _pt_request_pip_get_selftest_result(struct device *dev, + int protect, u16 read_offset, u16 read_length, u8 test_id, + u8 *status, u16 *actual_read_len, u8 *data) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return pt_pip_get_selftest_result(cd, read_offset, + read_length, test_id, status, actual_read_len, + data); + + return pt_pip_get_selftest_result_(cd, read_offset, + read_length, test_id, status, actual_read_len, + data); +} + +/******************************************************************************* + * FUNCTION: _pt_pip_load_self_test_param + * + * SUMMARY: Sends the PIP "Load Self Test Parameters" (0x25) command to the DUT + * to load parameters needed by a self test + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * self_test_id - enumerated test ID for which the parmeters belong + * load_offset - mem offset to where to load parameters + * load_length - length of parameter data to load + * *parameters - pointer to list of parameter data + * *status - pointer to store the response status + * *ret_test_id - pointer to returned test id the parameters were stored + * *act_load_len - pointer to store the actual load length that was writen + ******************************************************************************/ +static int pt_pip_load_self_test_param_(struct pt_core_data *cd, + u8 self_test_id, u16 load_offset, u16 load_length, + u8 *parameters, u8 *status, u8 *ret_test_id, u16 *act_load_len) +{ + int rc = 0; + int i; + u8 write_buf[PT_MAX_PIP1_MSG_SIZE]; + struct pt_hid_output hid_output = { + CREATE_PIP1_FW_CMD(PIP1_CMD_ID_LOAD_SELF_TEST_PARAM), + .write_length = 5 + load_length, + .write_buf = write_buf, + .timeout_ms = PT_PIP1_CMD_DEFAULT_TIMEOUT, + }; + + write_buf[0] = LOW_BYTE(load_offset); + write_buf[1] = HI_BYTE(load_offset); + write_buf[2] = LOW_BYTE(load_length); + write_buf[3] = HI_BYTE(load_length); + write_buf[4] = self_test_id; + for (i = 0; i < load_length; i++) + write_buf[i + 5] = parameters[i]; + + rc = pt_pip1_send_output_and_wait_(cd, &hid_output); + if (rc) + return rc; + + if (status) + *status = cd->response_buf[5]; + if (ret_test_id) + *ret_test_id = cd->response_buf[6]; + if (act_load_len) + *act_load_len = get_unaligned_le16(&cd->response_buf[7]); + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_pip_load_self_test_param + * + * SUMMARY: Protected call to pt_pip_load_self_test_param_ within an exclusive + * access to the DUT. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * self_test_id - enumerated test ID for which the parmeters belong + * load_offset - mem offset to where to load parameters + * load_length - length of parameter data to load + * *parameters - pointer to list of parameter data + * *status - pointer to store the response status + * *ret_test_id - pointer to returned test id the parameters were stored + * *act_load_len - pointer to store the actual load length that was writen + ******************************************************************************/ +static int pt_pip_load_self_test_param(struct pt_core_data *cd, + u8 self_test_id, u16 load_offset, u16 load_length, + u8 *parameters, u8 *status, u8 *ret_test_id, u16 *act_load_len) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = pt_pip_load_self_test_param_(cd, self_test_id, load_offset, + load_length, parameters, status, ret_test_id, act_load_len); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_request_pip_load_self_test_param + * + * SUMMARY: Function pointer included in core_nonhid_cmd struct for external + * calls to the protected or unprotected call to + * pt_pip_load_self_test_param + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * protect - flag to call protected or non-protected + * self_test_id - enumerated test ID for which the parmeters belong + * load_offset - mem offset to where to load parameters + * load_length - length of parameter data to load + * *parameters - pointer to list of parameter data + * *status - pointer to store the response status + * *ret_test_id - pointer to returned test id the parameters were stored + * *act_load_len - pointer to store the actual load length that was writen + ******************************************************************************/ +static int _pt_request_pip_load_self_test_param(struct device *dev, + int protect, u8 self_test_id, u16 load_offset, u16 load_length, + u8 *parameters, u8 *status, u8 *ret_test_id, u16 *act_load_len) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return pt_pip_load_self_test_param(cd, self_test_id, + load_offset, load_length, parameters, status, ret_test_id, + act_load_len); + + return pt_pip_load_self_test_param_(cd, self_test_id, load_offset, + load_length, parameters, status, ret_test_id, act_load_len); +} + +/******************************************************************************* + * FUNCTION: pt_pip_calibrate_ext_ + * + * SUMMARY: Send the PIP1 Extended Calibrate command (0x30) to the DUT waiting + * for the response + * + * NOTE: This calibrate command requires the DUT to support PIP version >= 1.10 + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * *cal_data - pointer to extended calibration data structure + * *status - pointer to where the command response status is stored + ******************************************************************************/ +static int pt_pip_calibrate_ext_(struct pt_core_data *cd, + struct pt_cal_ext_data *cal_data, u8 *status) +{ + int rc = 0; + int write_length = 4; + u8 write_buf[4]; + u16 size = 0; + unsigned short crc = 0; + + struct pt_hid_output hid_output = { + CREATE_PIP1_FW_CMD(PIP1_CMD_ID_CALIBRATE_DEVICE_EXTENDED), + .write_length = write_length, + .write_buf = write_buf, + .timeout_ms = PT_PIP1_CMD_CALIBRATE_EXT_TIMEOUT, + }; + + if (cal_data == NULL) + return -EINVAL; + + memcpy(write_buf, cal_data, sizeof(struct pt_cal_ext_data)); + + rc = pt_pip1_send_output_and_wait_(cd, &hid_output); + if (rc) + return rc; + + if (status) + *status = cd->response_buf[5]; + + /* + * When doing a calibration on a flashless DUT, save CAL data in + * the TTDL cache on any successful calibration + */ + if (cd->response_buf[5] == 0 && cd->cal_cache_in_host) { + pt_debug(cd->dev, DL_INFO, "%s: Retrieve and Save CAL\n", + __func__); + rc = _pt_manage_local_cal_data(cd->dev, PT_CAL_DATA_SAVE, + &size, &crc); + if (rc) + pt_debug(cd->dev, DL_ERROR, + "%s: Error Saving CAL rc=%d\n", __func__, rc); + else + pt_debug(cd->dev, DL_INFO, + "%s: Saved CAL: chip ID=0x%04X size=%d\n", + __func__, crc, size); + } + + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_pip_calibrate_ext + * + * SUMMARY: Protected call to pt_pip_calibrate_ext_ by exclusive access to the + * DUT + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * *cal_data - pointer to extended calibration data structure + * *status - pointer to where the command response status is stored + ******************************************************************************/ +static int pt_pip_calibrate_ext(struct pt_core_data *cd, + struct pt_cal_ext_data *cal_data, u8 *status) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = pt_pip_calibrate_ext_(cd, cal_data, status); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_request_pip_calibrate_ext + * + * SUMMARY: Function pointer included in core_nonhid_cmd struct for external + * calls to the protected or unprotected call to pt_pip_calibrate_ext + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * protect - flag to call protected or non-protected + * *cal_data - pointer to extended calibration data structure + * *status - pointer to where the command response status is stored + ******************************************************************************/ +static int _pt_request_pip_calibrate_ext(struct device *dev, + int protect, struct pt_cal_ext_data *cal_data, u8 *status) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return pt_pip_calibrate_ext(cd, cal_data, status); + + return pt_pip_calibrate_ext_(cd, cal_data, status); +} + +/******************************************************************************* + * FUNCTION: pt_pip_calibrate_idacs_ + * + * SUMMARY: Send the PIP Calibrate IDACs command (0x28) to the DUT waiting + * for the response + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * mode - sense mode to calibrate (0-5) + * *status - pointer to where the command response status is stored + ******************************************************************************/ +static int pt_pip_calibrate_idacs_(struct pt_core_data *cd, + u8 mode, u8 *status) +{ + int rc = 0; + int write_length = 1; + u8 write_buf[1]; + u8 cmd_offset = 0; + struct pt_hid_output hid_output = { + CREATE_PIP1_FW_CMD(PIP1_CMD_ID_CALIBRATE_IDACS), + .write_length = write_length, + .write_buf = write_buf, + .timeout_ms = PT_PIP1_CMD_CALIBRATE_IDAC_TIMEOUT, + }; + write_buf[cmd_offset++] = mode; + rc = pt_pip1_send_output_and_wait_(cd, &hid_output); + if (rc) + return rc; + + *status = cd->response_buf[5]; + if (*status) + return -EINVAL; + + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_pip_calibrate_idacs + * + * SUMMARY: Protected call to pt_hid_output_calibrate_idacs_ by exclusive + * access to the DUT + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * mode - sense mode to calibrate (0-5) + * *status - pointer to where the command response status is stored + ******************************************************************************/ +static int pt_pip_calibrate_idacs(struct pt_core_data *cd, + u8 mode, u8 *status) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = pt_pip_calibrate_idacs_(cd, mode, status); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_request_pip_calibrate_idacs + * + * SUMMARY: Function pointer included in core_nonhid_cmd struct for external + * calls to the protected or unprotected call to pt_pip_calibrate_idacs + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * protect - flag to call protected or non-protected + * mode - sense mode to calibrate (0-5) + * *status - pointer to where the command response status is stored + ******************************************************************************/ +static int _pt_request_pip_calibrate_idacs(struct device *dev, + int protect, u8 mode, u8 *status) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return pt_pip_calibrate_idacs(cd, mode, status); + + return pt_pip_calibrate_idacs_(cd, mode, status); +} + +/******************************************************************************* + * FUNCTION: pt_hid_output_initialize_baselines_ + * + * SUMMARY: Send the PIP "Initialize Baselines" command (0x29) to the DUT + * waiting for the response. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * test_id - bit type flag to allow initialize baseline MUT,BTN,SELG + * each or together with a single command. + * *status - pointer to where the command response status is stored + ******************************************************************************/ +static int pt_hid_output_initialize_baselines_( + struct pt_core_data *cd, u8 test_id, u8 *status) +{ + int rc = 0; + int write_length = 1; + u8 write_buf[1]; + u8 cmd_offset = 0; + struct pt_hid_output hid_output = { + CREATE_PIP1_FW_CMD(PIP1_CMD_ID_INITIALIZE_BASELINES), + .write_length = write_length, + .write_buf = write_buf, + }; + write_buf[cmd_offset++] = test_id; + + rc = pt_pip1_send_output_and_wait_(cd, &hid_output); + if (rc) + return rc; + + *status = cd->response_buf[5]; + if (*status) + return -EINVAL; + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_hid_output_initialize_baselines + * + * SUMMARY: Protected call to pt_hid_output_initialize_baselines_ by exclusive + * access to the DUT + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * test_id - enumerated ID against which to initialize the baseline + * *status - pointer to where the command response status is stored + ******************************************************************************/ +static int pt_hid_output_initialize_baselines(struct pt_core_data *cd, + u8 test_id, u8 *status) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = pt_hid_output_initialize_baselines_(cd, test_id, status); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_request_pip_initialize_baselines + * + * SUMMARY: Function pointer included in core_nonhid_cmd struct for external + * calls to the protected or unprotected call to + * pt_pip_initialize_baselines + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * protect - flag to call protected or non-protected + * test_id - enumerated ID against which to initialize the baseline + * *status - pointer to where the command response status is stored + ******************************************************************************/ +static int _pt_request_pip_initialize_baselines(struct device *dev, + int protect, u8 test_id, u8 *status) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return pt_hid_output_initialize_baselines(cd, test_id, + status); + + return pt_hid_output_initialize_baselines_(cd, test_id, status); +} + +/******************************************************************************* + * FUNCTION: pt_hid_output_exec_panel_scan_ + * + * SUMMARY: Sends the PIP "Execute Panel Scan" (0x2A) to the DUT and waits for + * the response + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static int pt_hid_output_exec_panel_scan_(struct pt_core_data *cd) +{ + struct pt_hid_output hid_output = { + CREATE_PIP1_FW_CMD(PIP1_CMD_ID_EXEC_PANEL_SCAN), + }; + + return pt_pip1_send_output_and_wait_(cd, &hid_output); +} + +/******************************************************************************* + * FUNCTION: pt_hid_output_exec_panel_scan + * + * SUMMARY: Protected call to pt_hid_output_exec_panel_scan_ by exclusive + * access to the DUT. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static int pt_hid_output_exec_panel_scan(struct pt_core_data *cd) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = pt_hid_output_exec_panel_scan_(cd); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_pip2_exec_panel_scan_ + * + * SUMMARY: Send the PIP2 "Execute Panel Scan" (0x21) to the DUT and waits for + * the response + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * scan_type - type of panel scan to perform (PIP2 only) + ******************************************************************************/ +static int pt_pip2_exec_panel_scan_(struct pt_core_data *cd, u8 scan_type) +{ + int rc = 0; + u8 data[2]; + u8 read_buf[10]; + u16 actual_read_len; + + pt_debug(cd->dev, DL_DEBUG, "%s: PIP2 Execute Scan %d\n", + __func__, scan_type); + data[0] = scan_type; + rc = _pt_request_pip2_send_cmd(cd->dev, + PT_CORE_CMD_UNPROTECTED, PIP2_CMD_EXECUTE_SCAN, + data, 1, read_buf, &actual_read_len); + if (rc) { + pt_debug(cd->dev, DL_ERROR, + "%s EXECUTE_SCAN command for type %d failed. rc=%d\n", + __func__, scan_type, rc); + } + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_pip2_exec_panel_scan + * + * SUMMARY: Protected call to pt_pip2_exec_panel_scan_ by exclusive + * access to the DUT. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * scan_type - type of panel scan to perform (PIP2 only) + ******************************************************************************/ +static int pt_pip2_exec_panel_scan(struct pt_core_data *cd, u8 scan_type) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = pt_pip2_exec_panel_scan_(cd, scan_type); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_request_pip_exec_panel_scan + * + * SUMMARY: Function pointer included in core_nonhid_cmd struct for external + * calls to the protected or unprotected call to + * pt_pip2_exec_panel_scan or pt_hid_output_exec_panel_scan + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * protect - flag to call protected or non-protected + * scan_type - type of panel scan to perform (PIP2 only) + ******************************************************************************/ +static int _pt_request_pip_exec_panel_scan(struct device *dev, + int protect, u8 scan_type) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + if (cd->sysinfo.ready && IS_PIP_VER_GE(&cd->sysinfo, 1, 12)) { + if (protect) + return pt_pip2_exec_panel_scan(cd, scan_type); + + return pt_pip2_exec_panel_scan_(cd, scan_type); + } + + if (protect) + return pt_hid_output_exec_panel_scan(cd); + + return pt_hid_output_exec_panel_scan_(cd); +} + +/******************************************************************************* + * FUNCTION: pt_hid_output_retrieve_panel_scan_ + * + * SUMMARY: Sends the PIP "Retrieve Panel Scan" (0x2B) command to the DUT + * to retrieve the specified data type for a the last successful Execute + * Panel Scan command. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * protect - flag to call protected or non-protected + * read_offset - read pointer offset + * read_count - length of data to read + * data_id - enumerated test ID to read selftest results from + * *response - pointer to store the read response status + * *config - pointer to store config data + * *actual_read_len - pointer to store data length actually read + * *read_buf - pointer to the read buffer + ******************************************************************************/ +static int pt_hid_output_retrieve_panel_scan_( + struct pt_core_data *cd, u16 read_offset, u16 read_count, + u8 data_id, u8 *response, u8 *config, u16 *actual_read_len, + u8 *read_buf) +{ + int status; + u8 read_data_id; + int rc = 0; + int write_length = 5; + u8 write_buf[5]; + u8 cmd_offset = 0; + u8 data_elem_size; + int size; + int data_size; + struct pt_hid_output hid_output = { + CREATE_PIP1_FW_CMD(PIP1_CMD_ID_RETRIEVE_PANEL_SCAN), + .write_length = write_length, + .write_buf = write_buf, + }; + + write_buf[cmd_offset++] = LOW_BYTE(read_offset); + write_buf[cmd_offset++] = HI_BYTE(read_offset); + write_buf[cmd_offset++] = LOW_BYTE(read_count); + write_buf[cmd_offset++] = HI_BYTE(read_count); + write_buf[cmd_offset++] = data_id; + + rc = pt_pip1_send_output_and_wait_(cd, &hid_output); + if (rc) + return rc; + + status = cd->response_buf[5]; + if (status) + return -EINVAL; + + read_data_id = cd->response_buf[6]; + if (read_data_id != data_id) + return -EPROTO; + + size = get_unaligned_le16(&cd->response_buf[0]); + *actual_read_len = get_unaligned_le16(&cd->response_buf[7]); + *config = cd->response_buf[9]; + + data_elem_size = *config & 0x07; + data_size = *actual_read_len * data_elem_size; + + if (read_buf) + memcpy(read_buf, &cd->response_buf[10], data_size); + if (response) + memcpy(response, cd->response_buf, size); + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_hid_output_retrieve_panel_scan + * + * SUMMARY: Protected call to pt_hid_output_retrieve_panel_scan_ by exclusive + * access to the DUT. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * protect - flag to call protected or non-protected + * read_offset - read pointer offset + * read_count - length of data to read + * data_id - enumerated test ID to read selftest results from + * *response - pointer to store the read response status + * *config - pointer to store config data + * *actual_read_len - pointer to store data length actually read + * *read_buf - pointer to the read buffer + ******************************************************************************/ +static int pt_hid_output_retrieve_panel_scan( + struct pt_core_data *cd, u16 read_offset, u16 read_count, + u8 data_id, u8 *response, u8 *config, u16 *actual_read_len, + u8 *read_buf) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = pt_hid_output_retrieve_panel_scan_(cd, read_offset, + read_count, data_id, response, config, + actual_read_len, read_buf); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_request_pip_retrieve_panel_scan + * + * SUMMARY: Function pointer included in core_nonhid_cmd struct for external + * calls to the protected or unprotected call to + * pt_hid_output_retrieve_panel_scan + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * protect - flag to call protected or non-protected + * read_offset - read pointer offset + * read_count - length of data to read + * data_id - enumerated test ID to read selftest results from + * *response - pointer to store the read response status + * *config - pointer to store config data + * *actual_read_len - pointer to store data length actually read + * *read_buf - pointer to the read buffer + ******************************************************************************/ +static int _pt_request_pip_retrieve_panel_scan(struct device *dev, + int protect, u16 read_offset, u16 read_count, u8 data_id, + u8 *response, u8 *config, u16 *actual_read_len, u8 *read_buf) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return pt_hid_output_retrieve_panel_scan(cd, + read_offset, read_count, data_id, response, + config, actual_read_len, read_buf); + + return pt_hid_output_retrieve_panel_scan_(cd, + read_offset, read_count, data_id, response, + config, actual_read_len, read_buf); +} + +/******************************************************************************* + * FUNCTION: _pt_request_pip_user_cmd + * + * SUMMARY: Function pointer included in core_nonhid_cmd struct for external + * calls to the protected or unprotected call to + * pt_hid_output_user_cmd + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * protect - flag to call protected or non-protected + * read_len - length of data to read + * *read_buf - pointer to store read data + * write_len - length of data to write + * *write_buf - pointer to buffer to write + * *actual_read_len - pointer to store data length actually read + ******************************************************************************/ +static int _pt_request_pip_user_cmd(struct device *dev, + int protect, u16 read_len, u8 *read_buf, u16 write_len, + u8 *write_buf, u16 *actual_read_len) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return pt_hid_output_user_cmd(cd, read_len, read_buf, + write_len, write_buf, actual_read_len); + + return pt_hid_output_user_cmd_(cd, read_len, read_buf, + write_len, write_buf, actual_read_len); +} + +/******************************************************************************* + * FUNCTION: pt_hid_output_bl_get_information_ + * + * SUMMARY: Sends the PIP "Get Bootloader Information" (0x38) command to the + * DUT to retrieve bootloader version and chip identification information. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * *return_data - pointer to store the return data + *****************************************************************************/ +static int pt_hid_output_bl_get_information_(struct pt_core_data *cd, + u8 *return_data) +{ + int rc; + int data_len; + struct pt_hid_output hid_output = { + CREATE_PIP1_BL_CMD(PIP1_BL_CMD_ID_GET_INFO), + }; + + rc = pt_pip1_send_output_and_wait_(cd, &hid_output); + if (rc) + return rc; + + data_len = get_unaligned_le16(&cd->input_buf[6]); + if (!data_len) + return -EPROTO; + + memcpy(return_data, &cd->response_buf[8], data_len); + + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_hid_output_bl_get_information + * + * SUMMARY: Protected call to pt_hid_output_bl_get_information_ by exclusive + * access to the DUT. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * *return_data - pointer to store the return data + ******************************************************************************/ +static int pt_hid_output_bl_get_information(struct pt_core_data *cd, + u8 *return_data) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = pt_hid_output_bl_get_information_(cd, return_data); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_request_pip_bl_get_information + * + * SUMMARY: Function pointer included in core_nonhid_cmd struct for external + * calls to the protected or unprotected call to + * pt_hid_output_bl_get_information + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * protect - flag to call protected or non-protected + * *return_data - pointer to store bl data + ******************************************************************************/ +static int _pt_request_pip_bl_get_information(struct device *dev, + int protect, u8 *return_data) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return pt_hid_output_bl_get_information(cd, return_data); + + return pt_hid_output_bl_get_information_(cd, return_data); +} + +/******************************************************************************* + * FUNCTION: pt_hid_output_bl_initiate_bl_ + * + * SUMMARY: Sends the PIP "Get Bootloader Information" (0x48) command to the + * DUT to erases the entire TrueTouch application, Configuration Data block, + * and Design Data block in flash and enables the host to execute the Program + * and Verify Row command to bootload the application image and data. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * protect - flag to call protected or non-protected + * key_size - size of key + * *key_buf - pointer to key data to allow operation + * row_size - size of the meta data row + * *metadata_row_buf - pointer to meta data to write + ******************************************************************************/ +static int pt_hid_output_bl_initiate_bl_(struct pt_core_data *cd, + u16 key_size, u8 *key_buf, u16 row_size, u8 *metadata_row_buf) +{ + u16 write_length = key_size + row_size; + u8 *write_buf; + int rc = 0; + struct pt_hid_output hid_output = { + CREATE_PIP1_BL_CMD(PIP1_BL_CMD_ID_INITIATE_BL), + .write_length = write_length, + .timeout_ms = PT_PIP1_CMD_INITIATE_BL_TIMEOUT, + }; + + write_buf = kzalloc(write_length, GFP_KERNEL); + if (!write_buf) + return -ENOMEM; + + hid_output.write_buf = write_buf; + + if (key_size) + memcpy(write_buf, key_buf, key_size); + + if (row_size) + memcpy(&write_buf[key_size], metadata_row_buf, row_size); + + rc = pt_pip1_send_output_and_wait_(cd, &hid_output); + + kfree(write_buf); + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_hid_output_bl_initiate_bl + * + * SUMMARY: Protected call to pt_hid_output_bl_initiate_bl_ by exclusive + * access to the DUT. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * key_size - size of key + * *key_buf - pointer to key data to allow operation + * row_size - size of the meta data row + * *metadata_row_buf - pointer to meta data to write + ******************************************************************************/ +static int pt_hid_output_bl_initiate_bl(struct pt_core_data *cd, + u16 key_size, u8 *key_buf, u16 row_size, u8 *metadata_row_buf) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = pt_hid_output_bl_initiate_bl_(cd, key_size, key_buf, + row_size, metadata_row_buf); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_request_pip_bl_initiate_bl + * + * SUMMARY: Function pointer included in core_nonhid_cmd struct for external + * calls to the protected or unprotected call to + * pt_hid_output_bl_initiate_bl + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * protect - flag to call protected or non-protected + * key_size - size of key + * *key_buf - pointer to key data to allow operation + * row_size - size of the meta data row + * *metadata_row_buf - pointer to meta data to write + ******************************************************************************/ +static int _pt_request_pip_bl_initiate_bl(struct device *dev, + int protect, u16 key_size, u8 *key_buf, u16 row_size, + u8 *metadata_row_buf) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return pt_hid_output_bl_initiate_bl(cd, key_size, key_buf, + row_size, metadata_row_buf); + + return pt_hid_output_bl_initiate_bl_(cd, key_size, key_buf, + row_size, metadata_row_buf); +} + +/******************************************************************************* + * FUNCTION: pt_hid_output_bl_program_and_verify_ + * + * SUMMARY: Sends the PIP "Get Bootloader Information" (0x39) command to upload + * and program a 128-byte row into the flash, and then verifies written data. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * data_len - length of data_buf + * *data_buf - firmware image to program + ******************************************************************************/ +static int pt_hid_output_bl_program_and_verify_( + struct pt_core_data *cd, u16 data_len, u8 *data_buf) +{ + struct pt_hid_output hid_output = { + CREATE_PIP1_BL_CMD(PIP1_BL_CMD_ID_PROGRAM_AND_VERIFY), + .write_length = data_len, + .write_buf = data_buf, + .timeout_ms = PT_PIP1_CMD_PROGRAM_AND_VERIFY_TIMEOUT, + }; + + return pt_pip1_send_output_and_wait_(cd, &hid_output); +} + +/******************************************************************************* + * FUNCTION: pt_hid_output_bl_program_and_verify + * + * SUMMARY: Protected call to pt_hid_output_bl_program_and_verify_ by exclusive + * access to the DUT. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * data_len - length of data_buf + * *data_buf - firmware image to program + ******************************************************************************/ +static int pt_hid_output_bl_program_and_verify( + struct pt_core_data *cd, u16 data_len, u8 *data_buf) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = pt_hid_output_bl_program_and_verify_(cd, data_len, data_buf); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_request_pip_bl_program_and_verify + * + * SUMMARY: Function pointer included in core_nonhid_cmds to allow other modules + * to request to have the BL program and verify a FW image + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * protect - boolean to determine to call the protected function + * data_len - length of data_buf + * *data_buf - firmware image to program + ******************************************************************************/ +static int _pt_request_pip_bl_program_and_verify( + struct device *dev, int protect, u16 data_len, u8 *data_buf) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return pt_hid_output_bl_program_and_verify(cd, data_len, + data_buf); + + return pt_hid_output_bl_program_and_verify_(cd, data_len, + data_buf); +} + +/******************************************************************************* + * FUNCTION: pt_hid_output_bl_verify_app_integrity_ + * + * SUMMARY: Sends the PIP "Get Bootloader Information" (0x31) command to + * perform a full verification of the application integrity by calculating the + * CRC of the image in flash and compare it to the expected CRC stored in the + * Metadata row. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * *result - pointer to store result + ******************************************************************************/ +static int pt_hid_output_bl_verify_app_integrity_( + struct pt_core_data *cd, u8 *result) +{ + int rc; + struct pt_hid_output hid_output = { + CREATE_PIP1_BL_CMD(PIP1_BL_CMD_ID_VERIFY_APP_INTEGRITY), + }; + + rc = pt_pip1_send_output_and_wait_(cd, &hid_output); + if (rc) { + *result = 0; + return rc; + } + + *result = cd->response_buf[8]; + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_hid_output_bl_verify_app_integrity + * + * SUMMARY: Protected call to pt_hid_output_bl_verify_app_integrity_ by + * exclusive access to the DUT. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * *result - pointer to store result + ******************************************************************************/ +static int pt_hid_output_bl_verify_app_integrity( + struct pt_core_data *cd, u8 *result) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = pt_hid_output_bl_verify_app_integrity_(cd, result); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_request_pip_bl_verify_app_integrity + * + * SUMMARY: Function pointer included in core_nonhid_cmds to allow other modules + * to request to have the BL verify the application integrity (PIP1.x only) + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * protect - boolean to determine to call the protected function + * *result - pointer to store result + ******************************************************************************/ +static int _pt_request_pip_bl_verify_app_integrity( + struct device *dev, int protect, u8 *result) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return pt_hid_output_bl_verify_app_integrity(cd, result); + + return pt_hid_output_bl_verify_app_integrity_(cd, result); +} + +/******************************************************************************* + * FUNCTION: pt_hid_output_bl_launch_app_ + * + * SUMMARY: Sends the PIP "Launch Application" (0x3B) command to launch the + * application from bootloader (PIP1.x only). + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static int pt_hid_output_bl_launch_app_(struct pt_core_data *cd) +{ + struct pt_hid_output hid_output = { + CREATE_PIP1_BL_CMD(PIP1_BL_CMD_ID_LAUNCH_APP), + .reset_expected = 1, + }; + + return pt_pip1_send_output_and_wait_(cd, &hid_output); +} + +/******************************************************************************* + * FUNCTION: pt_hid_output_bl_launch_app + * + * SUMMARY: Protected call to pt_hid_output_bl_launch_app_ by exclusive access + * to the DUT. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static int pt_hid_output_bl_launch_app(struct pt_core_data *cd) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = pt_hid_output_bl_launch_app_(cd); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_request_pip_launch_app + * + * SUMMARY: Function pointer included in core_nonhid_cmds to allow other modules + * to request to have the BL launch the application. (PIP1.x only) + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * protect - boolean to determine to call the protected function + ******************************************************************************/ +static int _pt_request_pip_launch_app(struct device *dev, + int protect) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return pt_hid_output_bl_launch_app(cd); + + return pt_hid_output_bl_launch_app_(cd); +} + +/******************************************************************************* + * FUNCTION: pt_hid_output_bl_get_panel_id_ + * + * SUMMARY: Sends the PIP "Get Panel ID" (0x3E) command to return the Panel ID + * value store in the System Information. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * *panel_id - pointer to where the panel ID will be stored + ******************************************************************************/ +static int pt_hid_output_bl_get_panel_id_( + struct pt_core_data *cd, u8 *panel_id) +{ + int rc; + struct pt_hid_output hid_output = { + CREATE_PIP1_BL_CMD(PIP1_BL_CMD_ID_GET_PANEL_ID), + }; + + rc = pt_pip1_send_output_and_wait_(cd, &hid_output); + if (rc == -EPROTO && cd->response_buf[5] == ERROR_COMMAND) { + pt_debug(cd->dev, DL_ERROR, + "%s: Get Panel ID command not supported\n", + __func__); + *panel_id = PANEL_ID_NOT_ENABLED; + return 0; + } else if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: Error on Get Panel ID command\n", __func__); + return rc; + } + + *panel_id = cd->response_buf[8]; + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_hid_output_bl_get_panel_id + * + * SUMMARY: Protected call to pt_hid_output_bl_get_panel_id_ by exclusive access + * to the DUT. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * *panel_id - pointer to where the panel ID will be stored + ******************************************************************************/ +static int pt_hid_output_bl_get_panel_id( + struct pt_core_data *cd, u8 *panel_id) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = pt_hid_output_bl_get_panel_id_(cd, panel_id); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_request_pip_bl_get_panel_id + * + * SUMMARY: Function pointer included in core_nonhid_cmds to allow other modules + * to have the BL retrieve the panel ID + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * protect - flag to run in protected mode + * *panel_id - pointer to where the panel ID will be stored + ******************************************************************************/ +static int _pt_request_pip_bl_get_panel_id( + struct device *dev, int protect, u8 *panel_id) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return pt_hid_output_bl_get_panel_id(cd, panel_id); + + return pt_hid_output_bl_get_panel_id_(cd, panel_id); +} + +/******************************************************************************* + * FUNCTION: pt_pip2_get_mode_sysmode_ + * + * SUMMARY: Determine the current mode and system mode of the DUT by use of the + * PIP2 STATUS command. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data structure + * *mode - pointer to store the retrieved mode + * *sys_mode - pointer to store the FW system mode + ******************************************************************************/ +static int pt_pip2_get_mode_sysmode_(struct pt_core_data *cd, + u8 *mode, u8 *sys_mode) +{ + u16 actual_read_len; + u8 read_buf[12]; + u8 status, boot; + int rc = 0; + + rc = _pt_request_pip2_send_cmd(cd->dev, PT_CORE_CMD_UNPROTECTED, + PIP2_CMD_ID_STATUS, NULL, 0, read_buf, &actual_read_len); + + pt_debug(cd->dev, DL_INFO, "%s: PIP2 STATUS command rc = %d\n", + __func__, rc); + + if (!rc) { + pt_pr_buf(cd->dev, DL_DEBUG, read_buf, actual_read_len, + "PIP2 STATUS"); + status = read_buf[PIP2_RESP_STATUS_OFFSET]; + boot = read_buf[PIP2_RESP_BODY_OFFSET] & 0x01; + if (sys_mode) { + if (status == PIP2_RSP_ERR_NONE && + boot == PIP2_STATUS_APP_EXEC) + *sys_mode = read_buf[PIP2_RESP_BODY_OFFSET + 1]; + else + *sys_mode = FW_SYS_MODE_UNDEFINED; + } + if (mode) { + if (status == PIP2_RSP_ERR_NONE && + boot == PIP2_STATUS_BOOT_EXEC) + *mode = PT_MODE_BOOTLOADER; + else if (status == PIP2_RSP_ERR_NONE && + boot == PIP2_STATUS_APP_EXEC) + *mode = PT_MODE_OPERATIONAL; + else + *mode = PT_MODE_UNKNOWN; + } + } else { + if (mode) + *mode = PT_MODE_UNKNOWN; + if (sys_mode) + *sys_mode = FW_SYS_MODE_UNDEFINED; + pt_debug(cd->dev, DL_WARN, + "%s: Mode and sys_mode could not be determined\n", + __func__); + } + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_pip2_get_mode_sysmode + * + * SUMMARY: Protected call to pt_pip2_get_mode_sysmode_ by exclusive + * access to the DUT. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data structure + * *mode - pointer to store the retrieved mode + * *sys_mode - pointer to store the FW system mode + ******************************************************************************/ +static int pt_pip2_get_mode_sysmode(struct pt_core_data *cd, + u8 *mode, u8 *sys_mode) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = pt_pip2_get_mode_sysmode_(cd, mode, sys_mode); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_request_pip2_get_mode_sysmode + * + * SUMMARY: Function pointer included in core_commands struct for external + * calls to the protected or unprotected call to + * pt_pip2_get_mode_sysmode + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *mode - pointer to store the retrieved mode + * *sys_mode - pointer to store the FW system mode + ******************************************************************************/ +static int _pt_request_pip2_get_mode_sysmode(struct device *dev, + int protect, u8 *mode, u8 *sys_mode) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return pt_pip2_get_mode_sysmode(cd, mode, sys_mode); + + return pt_pip2_get_mode_sysmode_(cd, mode, sys_mode); +} + +/******************************************************************************* + * FUNCTION: _pt_poll_for_fw_exit_boot_mode + * + * SUMMARY: Verify and or poll for the FW to exit BOOT mode. During the FW BOOT + * mode only the following PIP commands will be serviced, any other PIP + * command the FW will respond with an "Invalid PIP Command" response. + * - Get HID Descriptor (Register 0x0001, no command ID) + * - Reset (Register 0x0005, RESET HID request) + * - Ping (Register 0x0004, Command ID 0x00 + * - Get System Information (Register 0x0004, Command ID 0x02) + * - PIP2 Status (Register 0x0101, Command ID 0x01) + * - PIP2 Version (Register 0x0101, Command ID 0x07) + * This function will loop on the results of the STATUS command until + * the FW reports it is out of BOOT mode. + * + * NOTE: + * - This function will update cd->fw_system_mode + * - The STATUS cmd only supports this functionality for PIP 1.11+ + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * timeout - max time (ms) to wait for FW to exit BOOT mode + * actual_wait - pointer to actual time waited for FW to exit BOOT mode + ******************************************************************************/ +static int _pt_poll_for_fw_exit_boot_mode(struct pt_core_data *cd, int timeout, + int *actual_wait) +{ + int loop = 0; + u8 sys_mode = cd->fw_system_mode; + u8 pause = 10; /* in ms */ + int rc = 0; + int max_loop = (timeout / pause) + 1; /* Add 1 due to int math */ + + if (cd->sysinfo.ready && !IS_PIP_VER_GE(&cd->sysinfo, 1, 11)) { + /* + * For PIP <1.11, no support for polling wait so do a hard + * coded wait and assume the FW is out of BOOT. Added 1 to + * timeout to make it clear in kmsg if non polling was done. + */ + *actual_wait = PT_FW_EXIT_BOOT_MODE_TIMEOUT + 1; + pt_debug(cd->dev, DL_ERROR, + "%s: PIP %d.%d no support for ext STATUS, sleep %d\n", + __func__, + cd->sysinfo.ttdata.pip_ver_major, + cd->sysinfo.ttdata.pip_ver_minor, *actual_wait); + msleep(*actual_wait); + sys_mode = FW_SYS_MODE_SCANNING; + } + + if (sys_mode == FW_SYS_MODE_BOOT) { + while (!rc && loop <= max_loop && + (sys_mode == FW_SYS_MODE_BOOT)) { + loop++; + usleep_range(9000, pause * 1000); + rc = pt_pip2_get_mode_sysmode_(cd, NULL, &sys_mode); + pt_debug(cd->dev, DL_DEBUG, + "%s: FW in BOOT mode-sleep %dms, sys_mode=%d\n", + __func__, loop * pause, sys_mode); + } + *actual_wait = (int)(loop * pause); + pt_debug(cd->dev, DL_WARN, + "%s: FW exited BOOT mode in %dms, sys_mode=%d\n", + __func__, *actual_wait, sys_mode); + + if (rc) + sys_mode = FW_SYS_MODE_UNDEFINED; + else if (sys_mode == FW_SYS_MODE_BOOT || + sys_mode == FW_SYS_MODE_UNDEFINED) + rc = -EBUSY; + } + + mutex_lock(&cd->system_lock); + cd->fw_system_mode = sys_mode; + mutex_unlock(&cd->system_lock); + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_poll_for_fw_exit_boot_mode + * + * SUMMARY: Protected call to _pt_poll_for_fw_exit_boot_mode by exclusive + * access to the DUT. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * timeout - max time (ms) to wait for FW to exit BOOT mode + * actual_wait - pointer to actual time waited for FW to exit BOOT mode + ******************************************************************************/ +static int pt_poll_for_fw_exit_boot_mode(struct pt_core_data *cd, int timeout, + int *actual_wait) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = _pt_poll_for_fw_exit_boot_mode(cd, timeout, actual_wait); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_get_fw_sys_mode + * + * SUMMARY: Determine the FW system mode. For PIP 1.11+ the + * PIP2 STATUS command is used to directly query the FW system mode. For older + * PIP versions, there is no direct PIP commamnd that will directly provide this + * information but any PIP command above 0x1F requires scanning to be disabled + * before it will be operational. If scanning was not disabled before sending + * these PIP commands the FW will respond with a 6 byte error response. So to + * safely determine the scanning state, a PIP message that will not affect the + * operation of the FW was chosen: + * "Verify Data Block CRC (ID 0x20)" is sent and if a 6 byte error code is + * received scanning is active. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * *sys_mode - pointer to FW System mode + * *mode - pointer to mode (BL/FW) + ******************************************************************************/ +static int _pt_get_fw_sys_mode(struct pt_core_data *cd, u8 *sys_mode, u8 *mode) +{ + int write_length = 1; + int report_length; + int rc = 0; + u8 tmp_sys_mode = FW_SYS_MODE_UNDEFINED; + u8 tmp_mode = PT_MODE_UNKNOWN; + u8 param[1] = { PT_TCH_PARM_EBID }; + struct pt_hid_output hid_output = { + CREATE_PIP1_FW_CMD(PIP1_CMD_ID_VERIFY_CONFIG_BLOCK_CRC), + .write_length = write_length, + .write_buf = param, + .novalidate = true, + }; + + /* AFter PIP1.11 the preferred method is using STATUS cmd */ + if (IS_PIP_VER_GE(&cd->sysinfo, 1, 11)) { + rc = pt_pip2_get_mode_sysmode_(cd, &tmp_mode, &tmp_sys_mode); + pt_debug(cd->dev, DL_DEBUG, "%s: tmp_sys_mode=%d tmp_mode=%d\n", + __func__, tmp_sys_mode, tmp_mode); + if (!rc) { + if (tmp_mode != PT_MODE_OPERATIONAL) + tmp_sys_mode = FW_SYS_MODE_UNDEFINED; + } + goto exit; + } + + /* Older systems use PIP1 CONFIG_BLOCK_CRC to best determine sys_mode */ + if (cd->mode != PT_MODE_OPERATIONAL) { + tmp_mode = cd->mode; + goto exit; + } + + rc = pt_pip1_send_output_and_wait_(cd, &hid_output); + if (rc) + goto exit; + + report_length = (cd->response_buf[1] << 8) | (cd->response_buf[0]); + if ((report_length == 0x06) && + ((cd->response_buf[4] & PIP1_RESP_COMMAND_ID_MASK) == 0x00) && + (cd->response_buf[5] == PIP1_CMD_ID_VERIFY_CONFIG_BLOCK_CRC)) { + tmp_mode = PIP2_STATUS_APP_EXEC; + tmp_sys_mode = FW_SYS_MODE_SCANNING; + } else if ((report_length == 0x0A) && + ((cd->response_buf[4] & PIP1_RESP_COMMAND_ID_MASK) == + PIP1_CMD_ID_VERIFY_CONFIG_BLOCK_CRC)) { + tmp_mode = PIP2_STATUS_APP_EXEC; + tmp_sys_mode = FW_SYS_MODE_TEST; + } + +exit: + if (mode) + *mode = tmp_mode; + if (sys_mode) + *sys_mode = tmp_sys_mode; + pt_debug(cd->dev, DL_INFO, "%s: Return Mode=%d sys_mode=%d\n", + __func__, tmp_mode, tmp_sys_mode); + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_get_fw_sys_mode + * + * SUMMARY: Protected call to _pt_get_fw_sys_mode() to determine if FW scanning + * is active or not. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * *sys_mode - pointer to fw system mode + * *mode - pointer to mode + ******************************************************************************/ +static int pt_get_fw_sys_mode(struct pt_core_data *cd, u8 *sys_mode, u8 *mode) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = _pt_get_fw_sys_mode(cd, sys_mode, mode); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_request_get_fw_sys_mode + * + * SUMMARY: Function pointer included in core_cmds to allow other modules + * to request to get scan state + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * protect - flag to call protected or non-protected + * *sys_mode - pointer to FW system mode + * *mode - pointer to mode + ******************************************************************************/ +static int _pt_request_get_fw_sys_mode(struct device *dev, int protect, + u8 *sys_mode, u8 *mode) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return pt_get_fw_sys_mode(cd, sys_mode, mode); + + return _pt_get_fw_sys_mode(cd, sys_mode, mode); +} + +/* Default hid descriptor to provide basic register map */ +const struct pt_hid_desc hid_desc_default = { + 230, /* hid_desc_len */ + HID_APP_REPORT_ID, /* packet_id */ + 0x00, /* reserved_byte */ + 0x0100, /* bcd_version */ + 0x00EC, /* report_desc_len */ + 0x0002, /* report_desc_register */ + 0x0003, /* input_register */ + 0x00FE, /* max_input_len */ + 0x0004, /* output_register */ + 0x00FE, /* max_output_len */ + 0x0005, /* command_register */ + 0x0006, /* data_register */ + 0x04B4, /* vendor_id */ + 0xC101, /* product_id */ + 0x0100, /* version_id */ + {0x00, 0x00, 0x00, 0x00} /* reserved[4] */ +}; + +/******************************************************************************* + * FUNCTION: pt_init_hid_descriptor + * + * SUMMARY: Setup default values for HID descriptor structure + * + * + * PARAMETERS: + * *desc - pointer to the HID descriptor data read back from DUT + ******************************************************************************/ +static inline void pt_init_hid_descriptor(struct pt_hid_desc *desc) +{ + memcpy(desc, &hid_desc_default, sizeof(hid_desc_default)); +} + +/******************************************************************************* + * FUNCTION: pt_get_hid_descriptor_ + * + * SUMMARY: Send the get HID descriptor command to the DUT and load the response + * into the HID descriptor structure + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * *desc - pointer to the HID descriptor data read back from DUT + ******************************************************************************/ +static int pt_get_hid_descriptor_(struct pt_core_data *cd, + struct pt_hid_desc *desc) +{ + struct device *dev = cd->dev; + int rc = 0; + int t; + u8 cmd[2]; + + /* + * During startup the HID descriptor is required for all future + * processing. If IRQ is already asserted due to an early touch report + * the report must be cleared before sending command. + */ + pt_flush_bus_if_irq_asserted(cd, PT_FLUSH_BUS_BASED_ON_LEN); + + /* Read HID descriptor length and version */ + mutex_lock(&cd->system_lock); + cd->hid_cmd_state = 1; + mutex_unlock(&cd->system_lock); + + /* Set HID descriptor register */ + memcpy(cmd, &cd->hid_core.hid_desc_register, + sizeof(cd->hid_core.hid_desc_register)); + + pt_debug(cd->dev, DL_INFO, ">>> %s: Write Buffer [%zu]", + __func__, sizeof(cmd)); + pt_pr_buf(cd->dev, DL_DEBUG, cmd, sizeof(cmd), ">>> Get HID Desc"); + rc = pt_adap_write_read_specific(cd, 2, cmd, NULL); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: failed to get HID descriptor, rc=%d\n", + __func__, rc); + goto error; + } + + t = wait_event_timeout(cd->wait_q, (cd->hid_cmd_state == 0), + msecs_to_jiffies(PT_GET_HID_DESCRIPTOR_TIMEOUT)); + + if (IS_TMO(t)) { +#ifdef TTDL_DIAGNOSTICS + cd->bus_transmit_error_count++; + pt_toggle_err_gpio(cd, PT_ERR_GPIO_I2C_TRANS); +#endif /* TTDL_DIAGNOSTICS */ + pt_debug(cd->dev, DL_ERROR, + "%s: HID get descriptor timed out\n", __func__); + rc = -ETIME; + goto error; + } else { + cd->hw_detected = true; + } + + /* Load the HID descriptor including all registers */ + memcpy((u8 *)desc, cd->response_buf, sizeof(struct pt_hid_desc)); + + /* Check HID descriptor length and version */ + pt_debug(dev, DL_INFO, "%s: HID len:%X HID ver:%X\n", __func__, + le16_to_cpu(desc->hid_desc_len), + le16_to_cpu(desc->bcd_version)); + + if (le16_to_cpu(desc->hid_desc_len) != sizeof(*desc) || + le16_to_cpu(desc->bcd_version) != HID_VERSION) { + pt_debug(dev, DL_ERROR, "%s: Unsupported HID version\n", + __func__); + return -ENODEV; + } + + goto exit; + +error: + mutex_lock(&cd->system_lock); + cd->hid_cmd_state = 0; + mutex_unlock(&cd->system_lock); +exit: + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_get_hid_descriptor + * + * SUMMARY: Protected call to pt_get_hid_descriptor_() + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * *desc - pointer to the HID descriptor data read back from DUT + ******************************************************************************/ +static int pt_get_hid_descriptor(struct pt_core_data *cd, + struct pt_hid_desc *desc) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = pt_get_hid_descriptor_(cd, desc); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_pip2_get_version_ + * + * SUMMARY: Sends a PIP2 VERSION command to the DUT and stores the data in + * cd-ttdata + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static int pt_pip2_get_version_(struct pt_core_data *cd) +{ + int rc = 0; + int status; + u8 read_buf[64]; + u16 actual_read_len; + + rc = _pt_request_pip2_send_cmd(cd->dev, + PT_CORE_CMD_UNPROTECTED, PIP2_CMD_ID_VERSION, + NULL, 0, read_buf, &actual_read_len); + if (rc) { + pt_debug(cd->dev, DL_ERROR, + "%s: Error Sending PIP2 VERSION Cmd rc=%d\n", + __func__, rc); + return rc; + } + + status = read_buf[PIP2_RESP_STATUS_OFFSET]; + if (status == 0) { + /* Parse the PIP2 VERSION response into ttdata */ + pt_pip2_ver_load_ttdata(cd, actual_read_len); + } else { + pt_debug(cd->dev, DL_ERROR, + "%s: Error in PIP2 VERSION Cmd status=%d\n", + __func__, status); + return status; + } + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_pip2_get_version + * + * SUMMARY: Protected call to pt_pip2_get_version_ by exclusive + * access to the DUT. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data structure + ******************************************************************************/ +static int pt_pip2_get_version(struct pt_core_data *cd) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = pt_pip2_get_version_(cd); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + + return rc; +} + + +/******************************************************************************* + * FUNCTION: _pt_request_active_pip_protocol + * + * SUMMARY: Get active PIP protocol version using the PIP2 version command. + * Function will return PIP version of BL or application based on + * when it's called. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * protect - flag to run in protected mode + * *pip_version_major - pointer to store PIP major version + * *pip_version_minor - pointer to store PIP minor version + ******************************************************************************/ +int _pt_request_active_pip_protocol(struct device *dev, int protect, + u8 *pip_version_major, u8 *pip_version_minor) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_ttdata *ttdata = &cd->sysinfo.ttdata; + int rc = 0; + struct pt_hid_output sys_info = { + CREATE_PIP1_FW_CMD(PIP1_CMD_ID_GET_SYSINFO), + .timeout_ms = PT_PIP1_CMD_GET_SYSINFO_TIMEOUT, + }; + + pt_flush_bus_if_irq_asserted(cd, PT_FLUSH_BUS_BASED_ON_LEN); + + /* Skip PIP2 command if DUT generation is confirmed */ + if (cd->active_dut_generation == DUT_PIP1_ONLY) + goto skip_pip2_command; + + rc = pt_pip2_get_version_(cd); + if (!rc) { + *pip_version_major = ttdata->pip_ver_major; + *pip_version_minor = ttdata->pip_ver_minor; + pt_debug(dev, DL_INFO, + "%s: pip_version = %d.%d\n", __func__, + *pip_version_major, *pip_version_minor); + } else { + /* + * Legacy products do not support the pip2 protocol to get + * pip version. However, they do support the "get sysinfo" + * command to get pip version from FW, but the bootloader + * does not support it. This function will try "get sysinfo" + * command if the pip2 command failed but this cmd could also + * fail if DUT is stuck in bootloader mode. + */ + pt_debug(dev, DL_INFO, + "%s: PIP2 no response rc = %d, try legacy cmd\n", + __func__, rc); + +skip_pip2_command: + rc = pt_pip1_send_output_and_wait_(cd, &sys_info); + if (!rc) { + *pip_version_minor = + cd->response_buf[PIP1_SYSINFO_TTDATA_OFFSET + 1]; + *pip_version_major = + cd->response_buf[PIP1_SYSINFO_TTDATA_OFFSET]; + + pt_debug(dev, DL_INFO, + "%s: pip_version = %d.%d\n", __func__, + *pip_version_major, *pip_version_minor); + } else { + *pip_version_major = 0; + *pip_version_minor = 0; + pt_debug(dev, DL_ERROR, + "%s: pip_version Not Detected\n", __func__); + } + } + + return rc; +} +EXPORT_SYMBOL_GPL(_pt_request_active_pip_protocol); + +/******************************************************************************* + * FUNCTION: _pt_detect_dut_generation + * + * SUMMARY: Determine the generation of device that we are communicating with: + * DUT_PIP1_ONLY (Gen5 or Gen6) + * DUT_PIP2_CAPABLE (TC33xx or TT7xxx) + * The HID_DESC command is supported in Gen5/6 BL and FW as well as + * TT/TC FW. The packet ID in the descriptor, however, is unique when + * coming form the BL or the FW: + * Packet_ID in BL = HID_BL_REPORT_ID (0xFF) + * Packet_ID in FW = HID_APP_REPORT_ID (0xF7) + * This function will return a modified status if it detects the DUT + * is in the BL. In the case of a Gen5/6 BL, which also sends out a FW + * reset sentinel, the status is "corrected" from a FW to BL sentinel. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *status - pointer to status bitmask + * *dut_gen - pointer to store the dut_generation + * *mode - pointer to store the PT_MODE + ******************************************************************************/ +static int _pt_detect_dut_generation(struct device *dev, + u32 *status, u8 *dut_gen, enum pt_mode *mode) +{ + int rc = 0; + u8 dut_gen_tmp = DUT_UNKNOWN; + u8 mode_tmp = PT_MODE_UNKNOWN; + u8 attempt = 1; + u32 status_tmp = STARTUP_STATUS_START; + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_hid_desc hid_desc; + + memset(&hid_desc, 0, sizeof(hid_desc)); + rc = pt_get_hid_descriptor_(cd, &hid_desc); + + while (rc && attempt < 3) { + attempt++; + usleep_range(2000, 5000); + rc = pt_get_hid_descriptor_(cd, &hid_desc); + } + + if (!rc && hid_desc.packet_id == HID_BL_REPORT_ID) { + dut_gen_tmp = DUT_PIP1_ONLY; /* Gen5/6 BL */ + mode_tmp = PT_MODE_BOOTLOADER; + status_tmp = STARTUP_STATUS_BL_RESET_SENTINEL; + } else if (!rc && hid_desc.packet_id == HID_APP_REPORT_ID) { + rc = pt_pip2_get_version_(cd); + if (!rc) + dut_gen_tmp = DUT_PIP2_CAPABLE; /* TT/TC FW */ + else + dut_gen_tmp = DUT_PIP1_ONLY; /* Gen5/6 FW */ + mode_tmp = PT_MODE_OPERATIONAL; + status_tmp = STARTUP_STATUS_FW_RESET_SENTINEL; + rc = 0; /* To return success instead of error code */ + } else if (rc) { + rc = pt_pip2_get_version_(cd); + if (!rc) { + dut_gen_tmp = DUT_PIP2_CAPABLE; /* TT/TC BL */ + mode_tmp = PT_MODE_BOOTLOADER; + status_tmp = STARTUP_STATUS_BL_RESET_SENTINEL; + } + } + + mutex_lock(&cd->system_lock); + if (dut_gen) + *dut_gen = dut_gen_tmp; + if (mode) + *mode = mode_tmp; + if (status) + *status = status_tmp; + mutex_unlock(&cd->system_lock); + +#ifdef TTDL_DIAGNOSTICS + pt_debug(cd->dev, DL_INFO, "%s: Generation=%d Mode=%d\n", + __func__, dut_gen_tmp, mode_tmp); +#endif /* TTDL_DIAGNOSTICS */ + + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_request_dut_generation + * + * SUMMARY: Function pointer included in core_cmds to allow other modules + * to get current dut generation. + * + * NOTE: This function WILL NOT try to determine dut generation. + * + * RETURN: + * The current dut generation. + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int _pt_request_dut_generation(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + return cd->active_dut_generation; +} + +#define HW_VERSION_LEN_MAX 13 +/******************************************************************************* + * FUNCTION: _legacy_generate_hw_version + * + * SUMMARY: Format chip information from struct ttdata (maintained by PIP1 + * SYSINFO command) or struct bl_info (maintained by PIP1 BL INFORMATION + * command) to the hw_version. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * *hw_version - pointer to store the hardware version + ******************************************************************************/ +static int _legacy_generate_hw_version(struct pt_core_data *cd, + char *hw_version) +{ + struct pt_ttdata *ttdata = &cd->sysinfo.ttdata; + + if (cd->sysinfo.ready) { + scnprintf(hw_version, HW_VERSION_LEN_MAX, "%04X.FFFF.%02X", + ttdata->jtag_id_h, cd->pid_for_loader); + return 0; + } else if (cd->bl_info.ready) { + scnprintf(hw_version, HW_VERSION_LEN_MAX, "%04X.FFFF.%02X", + cd->bl_info.chip_id, cd->pid_for_loader); + return 0; + } else { + snprintf(hw_version, HW_VERSION_LEN_MAX, "FFFF.FFFF.FF"); + pt_debug(cd->dev, DL_ERROR, + "%s: SYSINFO and BL_INFO are not ready\n", __func__); + return -ENODATA; + } +} + +/******************************************************************************* + * FUNCTION: _pip2_generate_hw_version + * + * SUMMARY: Format chip information from struct ttdata (maintained by PIP2 + * VERSION command) to the hw_version. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * *hw_version - pointer to store the hardware version + ******************************************************************************/ +static int _pip2_generate_hw_version(struct pt_core_data *cd, char *hw_version) +{ + struct pt_ttdata *ttdata = &cd->sysinfo.ttdata; + + if (cd->app_pip_ver_ready | cd->bl_pip_ver_ready) { + snprintf(hw_version, HW_VERSION_LEN_MAX, "%04X.%04X.%02X", + ttdata->chip_id, ttdata->chip_rev, cd->pid_for_loader); + return 0; + } else { + snprintf(hw_version, HW_VERSION_LEN_MAX, "FFFF.FFFF.FF"); + pt_debug(cd->dev, DL_ERROR, + "%s: PIP Version are not ready\n", __func__); + return -ENODATA; + } +} + +/******************************************************************************* + * FUNCTION: pt_generate_hw_version + * + * SUMMARY: Wraaper function for both legacy and TT/TC products generate the + * hw_version from static data. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * *hw_version - pointer to store the hardware version + ******************************************************************************/ +static int pt_generate_hw_version(struct pt_core_data *cd, char *hw_version) +{ + int rc = 0; + + if (!hw_version) + return -ENOMEM; + + if (cd->active_dut_generation == DUT_PIP1_ONLY) + rc = _legacy_generate_hw_version(cd, hw_version); + else if (cd->active_dut_generation == DUT_PIP2_CAPABLE) + rc = _pip2_generate_hw_version(cd, hw_version); + else { + snprintf(hw_version, HW_VERSION_LEN_MAX, "FFFF.FFFF.FF"); + rc = -ENODATA; + } + + return rc; +} +/******************************************************************************* + * SUMMARY: Attempt to retrieve the HW version of the connected DUT + * + * NOTE: The calling function must ensure to free *hw_version + * + * RETURN: + * 0 = success + * !0 = Failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *hw_version - pointer to where the hw_version string will be stored + ******************************************************************************/ +static int _pt_request_hw_version(struct device *dev, char *hw_version) +{ + int rc = 0; + u16 actual_read_len; + u16 pip_ver; + u8 rd_buf[256]; + u8 status; + u8 index = PIP2_RESP_STATUS_OFFSET; + u8 return_data[8]; + u8 panel_id; + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_ttdata *ttdata = &cd->sysinfo.ttdata; + + if (!hw_version) + return -ENOMEM; + + if (!cd->hw_detected) { + /* No HW detected */ + rc = -ENODEV; + pt_debug(dev, DL_ERROR, "%s: no hardware is detected!\n", + __func__); + goto exit_error; + } + + memset(return_data, 0, ARRAY_SIZE(return_data)); + /* For Parade TC or TT parts */ + if (cd->active_dut_generation == DUT_PIP2_CAPABLE) { + rc = _pt_request_pip2_send_cmd(dev, + PT_CORE_CMD_UNPROTECTED, PIP2_CMD_ID_VERSION, + NULL, 0, rd_buf, &actual_read_len); + + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Failed to send PIP2 VERSION cmd\n", + __func__); + goto exit_error; + } + + status = rd_buf[index]; + if (status == 0) { + pip_ver = 256 * rd_buf[index + 2] + rd_buf[index + 1]; + + /* + * BL PIP 2.02 and greater the version fields are + * swapped + */ + if (pip_ver >= 0x0202) { + snprintf(hw_version, HW_VERSION_LEN_MAX, + "%02X%02X.%02X%02X.FF", + rd_buf[index + 10], rd_buf[index + 9], + rd_buf[index + 8], rd_buf[index + 7]); + } else { + snprintf(hw_version, HW_VERSION_LEN_MAX, + "%02X%02X.%02X%02X.FF", + rd_buf[index + 8], rd_buf[index + 7], + rd_buf[index + 10], rd_buf[index + 9]); + } + return STATUS_SUCCESS; + } else { + rc = status; + pt_debug(dev, DL_WARN, + "%s: PIP2 VERSION cmd response error\n", + __func__); + } + } else if (cd->active_dut_generation == DUT_PIP1_ONLY) { + /* + * For Parade/Cypress legacy parts the RevID and FamilyID are + * hard coded to FFFF + */ + if (cd->mode == PT_MODE_OPERATIONAL) { + rc = pt_hid_output_get_sysinfo(cd); + if (!rc) { + panel_id = + cd->sysinfo.sensing_conf_data.panel_id; + } else { + panel_id = PANEL_ID_NOT_ENABLED; + } + /* In FW - simply retrieve from ttdata struct */ + snprintf(hw_version, HW_VERSION_LEN_MAX, + "%04X.FFFF.%02X", + ttdata->jtag_id_h, + panel_id); + return STATUS_SUCCESS; + } else { + /* + * Return the stored value if PT_PANEL_ID_BY_BL + * is not supported while other feature is. + */ + if (cd->panel_id_support & PT_PANEL_ID_BY_BL) { + rc = pt_hid_output_bl_get_information( + cd, return_data); + if (!rc) { + rc = pt_hid_output_bl_get_panel_id( + cd, &panel_id); + } + } else + panel_id = cd->pid_for_loader; + if (!rc) { + snprintf(hw_version, + HW_VERSION_LEN_MAX, + "%02X%02X.FFFF.%02X", + return_data[3], return_data[2], + panel_id); + return STATUS_SUCCESS; + } + } + } else { + /* Unknown generation */ + rc = -ENODEV; + pt_debug(dev, DL_ERROR, "%s: generation is unknown!\n", + __func__); + } + +exit_error: + snprintf(hw_version, HW_VERSION_LEN_MAX, "FFFF.FFFF.FF"); + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_start_wd_timer + * + * SUMMARY: Starts the TTDL watchdog timer if the timer interval is > 0 + * + * RETURN: void + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static void pt_start_wd_timer(struct pt_core_data *cd) +{ + if (cd->watchdog_interval < 100) { + pt_debug(cd->dev, DL_ERROR, + "%s: WARNING: Invalid watchdog interval: %d\n", + __func__, cd->watchdog_interval); + return; + } + + if (cd->watchdog_force_stop) { + pt_debug(cd->dev, DL_INFO, + "%s: TTDL WD Forced stop\n", __func__); + return; + } + + mod_timer(&cd->watchdog_timer, jiffies + + msecs_to_jiffies(cd->watchdog_interval)); + cd->watchdog_enabled = 1; + pt_debug(cd->dev, DL_INFO, "%s: TTDL WD Started\n", __func__); +} + +/******************************************************************************* + * FUNCTION: pt_stop_wd_timer + * + * SUMMARY: Stops the TTDL watchdog timer if the timer interval is > 0 + * + * RETURN: void + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static void pt_stop_wd_timer(struct pt_core_data *cd) +{ + if (!cd->watchdog_interval) + return; + + /* + * Ensure we wait until the watchdog timer + * running on a different CPU finishes + */ + del_timer_sync(&cd->watchdog_timer); + cancel_work_sync(&cd->watchdog_work); + del_timer_sync(&cd->watchdog_timer); + cd->watchdog_enabled = 0; + pt_debug(cd->dev, DL_INFO, "%s: TTDL WD Stopped\n", __func__); +} + +/******************************************************************************* + * FUNCTION: pt_hw_soft_reset + * + * SUMMARY: Sends a PIP reset command to the DUT. Disable/re-enable the + * TTDL watchdog around the reset to ensure the WD doesn't happen to + * schedule an enum if it fires when the DUT is being reset. + * This can cause a double reset. + * + * NOTE: The WD MUST be stopped/restarted by the calling Function. Having + * the WD active could cause this function to fail! + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data struct + * protect - flag to call protected or non-protected + ******************************************************************************/ +static int pt_hw_soft_reset(struct pt_core_data *cd, int protect) +{ + int rc = 0; + + mutex_lock(&cd->system_lock); + cd->startup_status = STARTUP_STATUS_START; + pt_debug(cd->dev, DL_DEBUG, "%s: Startup Status Reset\n", __func__); + mutex_unlock(&cd->system_lock); + if (protect) + rc = pt_hid_cmd_reset(cd); + else + rc = pt_hid_cmd_reset_(cd); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: FAILED to execute SOFT reset\n", __func__); + return rc; + } + pt_debug(cd->dev, DL_INFO, "%s: SOFT reset successful\n", + __func__); + + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_hw_hard_reset + * + * SUMMARY: Calls the platform xres function if it exists to perform a hard + * reset on the DUT by toggling the XRES gpio. Disable/re-enable the + * TTDL watchdog around the reset to ensure the WD doesn't happen to + * schedule an enum if it fires when the DUT is being reset. + * This can cause a double reset. + * + * NOTE: The WD MUST be stopped/restarted by the calling Function. Having + * the WD active could cause this function to fail! + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data struct + ******************************************************************************/ +static int pt_hw_hard_reset(struct pt_core_data *cd) +{ + if (cd->cpdata->xres) { + cd->startup_status = STARTUP_STATUS_START; + pt_debug(cd->dev, DL_DEBUG, "%s: Startup Status Reset\n", + __func__); + cd->cpdata->xres(cd->cpdata, cd->dev); + pt_debug(cd->dev, DL_WARN, "%s: executed HARD reset\n", + __func__); + return 0; + } + pt_debug(cd->dev, DL_ERROR, + "%s: FAILED to execute HARD reset\n", __func__); + + return -ENODEV; +} + +/******************************************************************************* + * FUNCTION: pt_dut_reset + * + * SUMMARY: Attempts to reset the DUT by a hard reset and if that fails a + * soft reset. + * + * NOTE: The WD MUST be stopped/restarted by the calling Function. Having + * the WD active could cause this function to fail! + * NOTE: "protect" flag is only used for soft reset. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data structure + * protect - flag to call protected or non-protected + ******************************************************************************/ +static int pt_dut_reset(struct pt_core_data *cd, int protect) +{ + int rc = 0; + + pt_debug(cd->dev, DL_INFO, "%s: reset hw...\n", __func__); + mutex_lock(&cd->system_lock); + cd->hid_reset_cmd_state = 1; + rc = pt_hw_hard_reset(cd); + mutex_unlock(&cd->system_lock); + + if (rc == -ENODEV) { + mutex_lock(&cd->system_lock); + cd->hid_reset_cmd_state = 0; + mutex_unlock(&cd->system_lock); + pt_debug(cd->dev, DL_ERROR, + "%s: Hard reset failed, try soft reset\n", __func__); + rc = pt_hw_soft_reset(cd, protect); + } + + if (rc) + pt_debug(cd->dev, DL_ERROR, "%s: %s dev='%s' r=%d\n", + __func__, "Fail hw reset", dev_name(cd->dev), rc); + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_dut_reset_and_wait + * + * SUMMARY: Wrapper function for pt_dut_reset that waits for the reset to + * complete + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data structure + ******************************************************************************/ +static int pt_dut_reset_and_wait(struct pt_core_data *cd) +{ + int rc = 0; + int t; + + rc = pt_dut_reset(cd, PT_CORE_CMD_UNPROTECTED); + if (rc < 0) + goto exit; + + t = wait_event_timeout(cd->wait_q, + (cd->hid_reset_cmd_state == 0), + msecs_to_jiffies(PT_HID_CMD_DEFAULT_TIMEOUT)); + if (IS_TMO(t)) { +#ifdef TTDL_DIAGNOSTICS + cd->bus_transmit_error_count++; + pt_toggle_err_gpio(cd, PT_ERR_GPIO_I2C_TRANS); +#endif /* TTDL_DIAGNOSTICS */ + pt_debug(cd->dev, DL_ERROR, "%s: reset timed out\n", + __func__); + rc = -ETIME; + goto exit; + } + +exit: + return rc; +} + +/* + * touch default parameters (from report descriptor) to resolve protocol for + * touch report + */ +const struct pt_tch_abs_params tch_hdr_default[PT_TCH_NUM_HDR] = { + /* byte offset, size, min, max, bit offset, report */ + {0x00, 0x02, 0x00, 0x10000, 0x00, 0x01}, /* SCAN TIME */ + {0x02, 0x01, 0x00, 0x20, 0x00, 0x01}, /* NUMBER OF RECORDS */ + {0x02, 0x01, 0x00, 0x02, 0x05, 0x01}, /* LARGE OBJECT */ + {0x03, 0x01, 0x00, 0x08, 0x00, 0x01}, /* NOISE EFFECT */ + {0x03, 0x01, 0x00, 0x04, 0x06, 0x01}, /* REPORT_COUNTER */ +}; + +/* + * button default parameters (from report descriptor) to resolve protocol for + * button report + */ +const struct pt_tch_abs_params tch_abs_default[PT_TCH_NUM_ABS] = { + /* byte offset, size, min, max, bit offset, report */ + {0x02, 0x02, 0x00, 0x10000, 0x00, 0x01}, /* X */ + {0x04, 0x02, 0x00, 0x10000, 0x00, 0x01}, /* Y */ + {0x06, 0x01, 0x00, 0x100, 0x00, 0x01}, /* P (Z) */ + {0x01, 0x01, 0x00, 0x20, 0x00, 0x01}, /* TOUCH ID */ + {0x01, 0x01, 0x00, 0x04, 0x05, 0x01}, /* EVENT ID */ + {0x00, 0x01, 0x00, 0x08, 0x00, 0x01}, /* OBJECT ID */ + {0x01, 0x01, 0x00, 0x02, 0x07, 0x01}, /* LIFTOFF */ + {0x07, 0x01, 0x00, 0x100, 0x00, 0x01}, /* TOUCH_MAJOR */ + {0x08, 0x01, 0x00, 0x100, 0x00, 0x01}, /* TOUCH_MINOR */ + {0x09, 0x01, 0x00, 0x100, 0x00, 0x01}, /* ORIENTATION */ +}; + +/******************************************************************************* + * FUNCTION: pt_init_pip_report_fields + * + * SUMMARY: Setup default values for touch/button report parsing. + * + * PARAMETERS: + * *cd - pointer to core data structure + ******************************************************************************/ +static void pt_init_pip_report_fields(struct pt_core_data *cd) +{ + struct pt_sysinfo *si = &cd->sysinfo; + + memcpy(si->tch_hdr, tch_hdr_default, sizeof(tch_hdr_default)); + memcpy(si->tch_abs, tch_abs_default, sizeof(tch_abs_default)); + + si->desc.tch_report_id = PT_PIP_TOUCH_REPORT_ID; + si->desc.tch_record_size = TOUCH_REPORT_SIZE; + si->desc.tch_header_size = TOUCH_INPUT_HEADER_SIZE; + si->desc.btn_report_id = PT_PIP_CAPSENSE_BTN_REPORT_ID; + + cd->features.easywake = 1; + cd->features.noise_metric = 1; + cd->features.tracking_heatmap = 1; + cd->features.sensor_data = 1; +} + +/******************************************************************************* + * FUNCTION: pt_get_mode + * + * SUMMARY: Determine the current mode from the contents of a HID descriptor + * message + * + * RETURN: Enum of the current mode + * + * PARAMETERS: + * *cd - pointer to the Core Data structure + * protect - run command in protected mode + * *mode - pointer to store the retrieved mode + ******************************************************************************/ +static int pt_get_mode(struct pt_core_data *cd, struct pt_hid_desc *desc) +{ + if (desc->packet_id == HID_APP_REPORT_ID) + return PT_MODE_OPERATIONAL; + else if (desc->packet_id == HID_BL_REPORT_ID) + return PT_MODE_BOOTLOADER; + + return PT_MODE_UNKNOWN; +} + +/******************************************************************************* + * FUNCTION: _pt_request_get_mode + * + * SUMMARY: Function pointer included in core_cmds to allow other modules + * to determine the current mode of the DUT by use of the Get HID + * Descriptor command. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * protect - run command in protected mode + * *mode - pointer to store the retrieved mode + ******************************************************************************/ +static int _pt_request_get_mode(struct device *dev, int protect, u8 *mode) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_hid_desc hid_desc; + int rc = 0; + + memset(&hid_desc, 0, sizeof(hid_desc)); + + if (protect) + rc = pt_get_hid_descriptor(cd, &hid_desc); + else + rc = pt_get_hid_descriptor_(cd, &hid_desc); + + if (rc) + *mode = PT_MODE_UNKNOWN; + else + *mode = pt_get_mode(cd, &hid_desc); + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_queue_enum_ + * + * SUMMARY: Queues a TTDL enum by scheduling work with the pt_enum_with_dut() + * function. It won't try to add/delete sysfs node or modules. + * + * RETURN: void + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static void pt_queue_enum_(struct pt_core_data *cd) +{ + if (cd->startup_state == STARTUP_NONE) { + cd->startup_state = STARTUP_QUEUED; + +#ifdef TTDL_DIAGNOSTICS + if (!cd->bridge_mode) + schedule_work(&cd->enum_work); + else + cd->startup_state = STARTUP_NONE; +#else + schedule_work(&cd->enum_work); +#endif + pt_debug(cd->dev, DL_INFO, + "%s: enum_work queued\n", __func__); + } else { + pt_debug(cd->dev, DL_WARN, + "%s: Enum not queued - startup_state = %d\n", + __func__, cd->startup_state); + } +} + +/******************************************************************************* + * FUNCTION: pt_queue_enum + * + * SUMMARY: Queues a TTDL enum within a mutex lock + * + * RETURN: void + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static void pt_queue_enum(struct pt_core_data *cd) +{ + mutex_lock(&cd->system_lock); + pt_queue_enum_(cd); + mutex_unlock(&cd->system_lock); +} + +static void remove_sysfs_and_modules(struct device *dev); +/******************************************************************************* + * FUNCTION: pt_queue_restart + * + * SUMMARY: Queues a TTDL restart within a mutex lock + * + * RETURN: void + * + * PARAMETERS: + * *cd - pointer to core data + * remove_sysfs_module - True: remove all DUT relative sysfs nodes and modules + * False: will not perform remove action + ******************************************************************************/ +static void pt_queue_restart(struct pt_core_data *cd) +{ + mutex_lock(&cd->system_lock); + if (cd->startup_state == STARTUP_NONE) { + cd->startup_state = STARTUP_QUEUED; + + schedule_work(&cd->ttdl_restart_work); + pt_debug(cd->dev, DL_INFO, + "%s: pt_ttdl_restart queued\n", __func__); + } else { + pt_debug(cd->dev, DL_INFO, "%s: startup_state = %d\n", + __func__, cd->startup_state); + } + mutex_unlock(&cd->system_lock); +} + +/******************************************************************************* + * FUNCTION: call_atten_cb + * + * SUMMARY: Iterate over attention list call the function that registered. + * + * RETURN: void + * + * PARAMETERS: + * *cd - pointer to core data + * type - type of attention list + * mode - condition for execution + ******************************************************************************/ +static void call_atten_cb(struct pt_core_data *cd, + enum pt_atten_type type, int mode) +{ + struct atten_node *atten, *atten_n; + + pt_debug(cd->dev, DL_DEBUG, "%s: check list type=%d mode=%d\n", + __func__, type, mode); + spin_lock(&cd->spinlock); + list_for_each_entry_safe(atten, atten_n, + &cd->atten_list[type], node) { + if (!mode || atten->mode & mode) { + spin_unlock(&cd->spinlock); + pt_debug(cd->dev, DL_DEBUG, + "%s: attention for '%s'", + __func__, dev_name(atten->dev)); + atten->func(atten->dev); + spin_lock(&cd->spinlock); + } + } + spin_unlock(&cd->spinlock); +} + +/******************************************************************************* + * FUNCTION: start_fw_upgrade + * + * SUMMARY: Calling "PT_ATTEN_LOADER" attention list that loader registered to + * start firmware upgrade. + * + * RETURN: + * 0 = success + * + * PARAMETERS: + * *data - pointer to core data + ******************************************************************************/ +static int start_fw_upgrade(void *data) +{ + struct pt_core_data *cd = (struct pt_core_data *)data; + + call_atten_cb(cd, PT_ATTEN_LOADER, 0); + + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_put_device_into_easy_wakeup_ + * + * SUMMARY: Call the enter_easywake_state function and set the device into easy + * wake up state. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static int pt_put_device_into_easy_wakeup_(struct pt_core_data *cd) +{ + int rc = 0; + u8 status = 0; + + + rc = pt_hid_output_enter_easywake_state_(cd, + cd->easy_wakeup_gesture, &status); + if (rc || status == 0) + return -EBUSY; + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_put_device_into_deep_sleep_ + * + * SUMMARY: Call the set_power function and set the DUT to deep sleep + * + * RETURN: + * 0 = success + * !0 = error + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static int pt_put_device_into_deep_sleep_(struct pt_core_data *cd) +{ + int rc = 0; + + rc = pt_hid_cmd_set_power_(cd, HID_POWER_SLEEP); + if (rc) + rc = -EBUSY; + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_put_device_into_deep_standby_ + * + * SUMMARY: Call the set_power function and set the DUT to Deep Standby + * + * RETURN: + * 0 = success + * !0 = error + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static int pt_put_device_into_deep_standby_(struct pt_core_data *cd) +{ + int rc = 0; + + rc = pt_hid_cmd_set_power_(cd, HID_POWER_STANDBY); + if (rc) + rc = -EBUSY; + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_core_poweroff_device_ + * + * SUMMARY: Disable IRQ and HW power down the device. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static int pt_core_poweroff_device_(struct pt_core_data *cd) +{ + int rc; + + if (cd->irq_enabled) { + cd->irq_enabled = false; + disable_irq_nosync(cd->irq); + } + + rc = cd->cpdata->power(cd->cpdata, 0, cd->dev, 0); + if (rc < 0) + pt_debug(cd->dev, DL_ERROR, "%s: HW Power down fails r=%d\n", + __func__, rc); + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_core_sleep_ + * + * SUMMARY: Suspend the device with power off or deep sleep based on the + * configuration in the core platform data structure. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static int pt_core_sleep_(struct pt_core_data *cd) +{ + int rc = 0; + + mutex_lock(&cd->system_lock); + pt_debug(cd->dev, DL_INFO, "%s - sleep_state %d\n", __func__, cd->sleep_state); + if (cd->sleep_state == SS_SLEEP_ON || cd->sleep_state == SS_SLEEPING) { + mutex_unlock(&cd->system_lock); + pt_debug(cd->dev, DL_INFO, + "%s - Skip slee[ state %d\n", __func__, cd->sleep_state); + return 0; + } else { + cd->sleep_state = SS_SLEEPING; + } + mutex_unlock(&cd->system_lock); + + /* Ensure watchdog and startup works stopped */ + pt_stop_wd_timer(cd); + cancel_work_sync(&cd->enum_work); + pt_stop_wd_timer(cd); + + if (cd->cpdata->flags & PT_CORE_FLAG_POWEROFF_ON_SLEEP) { + pt_debug(cd->dev, DL_INFO, "%s: Entering into power off mode:\n", __func__); + rc = pt_core_poweroff_device_(cd); + if (rc) + pr_err("%s: Power off error detected :rc=%d\n", __func__, rc); + } else if (cd->cpdata->flags & PT_CORE_FLAG_DEEP_STANDBY) { + pt_debug(cd->dev, DL_INFO, + "%s: Entering into deep standby mode:\n", __func__); + rc = pt_put_device_into_deep_standby_(cd); + if (rc) + pr_err("%s: Deep standby error detected :rc=%d\n", __func__, rc); + } else { + pt_debug(cd->dev, DL_INFO, + "%s: Entering into deep sleep mode:\n", __func__); + rc = pt_put_device_into_deep_sleep_(cd); + if (rc) + pr_err("%s: Deep sleep error detected :rc=%d\n", __func__, rc); + } + + mutex_lock(&cd->system_lock); + cd->sleep_state = SS_SLEEP_ON; + mutex_unlock(&cd->system_lock); + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_core_easywake_on_ + * + * SUMMARY: Suspend the device with easy wake on the + * configuration in the core platform data structure. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static int pt_core_easywake_on_(struct pt_core_data *cd) +{ + int rc = 0; + + mutex_lock(&cd->system_lock); + + if (cd->sleep_state == SS_EASY_WAKING_ON) { + mutex_unlock(&cd->system_lock); + pt_debug(cd->dev, DL_INFO, "%s - Skip sleep state %d\n", + __func__, cd->sleep_state); + return 0; + } + mutex_unlock(&cd->system_lock); + + /* Ensure watchdog and startup works stopped */ + pt_stop_wd_timer(cd); + cancel_work_sync(&cd->enum_work); + pt_stop_wd_timer(cd); + + if (IS_EASY_WAKE_CONFIGURED(cd->easy_wakeup_gesture)) { + rc = pt_put_device_into_easy_wakeup_(cd); + pt_debug(cd->dev, DL_INFO, "%s :Entering into easywakeup: rc=%d\n", __func__, rc); + if (rc) + pr_err("%s: Easy wakeup error detected :rc=%d\n", __func__, rc); + } + mutex_lock(&cd->system_lock); + cd->sleep_state = SS_EASY_WAKING_ON; + mutex_unlock(&cd->system_lock); + + return rc; +} + + +/******************************************************************************* + * FUNCTION: pt_core_easywake_on + * + * SUMMARY: Protected call to pt_core_easywake_on_ by exclusive access to the DUT. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static int pt_core_easywake_on(struct pt_core_data *cd) +{ + int rc = 0; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = pt_core_easywake_on_(cd); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + else + pt_debug(cd->dev, DL_DEBUG, "%s: pass release exclusive\n", + __func__); + + return rc; +} + + +/******************************************************************************* + * FUNCTION: pt_core_sleep + * + * SUMMARY: Protected call to pt_core_sleep_ by exclusive access to the DUT. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static int pt_core_sleep(struct pt_core_data *cd) +{ + int rc = 0; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = pt_core_sleep_(cd); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + else + pt_debug(cd->dev, DL_DEBUG, "%s: pass release exclusive\n", + __func__); + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_wakeup_host + * + * SUMMARY: Check wake up report and call the PT_ATTEN_WAKE attention list. + * + * NOTE: TSG5 EasyWake and TSG6 EasyWake use different protocol. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static int pt_wakeup_host(struct pt_core_data *cd) +{ +#ifndef EASYWAKE_TSG6 + /* TSG5 EasyWake */ + int rc = 0; + int event_id; + int size = get_unaligned_le16(&cd->input_buf[0]); + + /* Validate report */ + if (size != 4 || cd->input_buf[2] != 4) + rc = -EINVAL; + + event_id = cd->input_buf[3]; + + pt_debug(cd->dev, DL_INFO, "%s: e=%d, rc=%d\n", + __func__, event_id, rc); + + if (rc) { + pt_core_sleep_(cd); + goto exit; + } + + /* attention WAKE */ + call_atten_cb(cd, PT_ATTEN_WAKE, 0); +exit: + return rc; +#else + /* TSG6 FW1.3 EasyWake */ + int rc = 0; + int i = 0; + int report_length; + + /* Validate report */ + if (cd->input_buf[2] != PT_PIP_WAKEUP_REPORT_ID) { + rc = -EINVAL; + pt_core_sleep_(cd); + goto exit; + } + + /* Get gesture id and gesture data length */ + cd->gesture_id = cd->input_buf[3]; + report_length = (cd->input_buf[1] << 8) | (cd->input_buf[0]); + cd->gesture_data_length = report_length - 4; + + pt_debug(cd->dev, DL_INFO, + "%s: gesture_id = %d, gesture_data_length = %d\n", + __func__, cd->gesture_id, cd->gesture_data_length); + + for (i = 0; i < cd->gesture_data_length; i++) + cd->gesture_data[i] = cd->input_buf[4 + i]; + + /* attention WAKE */ + call_atten_cb(cd, PT_ATTEN_WAKE, 0); +exit: + return rc; +#endif +} + +/******************************************************************************* + * FUNCTION: pt_get_touch_axis + * + * SUMMARY: Function to calculate touch axis + * + * PARAMETERS: + * *cd - pointer to core data structure + * *axis - pointer to axis calculation result + * size - size in bytes + * max - max value of result + * *xy_data - pointer to input data to be parsed + * bofs - bit offset + ******************************************************************************/ +static void pt_get_touch_axis(struct pt_core_data *cd, + int *axis, int size, int max, u8 *data, int bofs) +{ + int nbyte; + int next; + + for (nbyte = 0, *axis = 0, next = 0; nbyte < size; nbyte++) { + *axis = *axis + ((data[next] >> bofs) << (nbyte * 8)); + next++; + } + + *axis &= max - 1; +} + +/******************************************************************************* + * FUNCTION: move_tracking_heatmap_data + * + * SUMMARY: Move the valid tracking heatmap data from the input buffer into the + * system information structure, xy_mode and xy_data. + * - If TTHE_TUNER_SUPPORT is defined print the raw sensor data into + * the tthe_tuner sysfs node under the label "THM" + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * *si - pointer to the system information structure + ******************************************************************************/ +static int move_tracking_heatmap_data(struct pt_core_data *cd, + struct pt_sysinfo *si) +{ +#ifdef TTHE_TUNER_SUPPORT + int size = get_unaligned_le16(&cd->input_buf[0]); + + if (size) + tthe_print(cd, cd->input_buf, size, "THM="); +#endif + memcpy(si->xy_mode, cd->input_buf, SENSOR_HEADER_SIZE); + return 0; +} + +/******************************************************************************* + * FUNCTION: move_sensor_data + * + * SUMMARY: Move the valid sensor data from the input buffer into the system + * information structure, xy_mode and xy_data. + * - If TTHE_TUNER_SUPPORT is defined print the raw sensor data into + * the tthe_tuner sysfs node under the label "sensor_monitor" + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * *si - pointer to the system information structure + ******************************************************************************/ +static int move_sensor_data(struct pt_core_data *cd, + struct pt_sysinfo *si) +{ +#ifdef TTHE_TUNER_SUPPORT + int size = get_unaligned_le16(&cd->input_buf[0]); + + if (size) + tthe_print(cd, cd->input_buf, size, "sensor_monitor="); +#endif + memcpy(si->xy_mode, cd->input_buf, SENSOR_HEADER_SIZE); + return 0; +} + +/******************************************************************************* + * FUNCTION: move_button_data + * + * SUMMARY: Move the valid button data from the input buffer into the system + * information structure, xy_mode and xy_data. + * - If TTHE_TUNER_SUPPORT is defined print the raw button data into + * the tthe_tuner sysfs node under the label "OpModeData" + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * *si - pointer to the system information structure + ******************************************************************************/ +static int move_button_data(struct pt_core_data *cd, + struct pt_sysinfo *si) +{ +#ifdef TTHE_TUNER_SUPPORT + int size = get_unaligned_le16(&cd->input_buf[0]); + + if (size) + tthe_print(cd, cd->input_buf, size, "OpModeData="); +#endif + memcpy(si->xy_mode, cd->input_buf, BTN_INPUT_HEADER_SIZE); + pt_pr_buf(cd->dev, DL_INFO, (u8 *)si->xy_mode, BTN_INPUT_HEADER_SIZE, + "xy_mode"); + + memcpy(si->xy_data, &cd->input_buf[BTN_INPUT_HEADER_SIZE], + BTN_REPORT_SIZE); + pt_pr_buf(cd->dev, DL_INFO, (u8 *)si->xy_data, BTN_REPORT_SIZE, + "xy_data"); + return 0; +} + +/******************************************************************************* + * FUNCTION: move_touch_data + * + * SUMMARY: Move the valid touch data from the input buffer into the system + * information structure, xy_mode and xy_data. + * - If TTHE_TUNER_SUPPORT is defined print the raw touch data into + * the tthe_tuner sysfs node under the label "OpModeData" + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * *si - pointer to the system information structure + ******************************************************************************/ +static int move_touch_data(struct pt_core_data *cd, struct pt_sysinfo *si) +{ + int max_tch = si->sensing_conf_data.max_tch; + int num_cur_tch; + int length; + struct pt_tch_abs_params *tch = &si->tch_hdr[PT_TCH_NUM]; +#ifdef TTHE_TUNER_SUPPORT + int size = get_unaligned_le16(&cd->input_buf[0]); + + if (size) + tthe_print(cd, cd->input_buf, size, "OpModeData="); +#endif + + memcpy(si->xy_mode, cd->input_buf, si->desc.tch_header_size); + pt_pr_buf(cd->dev, DL_INFO, (u8 *)si->xy_mode, + si->desc.tch_header_size, "xy_mode"); + + pt_get_touch_axis(cd, &num_cur_tch, tch->size, + tch->max, si->xy_mode + 3 + tch->ofs, tch->bofs); + if (unlikely(num_cur_tch > max_tch)) + num_cur_tch = max_tch; + + length = num_cur_tch * si->desc.tch_record_size; + + memcpy(si->xy_data, &cd->input_buf[si->desc.tch_header_size], length); + pt_pr_buf(cd->dev, DL_INFO, (u8 *)si->xy_data, length, "xy_data"); + return 0; +} + +/******************************************************************************* + * FUNCTION: move_hid_pen_data + * + * SUMMARY: TODO Move the valid pen data from the input buffer into the system + * information structure, xy_mode and xy_data. + * - If TTHE_TUNER_SUPPORT is defined print the raw pen data into + * the tthe_tuner sysfs node under the label "HID" starting with the + * report ID. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * *si - pointer to the system information structure + ******************************************************************************/ +static int move_hid_pen_data(struct pt_core_data *cd, struct pt_sysinfo *si) +{ +#ifdef TTHE_TUNER_SUPPORT + int size = get_unaligned_le16(&cd->input_buf[0]); + + if (size) { + /* + * HID over USB does not require the two byte length field, so + * this should print from input_buf[2] but to keep both finger + * and pen reports the same the length is included + */ + if (cd->tthe_hid_usb_format == PT_FEATURE_ENABLE) + tthe_print(cd, &(cd->input_buf[2]), size - 2, + "HID-USB="); + else + tthe_print(cd, &(cd->input_buf[0]), size, + "HID-I2C="); + } +#endif + pt_pr_buf(cd->dev, DL_INFO, (u8 *)&(cd->input_buf[0]), size, "HID Pen"); + return 0; +} + +/******************************************************************************* + * FUNCTION: parse_touch_input + * + * SUMMARY: Parse the touch report and take action based on the touch + * report_id. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * size - size of touch record + ******************************************************************************/ +static int parse_touch_input(struct pt_core_data *cd, int size) +{ + struct pt_sysinfo *si = &cd->sysinfo; + int report_id = cd->input_buf[2]; + int rc = -EINVAL; + + pt_debug(cd->dev, DL_DEBUG, "%s: Received touch report\n", + __func__); + if (!si->ready) { + pt_debug(cd->dev, DL_ERROR, + "%s: Need system information to parse touches\n", + __func__); + return 0; + } + + if (!si->xy_mode || !si->xy_data) + return rc; + + if (report_id == PT_PIP_TOUCH_REPORT_ID) + rc = move_touch_data(cd, si); + else if (report_id == PT_HID_PEN_REPORT_ID) + rc = move_hid_pen_data(cd, si); + else if (report_id == PT_PIP_CAPSENSE_BTN_REPORT_ID) + rc = move_button_data(cd, si); + else if (report_id == PT_PIP_SENSOR_DATA_REPORT_ID) + rc = move_sensor_data(cd, si); + else if (report_id == PT_PIP_TRACKING_HEATMAP_REPORT_ID) + rc = move_tracking_heatmap_data(cd, si); + + if (rc) + return rc; + + /* attention IRQ */ + call_atten_cb(cd, PT_ATTEN_IRQ, cd->mode); + + return 0; +} + +/******************************************************************************* + * FUNCTION: parse_command_input + * + * SUMMARY: Move the response data from the input buffer to the response buffer + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * size - size of response data + ******************************************************************************/ +static int parse_command_input(struct pt_core_data *cd, int size) +{ + pt_debug(cd->dev, DL_DEBUG, "%s: Received cmd interrupt\n", + __func__); + + memcpy(cd->response_buf, cd->input_buf, size); +#if defined(TTHE_TUNER_SUPPORT) && defined(TTDL_DIAGNOSTICS) + if (size && cd->show_tt_data) { + if (cd->pip2_prot_active) + tthe_print(cd, cd->input_buf, size, "TT_DATA_PIP2="); + else + tthe_print(cd, cd->input_buf, size, "TT_DATA="); + } +#endif + + mutex_lock(&cd->system_lock); + cd->hid_cmd_state = 0; + mutex_unlock(&cd->system_lock); + wake_up(&cd->wait_q); + + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_allow_enumeration + * + * SUMMARY: Determine if an enumeration or fully re-probe should perform when + * FW sentinel is seen. + * + * RETURN: + * true = allow enumeration or fully re-probe + * false = skip enumeration and fully re-probe + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static inline bool pt_allow_enumeration(struct pt_core_data *cd) +{ + if ((cd->panel_id_support & PT_PANEL_ID_BY_SYS_INFO) && + (!cd->hid_reset_cmd_state) && + (cd->core_probe_complete) && + (cd->hid_cmd_state != PIP1_CMD_ID_START_BOOTLOADER + 1) && + (cd->hid_cmd_state != PIP1_BL_CMD_ID_LAUNCH_APP + 1) && + (cd->mode == PT_MODE_OPERATIONAL)) { + return true; + } + + if ((!cd->hid_reset_cmd_state) && + (cd->core_probe_complete) && + (cd->hid_cmd_state != PIP1_CMD_ID_START_BOOTLOADER + 1) && + (cd->hid_cmd_state != PIP1_BL_CMD_ID_LAUNCH_APP + 1) && + (cd->active_dut_generation != DUT_PIP1_ONLY)) { + return true; + } + + pt_debug(cd->dev, DL_INFO, + "%s: Dissallow - %s=%d %s=%d %s=0x%02X %s=%d\n", + __func__, + "hid_reset_cmd_state(0)", cd->hid_reset_cmd_state, + "core_probe_complete(1)", cd->core_probe_complete, + "hid_cmd_state(Not 0x02 or 0x39)", cd->hid_cmd_state, + "active_dut_gen(0,2)", cd->active_dut_generation); + return false; +} + +/******************************************************************************* + * FUNCTION: pt_is_touch_report + * + * SUMMARY: Determine if a report ID should be treated as a touch report + * + * RETURN: + * true = report ID is a touch report + * false = report ID is not a touch report + * + * PARAMETERS: + * id - Report ID + ******************************************************************************/ +static bool pt_is_touch_report(int id) +{ + if (id == PT_PIP_TOUCH_REPORT_ID || + id == PT_HID_PEN_REPORT_ID || + id == PT_PIP_CAPSENSE_BTN_REPORT_ID || + id == PT_PIP_SENSOR_DATA_REPORT_ID || + id == PT_PIP_TRACKING_HEATMAP_REPORT_ID) + return true; + else + return false; +} + +/******************************************************************************* + * FUNCTION: pt_parse_input + * + * SUMMARY: Parse the input data read from DUT due to IRQ. Handle data based + * on if its a response to a command or asynchronous touch data / reset + * sentinel. PIP2.x messages have additional error checking that is + * parsed (SEQ match from cmd to rsp, CRC valid). + * Look for special packets based on unique lengths: + * 0 bytes - APP(FW) reset sentinel or Gen5/6 BL sentinel + * 2 bytes - Empty buffer (PIP 1.7 and earlier) + * 11 bytes - possible PIP2.x reset sentinel (TAG and SEQ must = 0) + * 0xFFXX - Empty buffer (PIP 1.7+) + * Queue a startup after any asynchronous FW reset sentinel is seen, unless + * the initial probe has not yet been done. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static int pt_parse_input(struct pt_core_data *cd) +{ + int report_id; + int cmd_id; + int is_command = 0; + int size; + int print_size; + bool touch_report = true; + unsigned short calc_crc; + unsigned short resp_crc; + + cd->fw_sys_mode_in_standby_state = false; + size = get_unaligned_le16(&cd->input_buf[0]); + print_size = size; + pt_debug(cd->dev, DL_DEBUG, "<<< %s: IRQ Triggered, read len [%d]\n", + __func__, print_size); + if (print_size <= PT_MAX_INPUT) + pt_pr_buf(cd->dev, DL_DEBUG, cd->input_buf, print_size, + "<<< Read buf"); + if (size == 0 || + (size == 11 && + (cd->input_buf[PIP2_RESP_SEQUENCE_OFFSET] & + PIP2_RESP_SEQUENCE_MASK) == 0 && + (cd->input_buf[PIP2_RESP_REPORT_ID_OFFSET] & + PIP2_CMD_COMMAND_ID_MASK) == + PIP2_CMD_ID_STATUS)) { + touch_report = false; + cd->hw_detected = true; + cd->bl_pip_ver_ready = false; + cd->app_pip_ver_ready = false; + if (size == 0) { + mutex_lock(&cd->system_lock); + cd->pip2_prot_active = false; + if (cd->active_dut_generation == DUT_PIP1_ONLY) { + /* + * For Gen5/6 this sentinel could be from + * the BL or FW. Attempt to set the correct + * mode based on the previous PIP command. + */ + if (cd->hid_cmd_state == + PIP1_BL_CMD_ID_LAUNCH_APP + 1) { + cd->mode = PT_MODE_OPERATIONAL; + cd->startup_status = + STARTUP_STATUS_FW_RESET_SENTINEL; + } else if (cd->hid_cmd_state == + PIP1_CMD_ID_START_BOOTLOADER + 1 || + cd->hid_reset_cmd_state) { + cd->mode = PT_MODE_BOOTLOADER; + cd->startup_status = + STARTUP_STATUS_BL_RESET_SENTINEL; + } else { + cd->mode = PT_MODE_UNKNOWN; + cd->startup_status = + STARTUP_STATUS_START; + } + cd->fw_system_mode = FW_SYS_MODE_UNDEFINED; + pt_debug(cd->dev, DL_INFO, + "%s: ATM Gen5/6 %s sentinel received\n", + __func__, + (cd->mode == PT_MODE_OPERATIONAL ? + "FW" : + (cd->mode == PT_MODE_BOOTLOADER ? + "BL" : "Unknown"))); + } else { + cd->mode = PT_MODE_OPERATIONAL; + cd->fw_system_mode = FW_SYS_MODE_BOOT; + cd->startup_status = + STARTUP_STATUS_FW_RESET_SENTINEL; + pt_debug(cd->dev, DL_INFO, + "%s: ATM PT/TT FW sentinel received\n", + __func__); + } + mutex_unlock(&cd->system_lock); + if (pt_allow_enumeration(cd)) { + if (cd->active_dut_generation == DUT_UNKNOWN) { + pt_debug(cd->dev, DL_INFO, + "%s: Queue Restart\n", __func__); + pt_queue_restart(cd); + } else { + pt_debug(cd->dev, DL_INFO, + "%s: Queue Enum\n", __func__); + pt_queue_enum(cd); + } + } else { + pt_debug(cd->dev, DL_INFO, + "%s: Sentinel - No Queued Action\n", + __func__); + } + + } else { + /* Sentinel must be from TT/TC BL */ + mutex_lock(&cd->system_lock); + cd->pip2_prot_active = true; + cd->startup_status = STARTUP_STATUS_BL_RESET_SENTINEL; + cd->mode = PT_MODE_BOOTLOADER; + cd->sysinfo.ready = false; + mutex_unlock(&cd->system_lock); + pt_debug(cd->dev, DL_INFO, + "%s: BL Reset sentinel received\n", __func__); + if (cd->flashless_dut && + cd->flashless_auto_bl == PT_ALLOW_AUTO_BL) { + pt_debug(cd->dev, DL_INFO, + "%s: BL to RAM for flashless DUT\n", + __func__); + kthread_run(start_fw_upgrade, cd, "pt_loader"); + } + } + mutex_lock(&cd->system_lock); + memcpy(cd->response_buf, cd->input_buf, 2); + if (!cd->hid_reset_cmd_state && !cd->hid_cmd_state) { + mutex_unlock(&cd->system_lock); + pt_debug(cd->dev, DL_WARN, + "%s: Device Initiated Reset\n", __func__); + wake_up(&cd->wait_q); + return 0; + } + + cd->hid_reset_cmd_state = 0; + if (cd->hid_cmd_state == PIP1_CMD_ID_START_BOOTLOADER + 1 || + cd->hid_cmd_state == PIP1_BL_CMD_ID_LAUNCH_APP + 1 || + cd->hid_cmd_state == PIP1_CMD_ID_USER_CMD + 1) + cd->hid_cmd_state = 0; + wake_up(&cd->wait_q); + mutex_unlock(&cd->system_lock); + return 0; + } else if (size == 2 || size >= PT_PIP_1P7_EMPTY_BUF) { + /* + * This debug message below is used by PBATS to calculate the + * time from the last lift off IRQ to when FW exits LFT mode. + */ + touch_report = false; + pt_debug(cd->dev, DL_WARN, + "%s: DUT - Empty buffer detected\n", __func__); + return 0; + } else if (size > PT_MAX_INPUT || size < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: DUT - Unexpected len field in active bus data!\n", + __func__); + return -EINVAL; + } + + if (cd->pip2_prot_active) { + pt_debug(cd->dev, DL_DEBUG, + "%s: Decode PIP2.x Response\n", __func__); + + /* PIP2 does not have a report id, hard code it */ + report_id = 0x00; + cmd_id = cd->input_buf[PIP2_RESP_COMMAND_ID_OFFSET]; + calc_crc = crc_ccitt_calculate(cd->input_buf, size - 2); + resp_crc = cd->input_buf[size - 2] << 8; + resp_crc |= cd->input_buf[size - 1]; + if ((cd->pip2_cmd_tag_seq != + (cd->input_buf[PIP2_RESP_SEQUENCE_OFFSET] & 0x0F)) && + (resp_crc != calc_crc) && + ((cd->input_buf[PIP1_RESP_REPORT_ID_OFFSET] + == PT_PIP_TOUCH_REPORT_ID) || + (cd->input_buf[PIP1_RESP_REPORT_ID_OFFSET] + == PT_PIP_CAPSENSE_BTN_REPORT_ID))) { + pt_debug(cd->dev, DL_WARN, + "%s: %s %d %s\n", + __func__, + "Received PIP1 report id =", + cd->input_buf[PIP1_RESP_REPORT_ID_OFFSET], + "when expecting a PIP2 report - IGNORE report"); + return 0; + } + is_command = 1; + touch_report = false; + } else { + report_id = cd->input_buf[PIP1_RESP_REPORT_ID_OFFSET]; + cmd_id = cd->input_buf[PIP1_RESP_COMMAND_ID_OFFSET]; + } + +#ifdef TTDL_DIAGNOSTICS + pt_debug(cd->dev, DL_DEBUG, + "%s: pip2 = %d report_id: 0x%02X, cmd_code: 0x%02X\n", + __func__, cd->pip2_prot_active, report_id, + (cmd_id & PIP2_CMD_COMMAND_ID_MASK)); +#endif /* TTDL_DIAGNOSTICS */ + + if (report_id == PT_PIP_WAKEUP_REPORT_ID) { + pt_wakeup_host(cd); +#ifdef TTHE_TUNER_SUPPORT + tthe_print(cd, cd->input_buf, size, "TT_WAKEUP="); +#endif + return 0; + } + mod_timer_pending(&cd->watchdog_timer, jiffies + + msecs_to_jiffies(cd->watchdog_interval)); + + /* + * If it is PIP2 response, the report_id has been set to 0, + * so it will not be parsed as a touch packet. + */ + if (!pt_is_touch_report(report_id)) { + is_command = 1; + touch_report = false; + } + if (unlikely(is_command)) { + parse_command_input(cd, size); + return 0; + } + if (touch_report) + parse_touch_input(cd, size); + + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_read_input + * + * SUMMARY: Reads incoming data off of the active bus + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static int pt_read_input(struct pt_core_data *cd) +{ + struct device *dev = cd->dev; + int rc = 0; + int t; + int retry = PT_BUS_READ_INPUT_RETRY_COUNT; + + /* + * Workaround for easywake failure + * Interrupt for easywake, wait for bus controller to wake + */ + mutex_lock(&cd->system_lock); + + if (IS_EASY_WAKE_CONFIGURED(cd->easy_wakeup_gesture)) { + if (cd->sleep_state == SS_SLEEP_ON || cd->sleep_state == SS_EASY_WAKING_ON) { + mutex_unlock(&cd->system_lock); + if (!dev->power.is_suspended) + goto read; + t = wait_event_timeout(cd->wait_q, + (cd->wait_until_wake == 1), + msecs_to_jiffies(2000)); +#ifdef TTDL_DIAGNOSTICS + if (IS_TMO(t)) { + cd->bus_transmit_error_count++; + pt_toggle_err_gpio(cd, PT_ERR_GPIO_I2C_TRANS); + pt_debug(dev, DL_ERROR, + "%s: !!!I2C Transmission Error %d\n", + __func__, + cd->bus_transmit_error_count); + } +#else + if (IS_TMO(t)) + pt_queue_enum(cd); +#endif /* TTDL_DIAGNOSTICS */ + goto read; + } + } + mutex_unlock(&cd->system_lock); + +read: + /* Try reading up to 'retry' times */ + while (retry-- != 0) { + rc = pt_adap_read_default_nosize(cd, cd->input_buf, + PT_MAX_INPUT); + if (!rc) { + pt_debug(dev, DL_DEBUG, + "%s: Read input successfully\n", __func__); + goto read_exit; + } + usleep_range(1000, 2000); + } + pt_debug(dev, DL_ERROR, + "%s: Error getting report, rc=%d\n", __func__, rc); + +read_exit: + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_irq + * + * SUMMARY: Process all detected interrupts + * + * RETURN: + * IRQ_HANDLED - Finished processing the interrupt + * + * PARAMETERS: + * irq - IRQ number + * *handle - pointer to core data struct + ******************************************************************************/ +irqreturn_t pt_irq(int irq, void *handle) +{ + struct pt_core_data *cd = handle; + int rc = 0; + + if (!pt_check_irq_asserted(cd)) + return IRQ_HANDLED; + + rc = pt_read_input(cd); +#ifdef TTDL_DIAGNOSTICS + cd->irq_count++; + + /* Used to calculate T-Refresh */ + if (cd->t_refresh_active) { + if (cd->t_refresh_count == 0) { + cd->t_refresh_time = jiffies; + cd->t_refresh_count++; + } else if (cd->t_refresh_count < cd->t_refresh_total) { + cd->t_refresh_count++; + } else { + cd->t_refresh_active = 0; + cd->t_refresh_time = + jiffies_to_msecs(jiffies - cd->t_refresh_time); + } + } +#endif /* TTDL_DIAGNOSTICS */ + + if (!rc) + pt_parse_input(cd); + + return IRQ_HANDLED; +} + + +/******************************************************************************* + * FUNCTION: _pt_subscribe_attention + * + * SUMMARY: Function pointer included in core_cmds to allow other modules + * to subscribe themselves into the TTDL attention list + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * type - attention type enum + * *id - ID of the module calling this function + * *func - callback function pointer to be called when notified + * mode - attention mode + ******************************************************************************/ +int _pt_subscribe_attention(struct device *dev, + enum pt_atten_type type, char *id, int (*func)(struct device *), + int mode) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct atten_node *atten, *atten_new; + + atten_new = kzalloc(sizeof(*atten_new), GFP_KERNEL); + if (!atten_new) + return -ENOMEM; + + pt_debug(cd->dev, DL_INFO, "%s from '%s'\n", __func__, + dev_name(cd->dev)); + + spin_lock(&cd->spinlock); + list_for_each_entry(atten, &cd->atten_list[type], node) { + if (atten->id == id && atten->mode == mode) { + spin_unlock(&cd->spinlock); + kfree(atten_new); + pt_debug(cd->dev, DL_INFO, "%s: %s=%p %s=%d\n", + __func__, + "already subscribed attention", + dev, "mode", mode); + + return 0; + } + } + + atten_new->id = id; + atten_new->dev = dev; + atten_new->mode = mode; + atten_new->func = func; + + list_add(&atten_new->node, &cd->atten_list[type]); + spin_unlock(&cd->spinlock); + + return 0; +} + +/******************************************************************************* + * FUNCTION: _pt_unsubscribe_attention + * + * SUMMARY: Function pointer included in core_cmds to allow other modules + * to unsubscribe themselves from the TTDL attention list + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * type - attention type enum + * *id - ID of the module calling this function + * *func - function pointer + * mode - attention mode + ******************************************************************************/ +int _pt_unsubscribe_attention(struct device *dev, + enum pt_atten_type type, char *id, int (*func)(struct device *), + int mode) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct atten_node *atten, *atten_n; + + spin_lock(&cd->spinlock); + list_for_each_entry_safe(atten, atten_n, &cd->atten_list[type], node) { + if (atten->id == id && atten->mode == mode) { + list_del(&atten->node); + spin_unlock(&cd->spinlock); + pt_debug(cd->dev, DL_DEBUG, "%s: %s=%p %s=%d\n", + __func__, + "unsub for atten->dev", atten->dev, + "atten->mode", atten->mode); + kfree(atten); + return 0; + } + } + spin_unlock(&cd->spinlock); + + return -ENODEV; +} + +/******************************************************************************* + * FUNCTION: _pt_request_exclusive + * + * SUMMARY: Function pointer included in core_cmds to allow other modules + * to request exclusive access + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * timeout_ms - timeout to wait for exclusive access + ******************************************************************************/ +static int _pt_request_exclusive(struct device *dev, + int timeout_ms) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + return request_exclusive(cd, (void *)dev, timeout_ms); +} + +/******************************************************************************* + * FUNCTION: _pt_release_exclusive + * + * SUMMARY: Function pointer included in core_cmds to allow other modules + * to release exclusive access + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int _pt_release_exclusive(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + return release_exclusive(cd, (void *)dev); +} + +/******************************************************************************* + * FUNCTION: _pt_request_reset + * + * SUMMARY: Function pointer included in core_cmds to allow other modules + * to request the DUT to be reset. Function returns err if refused or + * timeout occurs (Note: core uses fixed timeout period). + * + * NOTE: Function blocks until ISR occurs. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * protect - flag to call protected or non-protected + ******************************************************************************/ +static int _pt_request_reset(struct device *dev, int protect) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + int rc; + + rc = pt_dut_reset(cd, protect); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, "%s: Error on h/w reset r=%d\n", + __func__, rc); + } + + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_request_enum + * + * SUMMARY: Function pointer included in core_cmds to allow other modules + * to request TTDL to queue an enum. This function will return err + * if refused; if no error then enum has completed and system is in + * normal operation mode. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * wait - boolean to determine if to wait for startup event + ******************************************************************************/ +static int _pt_request_enum(struct device *dev, bool wait) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + pt_queue_enum(cd); + + if (wait) + wait_event_timeout(cd->wait_q, + cd->startup_state == STARTUP_NONE, + msecs_to_jiffies(PT_REQUEST_ENUM_TIMEOUT)); + + return 0; +} + +/******************************************************************************* + * FUNCTION: _pt_request_sysinfo + * + * SUMMARY: Function pointer included in core_cmds to allow other modules + * to request the pointer to the system information structure. This + * function will return NULL if sysinfo has not been acquired from the + * DUT yet. + * + * RETURN: Pointer to the system information struct + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +struct pt_sysinfo *_pt_request_sysinfo(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + int rc = 0; + + /* Attempt to get sysinfo if not ready and panel_id is from XY pin */ + if ((cd->panel_id_support & PT_PANEL_ID_BY_SYS_INFO) && + !cd->sysinfo.ready) { + rc = pt_hid_output_get_sysinfo_(cd); + if (rc) { + pt_debug(cd->dev, DL_ERROR, + "%s: Error getting sysinfo rc=%d\n", + __func__, rc); + } + } + + if (cd->sysinfo.ready) + return &cd->sysinfo; + + return NULL; +} + +/******************************************************************************* + * FUNCTION: _pt_request_loader_pdata + * + * SUMMARY: Function pointer included in core_cmds to allow other modules + * to request the pointer to the loader platform data + * + * RETURN: Pointer to the loader platform data struct + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static struct pt_loader_platform_data *_pt_request_loader_pdata( + struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + return cd->pdata->loader_pdata; +} + +/******************************************************************************* + * FUNCTION: _pt_request_start_wd + * + * SUMMARY: Function pointer included in core_cmds to allow other modules + * to request to start the TTDL watchdog + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int _pt_request_start_wd(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + pt_start_wd_timer(cd); + return 0; +} + +/******************************************************************************* + * FUNCTION: _pt_request_stop_wd + * + * SUMMARY: Function pointer included in core_cmds to allow other modules + * to request to stop the TTDL watchdog + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int _pt_request_stop_wd(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + pt_stop_wd_timer(cd); + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_pip2_launch_app + * + * SUMMARY: Sends the PIP2 EXECUTE command to launch the APP and then wait for + * the FW reset sentinel to indicate the function succeeded. + * + * NOTE: Calling this function when the DUT is in Application mode WILL result + * in a timeout delay and with the DUT being reset with an XRES. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data structure + * protect - flag to call protected or non-protected + ******************************************************************************/ +static int pt_pip2_launch_app(struct device *dev, int protect) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + u16 actual_read_len; + u16 tmp_startup_status = cd->startup_status; + u8 read_buf[12]; + u8 status; + int time = 0; + int rc = 0; + + mutex_lock(&cd->system_lock); + cd->startup_status = STARTUP_STATUS_START; + pt_debug(dev, DL_DEBUG, "%s: Startup Status Reset\n", __func__); + mutex_unlock(&cd->system_lock); + + rc = _pt_request_pip2_send_cmd(dev, protect, + PIP2_CMD_ID_EXECUTE, NULL, 0, read_buf, + &actual_read_len); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: PIP2 EXECUTE cmd failed rc = %d\n", + __func__, rc); + } else { + status = read_buf[PIP2_RESP_STATUS_OFFSET]; + + /* Test for no or invalid image in FLASH, no point to reset */ + if (status == PIP2_RSP_ERR_INVALID_IMAGE) { + rc = status; + goto exit; + } + + /* Any other boot failure */ + if (status != 0) { + pt_debug(dev, DL_ERROR, + "%s: FW did not EXECUTE, status = %d\n", + __func__, status); + rc = status; + } + } + + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Failed to launch APP, XRES DUT rc = %d\n", + __func__, rc); + goto exit; + } + + while ((cd->startup_status == STARTUP_STATUS_START) && time < 240) { + msleep(20); + pt_debug(cd->dev, DL_INFO, "%s: wait for %d for enum=0x%04X\n", + __func__, time, cd->startup_status); + time += 20; + } + if (cd->startup_status == STARTUP_STATUS_START) { + pt_debug(cd->dev, DL_WARN, + "%s: TMO waiting for FW reset sentinel\n", __func__); + rc = -ETIME; + } + +exit: + if (cd->startup_status == STARTUP_STATUS_START) { + /* Reset to original state because we could be stuck in BL */ + mutex_lock(&cd->system_lock); + cd->startup_status = tmp_startup_status; + mutex_unlock(&cd->system_lock); + } + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_request_pip2_launch_app + * + * SUMMARY: Calls pt_pip2_launch_app() when configured to. A small delay is + * inserted to ensure the reset has allowed the BL reset sentinel to be + * consumed. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data structure + ******************************************************************************/ +static int _pt_request_pip2_launch_app(struct device *dev, int protect) +{ + return pt_pip2_launch_app(dev, protect); +} + +/******************************************************************************* + * FUNCTION: _pt_request_wait_for_enum_state + * + * SUMMARY: Loops for up to timeout waiting for the startup_status to reach + * the state passed in or STARTUP_STATUS_COMPLETE whichever comes first + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data structure + * timeout - timeout for how long to wait + * state - enum state to wait for + ******************************************************************************/ +static int _pt_request_wait_for_enum_state(struct device *dev, int timeout, + int state) +{ + int rc = 0; + int t; + struct pt_core_data *cd = dev_get_drvdata(dev); + + t = wait_event_timeout(cd->wait_q, + (cd->startup_status & state) || (cd->startup_status & 0x0100), + msecs_to_jiffies(timeout)); + if (IS_TMO(t)) { + pt_debug(cd->dev, DL_ERROR, + "%s: TMO waiting for enum state 0x%04X in %dms\n", + __func__, state, timeout); + pt_debug(cd->dev, DL_WARN, + "%s: enum state reached 0x%04X\n", + __func__, cd->startup_status); + rc = -ETIME; + } else if (cd->startup_status & state) { + pt_debug(cd->dev, DL_INFO, + "%s: Enum state reached: enum=0x%04X in %dms\n", + __func__, cd->startup_status, + (t == 1) ? timeout : (timeout - jiffies_to_msecs(t))); + } else { + if (t == 1) { + pt_debug( + cd->dev, DL_ERROR, + "%s: TMO waiting for enum state 0x%04X in %dms\n", + __func__, state, timeout); + rc = -ETIME; + } else { + pt_debug( + cd->dev, DL_ERROR, + "%s: Enum state 0x%04X not reached in %dms\n", + __func__, state, timeout - jiffies_to_msecs(t)); + rc = -EINVAL; + } + pt_debug(cd->dev, DL_INFO, "%s: enum state reached 0x%04X\n", + __func__, cd->startup_status); + } + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_core_wake_device_from_deep_sleep_ + * + * SUMMARY: Call the set_power function and set the DUT to wake up from + * deep sleep. + * + * RETURN: + * 0 = success + * !0 = error + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static int pt_core_wake_device_from_deep_sleep_( + struct pt_core_data *cd) +{ + int rc; + + rc = pt_hid_cmd_set_power_(cd, HID_POWER_ON); + if (rc) + rc = -EAGAIN; + + /* Prevent failure on sequential wake/sleep requests from OS */ + msleep(20); + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_core_wake_device_from_easy_wake_ + * + * SUMMARY: Wake up device from Easy-Wake state. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static int pt_core_wake_device_from_easy_wake_(struct pt_core_data *cd) +{ + int rc = 0; + + + rc = pt_core_wake_device_from_deep_sleep_(cd); + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_restore_parameters_ + * + * SUMMARY: This function sends all RAM parameters stored in the linked list + * back to the DUT + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer the core data structure + ******************************************************************************/ +static int pt_restore_parameters_(struct pt_core_data *cd) +{ + struct param_node *param; + int rc = 0; + + if (!(cd->cpdata->flags & PT_CORE_FLAG_RESTORE_PARAMETERS)) + goto exit; + + spin_lock(&cd->spinlock); + list_for_each_entry(param, &cd->param_list, node) { + spin_unlock(&cd->spinlock); + pt_debug(cd->dev, DL_INFO, "%s: Parameter id:%d value:%d\n", + __func__, param->id, param->value); + rc = pt_pip_set_param_(cd, param->id, + param->value, param->size); + if (rc) + goto exit; + spin_lock(&cd->spinlock); + } + spin_unlock(&cd->spinlock); +exit: + return rc; +} + + +/******************************************************************************* + * FUNCTION: pt_pip2_exit_bl_ + * + * SUMMARY: Attempt to exit the BL and run the application, taking into account + * a DUT that may not have flash and will need FW to be loaded into RAM + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer the core data structure + * *status_str - pointer to optional status string buffer + * buf_size - size of status_str buffer + ******************************************************************************/ +int pt_pip2_exit_bl_(struct pt_core_data *cd, u8 *status_str, int buf_size) +{ + int rc; + int wait_time = 0; + u8 mode = PT_MODE_UNKNOWN; + bool load_status_str = false; + + /* + * Below function has protective call to ensure no enum is still on + * going, while this kind of protection should be applied widely in + * future (TODO). + */ + rc = pt_pip2_get_mode_sysmode(cd, &mode, NULL); + + if (status_str && buf_size <= 50) + load_status_str = true; + + if (mode == PT_MODE_BOOTLOADER) { + if (cd->flashless_dut == 1) { + rc = pt_hw_hard_reset(cd); + } else { + rc = pt_pip2_launch_app(cd->dev, + PT_CORE_CMD_UNPROTECTED); + if (rc == PIP2_RSP_ERR_INVALID_IMAGE) { + pt_debug(cd->dev, DL_ERROR, "%s: %s = %d\n", + __func__, "Invalid image in FLASH rc", rc); + } else if (rc) { + pt_debug(cd->dev, DL_ERROR, "%s: %s = %d\n", + __func__, "Failed to launch app rc", rc); + } + } + + if (!rc) { + if (cd->flashless_dut == 1) { + /* Wait for BL to complete before enum */ + rc = _pt_request_wait_for_enum_state(cd->dev, + 4000, STARTUP_STATUS_FW_RESET_SENTINEL); + if (rc && load_status_str) { + strlcpy(status_str, "No FW sentinel after BL", + sizeof(*status_str)*PT_STATUS_STR_LEN); + goto exit; + } + } + + /* + * If the host wants to interact with the FW or do a + * forced calibration, the FW must be out of BOOT mode + * and the system information must have been retrieved. + * Reaching the FW_OUT_OF_BOOT state guarantees both. + * If, however, the enumeration does not reach this + * point, the DUT may still be in APP mode so test + * for all conditions. + */ + rc = _pt_request_wait_for_enum_state(cd->dev, 4500, + STARTUP_STATUS_FW_OUT_OF_BOOT); + if (!rc || cd->startup_status >= + STARTUP_STATUS_FW_RESET_SENTINEL) { + mutex_lock(&cd->system_lock); + cd->mode = PT_MODE_OPERATIONAL; + mutex_unlock(&cd->system_lock); + } + if (rc) { + pt_debug(cd->dev, DL_WARN, "%s: %s: 0x%04X\n", + __func__, "Failed to enum with DUT", + cd->startup_status); + if (load_status_str && !(cd->startup_status & + STARTUP_STATUS_FW_OUT_OF_BOOT)) { + strlcpy(status_str, "FW Stuck in Boot mode", + sizeof(*status_str)*PT_STATUS_STR_LEN); + goto exit; + } + } + + /* + * The coming FW sentinel could wake up the event + * queue, which has chance to be taken by next command + * wrongly. Following delay is a workaround to avoid + * this issue for most situations. + */ + msleep(20); + + pt_start_wd_timer(cd); + } + if (load_status_str) { + if (rc == PIP2_RSP_ERR_INVALID_IMAGE) + strlcpy(status_str, "Failed - Invalid image in FLASH", + sizeof(*status_str)*PT_STATUS_STR_LEN); + else if (!rc) + strlcpy(status_str, "Entered APP from BL mode", + sizeof(*status_str)*PT_STATUS_STR_LEN); + else + strlcpy(status_str, "Failed to enter APP from BL mode", + sizeof(*status_str)*PT_STATUS_STR_LEN); + } + } else if (mode == PT_MODE_OPERATIONAL) { + mutex_lock(&cd->system_lock); + cd->mode = mode; + mutex_unlock(&cd->system_lock); + rc = pt_poll_for_fw_exit_boot_mode(cd, 1500, &wait_time); + if (load_status_str) { + if (!rc) + strlcpy(status_str, "Already in APP mode", + sizeof(*status_str)*PT_STATUS_STR_LEN); + else + strlcpy(status_str, "Already in APP mode - FW stuck in Boot mode", + sizeof(*status_str)*PT_STATUS_STR_LEN); + } + } else if (rc || mode == PT_MODE_UNKNOWN) { + mutex_lock(&cd->system_lock); + cd->mode = mode; + mutex_unlock(&cd->system_lock); + if (load_status_str) + strlcpy(status_str, "Failed to determine active mode", + sizeof(*status_str)*PT_STATUS_STR_LEN); + } + +exit: + if (!rc) + pt_start_wd_timer(cd); + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_pip2_exit_bl + * + * SUMMARY: Wrapper function for _pt_pip2_exit_bl that guarantees exclusive + * access. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer the core data structure + * *status_str - pointer to optional status string buffer + * buf_size - size of status_str buffer + ******************************************************************************/ +int pt_pip2_exit_bl(struct pt_core_data *cd, u8 *status_str, int buf_size) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", __func__, + cd->exclusive_dev, cd->dev); + return rc; + } + + rc = pt_pip2_exit_bl_(cd, status_str, buf_size); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, "%s: fail to release exclusive\n", + __func__); + + return rc; +} + +/******************************************************************************* + * FUNCTION: _fast_startup + * + * SUMMARY: Perform fast startup after resume device by power on/off stratergy. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer the core data structure + ******************************************************************************/ +static int _fast_startup(struct pt_core_data *cd) +{ + int retry = PT_CORE_STARTUP_RETRY_COUNT; + int rc = 0; + u8 mode = PT_MODE_UNKNOWN; + struct pt_hid_desc hid_desc; + int wait_time = 0; + + memset(&hid_desc, 0, sizeof(hid_desc)); +reset: + if (retry != PT_CORE_STARTUP_RETRY_COUNT) + pt_debug(cd->dev, DL_INFO, "%s: Retry %d\n", __func__, + PT_CORE_STARTUP_RETRY_COUNT - retry); + + if (cd->active_dut_generation == DUT_PIP1_ONLY) { + pt_debug(cd->dev, DL_INFO, "%s: PIP1 Enumeration start\n", + __func__); + pt_flush_bus_if_irq_asserted(cd, PT_FLUSH_BUS_BASED_ON_LEN); + + rc = pt_get_hid_descriptor_(cd, &hid_desc); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: Error on getting HID descriptor r=%d\n", + __func__, rc); + if (retry--) + goto reset; + goto exit; + } + cd->mode = pt_get_mode(cd, &hid_desc); + + if (cd->mode == PT_MODE_BOOTLOADER) { + pt_debug(cd->dev, DL_INFO, "%s: Bootloader mode\n", + __func__); + rc = pt_hid_output_bl_launch_app_(cd); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: Error on launch app r=%d\n", + __func__, rc); + if (retry--) + goto reset; + goto exit; + } + rc = pt_get_hid_descriptor_(cd, &hid_desc); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: Error on getting HID descriptor r=%d\n", + __func__, rc); + if (retry--) + goto reset; + goto exit; + } + cd->mode = pt_get_mode(cd, &hid_desc); + if (cd->mode == PT_MODE_BOOTLOADER) { + if (retry--) + goto reset; + goto exit; + } + } + + cd->startup_status |= STARTUP_STATUS_GET_DESC; + cd->startup_status |= STARTUP_STATUS_FW_OUT_OF_BOOT; + } else { + pt_debug(cd->dev, DL_INFO, "%s: PIP2 Enumeration start\n", + __func__); + + if (retry == PT_CORE_STARTUP_RETRY_COUNT) { + /* Wait for any sentinel before first try */ + rc = _pt_request_wait_for_enum_state( + cd->dev, 150, + STARTUP_STATUS_BL_RESET_SENTINEL | + STARTUP_STATUS_FW_RESET_SENTINEL); + if (rc) + pt_debug(cd->dev, DL_ERROR, + "%s: No Sentinel detected rc = %d\n", + __func__, rc); + } else + pt_flush_bus_if_irq_asserted(cd, + PT_FLUSH_BUS_BASED_ON_LEN); + + rc = pt_pip2_get_mode_sysmode_(cd, &mode, NULL); + if (rc) { + pt_debug(cd->dev, DL_ERROR, + "%s: Get mode failed, mode unknown\n", + __func__); + mode = PT_MODE_UNKNOWN; + } + cd->mode = mode; + + if (cd->mode == PT_MODE_BOOTLOADER) { + pt_debug(cd->dev, DL_INFO, "%s: Bootloader mode\n", + __func__); + rc = pt_pip2_exit_bl_(cd, NULL, 0); + if (rc) { + pt_debug(cd->dev, DL_ERROR, + "%s Failed to exit bootloader\n", + __func__); + msleep(50); + rc = -ENODEV; + if (retry--) + goto reset; + goto exit; + } else { + pt_debug(cd->dev, DL_INFO, + "%s: Exit bootloader successfully\n", + __func__); + } + + if (cd->mode != PT_MODE_OPERATIONAL) { + pt_debug(cd->dev, DL_WARN, + "%s: restore mode failure mode = %d\n", + __func__, cd->mode); + if (retry--) + goto reset; + goto exit; + } + } + cd->startup_status |= STARTUP_STATUS_GET_DESC; + } + + /* FW cannot handle most PIP cmds when it is still in BOOT mode */ + rc = _pt_poll_for_fw_exit_boot_mode(cd, 500, &wait_time); + if (!rc) { + cd->startup_status |= STARTUP_STATUS_FW_OUT_OF_BOOT; + pt_debug(cd->dev, DL_WARN, + "%s: Exit FW BOOT Mode after %dms\n", + __func__, wait_time); + } else { + pt_debug(cd->dev, DL_WARN, + "%s: FW stuck in BOOT Mode after %dms\n", + __func__, wait_time); + goto exit; + } + + if (!cd->sysinfo.ready) { + rc = pt_hid_output_get_sysinfo_(cd); + if (rc) { + pt_debug(cd->dev, DL_ERROR, + "%s: Error on getting sysinfo r=%d\n", + __func__, rc); + if (retry--) + goto reset; + goto exit; + } + } + cd->startup_status |= STARTUP_STATUS_GET_SYS_INFO; + + rc = pt_restore_parameters_(cd); + if (rc) + pt_debug(cd->dev, DL_ERROR, + "%s: failed to restore parameters rc=%d\n", + __func__, rc); + else + cd->startup_status |= STARTUP_STATUS_RESTORE_PARM; + +exit: + cd->startup_status |= STARTUP_STATUS_COMPLETE; + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_core_poweron_device_ + * + * SUMMARY: Power on device, enable IRQ, and then perform a fast startup. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static int pt_core_poweron_device_(struct pt_core_data *cd) +{ + struct device *dev = cd->dev; + int rc = 0; + + /* + * After power on action, the chip can general FW sentinel. It can + * trigger an enumeration without hid_reset_cmd_state flag. Since the + * _fast_startup() can perform a quick enumeration too, here doesn't + * need another enumeration. + */ + mutex_lock(&cd->system_lock); + cd->startup_status = STARTUP_STATUS_START; + cd->hid_reset_cmd_state = 1; + mutex_unlock(&cd->system_lock); + + rc = cd->cpdata->power(cd->cpdata, 1, dev, 0); + if (rc < 0) { + pt_debug(dev, DL_ERROR, "%s: HW Power up fails r=%d\n", + __func__, rc); + goto exit; + } + + if (!cd->irq_enabled) { + cd->irq_enabled = true; + enable_irq(cd->irq); + } + + /* TBD: following function doesn't update startup_status */ + rc = _fast_startup(cd); +exit: + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_core_wake_device_from_deep_standby_ + * + * SUMMARY: Reset device, and then trigger a full enumeration. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static int pt_core_wake_device_from_deep_standby_(struct pt_core_data *cd) +{ + int rc; + + rc = pt_dut_reset_and_wait(cd); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, "%s: Error on h/w reset r=%d\n", + __func__, rc); + goto exit; + } + + rc = _fast_startup(cd); +exit: + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_core_easywake_off_ + * + * SUMMARY: Resume the device with a power on or wake from deep sleep based on + * the configuration in the core platform data structure. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static int pt_core_easywake_off_(struct pt_core_data *cd) +{ + int rc = 0; + + mutex_lock(&cd->system_lock); + if (cd->sleep_state == SS_EASY_WAKING_OFF) { + mutex_unlock(&cd->system_lock); + pt_debug(cd->dev, DL_INFO, + "%s - %d skip wakeoff\n", __func__, cd->sleep_state); + return 0; + } + mutex_unlock(&cd->system_lock); + + if (!(cd->cpdata->flags & PT_CORE_FLAG_SKIP_RESUME)) { + if (IS_EASY_WAKE_CONFIGURED(cd->easy_wakeup_gesture)) + rc = pt_core_wake_device_from_easy_wake_(cd); + if (rc < 0) + pt_debug(cd->dev, DL_ERROR, + "%s - %d failed %d\n", __func__, rc); + } + + mutex_lock(&cd->system_lock); + cd->sleep_state = SS_EASY_WAKING_OFF; + mutex_unlock(&cd->system_lock); + pt_start_wd_timer(cd); + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_core_easywake_off + * + * SUMMARY: Protected call to pt_core_easywake_off by exclusive access to the DUT. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static int pt_core_easywake_off(struct pt_core_data *cd) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = pt_core_easywake_off_(cd); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, "%s: fail to release exclusive\n", + __func__); + else + pt_debug(cd->dev, DL_DEBUG, "%s: pass release exclusive\n", + __func__); + + return rc; +} + + +/******************************************************************************* + * FUNCTION: pt_core_wake_ + * + * SUMMARY: Resume the device with a power on or wake from deep sleep based on + * the configuration in the core platform data structure. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static int pt_core_wake_(struct pt_core_data *cd) +{ + int rc = 0; + + mutex_lock(&cd->system_lock); + + if (cd->sleep_state == SS_SLEEP_OFF || cd->sleep_state == SS_WAKING) { + mutex_unlock(&cd->system_lock); + pt_debug(cd->dev, DL_INFO, + "%s - skip wake sleep state %d\n", __func__, cd->sleep_state); + return 0; + } else { + cd->sleep_state = SS_WAKING; + } + mutex_unlock(&cd->system_lock); + + if (!(cd->cpdata->flags & PT_CORE_FLAG_SKIP_RESUME)) { + if (cd->cpdata->flags & PT_CORE_FLAG_POWEROFF_ON_SLEEP) { + pt_debug(cd->dev, DL_INFO, + "%s: Entering into poweron mode:\n", __func__); + rc = pt_core_poweron_device_(cd); + if (rc < 0) + pr_err("%s: Poweron error detected: rc=%d\n", + __func__, rc); + } + else if (cd->cpdata->flags & PT_CORE_FLAG_DEEP_STANDBY) + rc = pt_core_wake_device_from_deep_standby_(cd); + else /* Default action to exit DeepSleep */ + rc = pt_core_wake_device_from_deep_sleep_(cd); + } + + mutex_lock(&cd->system_lock); + cd->sleep_state = SS_SLEEP_OFF; + mutex_unlock(&cd->system_lock); + + pt_start_wd_timer(cd); + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_core_wake_ + * + * SUMMARY: Protected call to pt_core_wake_ by exclusive access to the DUT. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static int pt_core_wake(struct pt_core_data *cd) +{ + int rc = 0; + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = pt_core_wake_(cd); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, "%s: fail to release exclusive\n", + __func__); + else + pt_debug(cd->dev, DL_DEBUG, "%s: pass release exclusive\n", + __func__); + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_get_ic_crc_ + * + * SUMMARY: This function retrieves the config block CRC + * + * NOTE: The post condition of calling this function will be that the DUT will + * be in SCANNINING mode if no failures occur + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer the core data structure + * ebid - enumerated block ID + ******************************************************************************/ +static int pt_get_ic_crc_(struct pt_core_data *cd, u8 ebid) +{ + struct pt_sysinfo *si = &cd->sysinfo; + int rc = 0; + u8 status; + u16 calculated_crc = 0; + u16 stored_crc = 0; + + rc = pt_pip_suspend_scanning_(cd); + if (rc) + goto error; + + rc = pt_pip_verify_config_block_crc_(cd, ebid, &status, + &calculated_crc, &stored_crc); + if (rc) + goto exit; + + if (status) { + rc = -EINVAL; + goto exit; + } + + si->ttconfig.crc = stored_crc; + +exit: + pt_pip_resume_scanning_(cd); +error: + pt_debug(cd->dev, DL_INFO, + "%s: CRC: ebid:%d, calc:0x%04X, stored:0x%04X, rc=%d\n", + __func__, ebid, calculated_crc, stored_crc, rc); + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_pip2_read_gpio + * + * SUMMARY: Sends a PIP2 READ_GPIO command to the DUT and stores the 32 gpio + * bits into the passed in variable + * + * NOTE: PIP2 READ_GPIO command is only supported in bootloader + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *status - pointer to where the command response status is stored + * *gpio - pointer to device attributes structure + ******************************************************************************/ +static int pt_pip2_read_gpio(struct device *dev, u8 *status, u32 *gpio) +{ + int rc = 0; + u16 actual_read_len; + u8 read_buf[12]; + u8 tmp_status = 0; + u8 index = PIP2_RESP_STATUS_OFFSET; + + memset(read_buf, 0, ARRAY_SIZE(read_buf)); + rc = _pt_request_pip2_send_cmd(dev, + PT_CORE_CMD_UNPROTECTED, PIP2_CMD_ID_READ_GPIO, + NULL, 0, read_buf, &actual_read_len); + + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Failed to send PIP2 READ_GPIO cmd\n", __func__); + rc = -ECOMM; + } else { + tmp_status = read_buf[index]; + } + + if (status) + *status = tmp_status; + + if (!rc && gpio && (tmp_status == 0)) { + *gpio = ((read_buf[index + 4] << 24) | + (read_buf[index + 3] << 16) | + (read_buf[index + 2] << 8) | + (read_buf[index + 1])); + } + + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_pip2_get_panel_id_by_gpio + * + * SUMMARY: Wrapper function to call pt_pip2_read_gpio() to get panel ID + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer the core data structure + * *pid - pointer to store panel ID + ******************************************************************************/ +static int _pt_pip2_get_panel_id_by_gpio(struct pt_core_data *cd, u8 *pid) +{ + u32 gpio_value = 0; + u8 status = 0; + u8 panel_id = PANEL_ID_NOT_ENABLED; + int rc = 0; + + if (!pid) + return -ENOMEM; + + rc = pt_pip2_read_gpio(cd->dev, &status, &gpio_value); + if (!rc) { + if (status == 0) { + panel_id = (gpio_value & PT_PANEL_ID_BITMASK) >> + PT_PANEL_ID_SHIFT; + pt_debug(cd->dev, DL_INFO, "%s: %s=0x%02X %s=0x%08X\n", + __func__, + "BL mode PID", panel_id, "gpio", gpio_value); + *pid = panel_id; + } else { + pt_debug(cd->dev, DL_ERROR, "%s: %s=%d\n", + __func__, + "BL read gpio failed status", status); + } + } else { + pt_debug(cd->dev, DL_ERROR, "%s: %s=%d\n", + __func__, + "BL read gpio failed status", status); + } + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_enum_with_dut_ + * + * SUMMARY: This function does the full enumeration of the DUT with TTDL. + * The core data (cd) startup_status will store, as a bitmask, each + * state of the enumeration process. The startup will be attempted + * PT_CORE_STARTUP_RETRY_COUNT times before giving up. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer the core data structure + * reset - Flag to reset the DUT before attempting to enumerate + * *status - poionter to store the enum status bitmask flags + ******************************************************************************/ +static int pt_enum_with_dut_(struct pt_core_data *cd, bool reset, + u32 *enum_status) +{ + int try = 1; + int rc = 0; + int wait_time = 0; + bool detected = false; + u8 return_data[8]; + u8 mode = PT_MODE_UNKNOWN; + u8 pid = PANEL_ID_NOT_ENABLED; + u8 sys_mode = FW_SYS_MODE_UNDEFINED; + struct pt_hid_desc hid_desc; + + memset(&hid_desc, 0, sizeof(hid_desc)); +#ifdef TTHE_TUNER_SUPPORT + tthe_print(cd, NULL, 0, "enter startup"); +#endif + pt_debug(cd->dev, DL_INFO, "%s: Start enum... 0x%04X, reset=%d\n", + __func__, cd->startup_status, reset); + pt_stop_wd_timer(cd); +reset: + if (try > 1) + pt_debug(cd->dev, DL_WARN, "%s: DUT Enum Attempt %d\n", + __func__, try); + pt_flush_bus_if_irq_asserted(cd, PT_FLUSH_BUS_BASED_ON_LEN); + if (cd->active_dut_generation == DUT_PIP1_ONLY) { + pt_debug(cd->dev, DL_INFO, + "%s: PIP1 Enumeration start\n", __func__); + + /* Only reset the DUT after the first try */ + if (reset || (try > 1)) { + /* + * Reset hardware only for Legacy parts. Skip for TT/TC + * parts because if the FW image was loaded directly + * to SRAM issueing a reset ill wipe out what was just + * loaded. + */ + rc = pt_dut_reset_and_wait(cd); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: Error on h/w reset r=%d\n", + __func__, rc); + if (try++ < PT_CORE_STARTUP_RETRY_COUNT) + goto reset; + goto exit; + } + /* sleep to allow FW to be launched if available */ + msleep(120); + } + rc = pt_get_hid_descriptor_(cd, &hid_desc); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: Error getting HID Descriptor r=%d\n", + __func__, rc); + if (try++ < PT_CORE_STARTUP_RETRY_COUNT) + goto reset; + rc = -EIO; + goto exit; + } + detected = true; + cd->mode = pt_get_mode(cd, &hid_desc); + /* + * Most systems do not use an XY pin as the panel_id and so + * the BL is used to retrieve the panel_id, however for + * systems that do use an XY pin, the panel_id MUST be + * retrieved from the system information when running FW + * (done below) and so this section of code is skipped. + * Entering the BL here is only needed on XY_PIN systems. + */ + if (cd->panel_id_support & PT_PANEL_ID_BY_BL) { + if (cd->mode == PT_MODE_OPERATIONAL) { + rc = pt_pip_start_bootloader_(cd); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: Error on start bootloader r=%d\n", + __func__, rc); + if (try++ < PT_CORE_STARTUP_RETRY_COUNT) + goto reset; + goto exit; + } + cd->mode = PT_MODE_BOOTLOADER; + pt_debug(cd->dev, DL_INFO, "%s: Bootloader mode\n", + __func__); + } + rc = pt_hid_output_bl_get_information_(cd, return_data); + if (!rc) { + cd->bl_info.ready = true; + cd->bl_info.chip_id = + get_unaligned_le16(&return_data[2]); + pt_debug(cd->dev, DL_INFO, "%s: chip ID %04X\n", + __func__, cd->bl_info.chip_id); + } else { + pt_debug(cd->dev, DL_ERROR, + "%s: failed to get chip ID, r=%d\n", + __func__, rc); + } + rc = pt_hid_output_bl_get_panel_id_(cd, &pid); + mutex_lock(&cd->system_lock); + if (!rc) { + cd->pid_for_loader = pid; + pt_debug(cd->dev, DL_INFO, "%s: Panel ID: 0x%02X\n", + __func__, cd->pid_for_loader); + } else { + cd->pid_for_loader = PANEL_ID_NOT_ENABLED; + pt_debug(cd->dev, DL_WARN, + "%s: Read Failed, disable Panel ID: 0x%02X\n", + __func__, cd->pid_for_loader); + } + mutex_unlock(&cd->system_lock); + } + + /* Exit BL due to XY_PIN case or any other cause to be in BL */ + if (cd->mode == PT_MODE_BOOTLOADER) { + rc = pt_hid_output_bl_launch_app_(cd); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: Error on launch app r=%d\n", + __func__, rc); + if (try++ < PT_CORE_STARTUP_RETRY_COUNT) + goto reset; + rc = -ENODEV; + goto exit; + } + + /* sleep to allow FW to be launched if available */ + msleep(120); + + /* Ensure the DUT is now in Application mode */ + rc = pt_get_hid_descriptor_(cd, &hid_desc); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: Error getting HID Desc r=%d\n", + __func__, rc); + if (try++ < PT_CORE_STARTUP_RETRY_COUNT) + goto reset; + rc = -EIO; + goto exit; + } + + cd->mode = pt_get_mode(cd, &hid_desc); + if (cd->mode == PT_MODE_BOOTLOADER) { + pt_debug(cd->dev, DL_WARN, + "%s: Error confiming exit BL\n", + __func__); + if (try++ < PT_CORE_STARTUP_RETRY_COUNT) + goto reset; + rc = -ENODEV; + goto exit; + } + } + pt_debug(cd->dev, DL_INFO, "%s: Operational mode\n", __func__); + cd->mode = PT_MODE_OPERATIONAL; + *enum_status |= STARTUP_STATUS_GET_DESC; + *enum_status |= STARTUP_STATUS_FW_OUT_OF_BOOT; + } else { + /* Generation is PIP2 Capable */ + pt_debug(cd->dev, DL_INFO, + "%s: PIP2 Enumeration start\n", __func__); + + rc = pt_pip2_get_mode_sysmode_(cd, &mode, NULL); + if (rc) { + pt_debug(cd->dev, DL_ERROR, + "%s: Get mode failed, mode unknown\n", + __func__); + mode = PT_MODE_UNKNOWN; + } else + detected = true; + + cd->mode = mode; + switch (cd->mode) { + case PT_MODE_OPERATIONAL: + pt_debug(cd->dev, DL_INFO, + "%s: Operational mode\n", __func__); + if (cd->app_pip_ver_ready == false) { + rc = pt_pip2_get_version_(cd); + if (!rc) + cd->app_pip_ver_ready = true; + else { + if (try++ < PT_CORE_STARTUP_RETRY_COUNT) + goto reset; + goto exit; + } + } + break; + case PT_MODE_BOOTLOADER: + pt_debug(cd->dev, DL_INFO, + "%s: Bootloader mode\n", __func__); + if (cd->panel_id_support & PT_PANEL_ID_BY_BL) { + rc = _pt_pip2_get_panel_id_by_gpio(cd, &pid); + mutex_lock(&cd->system_lock); + if (!rc) { + cd->pid_for_loader = pid; + pt_debug(cd->dev, DL_INFO, + "%s: Panel ID: 0x%02X\n", + __func__, cd->pid_for_loader); + } else { + cd->pid_for_loader = + PANEL_ID_NOT_ENABLED; + pt_debug(cd->dev, DL_WARN, + "%s: Read Failed, disable Panel ID: 0x%02X\n", + __func__, cd->pid_for_loader); + } + mutex_unlock(&cd->system_lock); + } + + if (cd->bl_pip_ver_ready == false) { + rc = pt_pip2_get_version_(cd); + if (!rc) + cd->bl_pip_ver_ready = true; + else { + if (try++ < PT_CORE_STARTUP_RETRY_COUNT) + goto reset; + goto exit; + } + } + + /* + * Launch app command will fail in flashless mode. + * Skip launch app command here to save time for + * enumeration flow. + */ + if (cd->flashless_dut) + goto exit; + + /* + * pt_pip2_launch_app() is needed here instead of + * pt_pip2_exit_bl() because exit_bl will cause several + * hw_resets to occur and the auto BL on a flashless + * DUT will fail. + */ + rc = pt_pip2_launch_app(cd->dev, + PT_CORE_CMD_UNPROTECTED); + if (rc) { + pt_debug(cd->dev, DL_ERROR, + "%s: Error on launch app r=%d\n", + __func__, rc); + msleep(50); + if (try++ < PT_CORE_STARTUP_RETRY_COUNT) + goto reset; + goto exit; + } + + /* + * IRQ thread can be delayed if the serial log print is + * enabled. It causes next command to get wrong response + * Here the delay is to ensure pt_parse_input() to be + * finished. + */ + msleep(60); + /* Check and update the mode */ + rc = pt_pip2_get_mode_sysmode_(cd, &mode, NULL); + if (rc) { + pt_debug(cd->dev, DL_ERROR, + "%s: Get mode failed, mode unknown\n", + __func__); + mode = PT_MODE_UNKNOWN; + } + + cd->mode = mode; + if (cd->mode == PT_MODE_OPERATIONAL) { + pt_debug(cd->dev, DL_INFO, + "%s: Launched to Operational mode\n", + __func__); + } else if (cd->mode == PT_MODE_BOOTLOADER) { + pt_debug(cd->dev, DL_ERROR, + "%s: Launch failed, Bootloader mode\n", + __func__); + goto exit; + } else if (cd->mode == PT_MODE_UNKNOWN) { + pt_debug(cd->dev, DL_ERROR, + "%s: Launch failed, Unknown mode\n", + __func__); + if (try++ < PT_CORE_STARTUP_RETRY_COUNT) + goto reset; + goto exit; + } + if (cd->app_pip_ver_ready == false) { + rc = pt_pip2_get_version_(cd); + if (!rc) + cd->app_pip_ver_ready = true; + else { + if (try++ < PT_CORE_STARTUP_RETRY_COUNT) + goto reset; + goto exit; + } + } + break; + default: + pt_debug(cd->dev, DL_ERROR, + "%s: Unknown mode\n", __func__); + if (try++ < PT_CORE_STARTUP_RETRY_COUNT) + goto reset; + goto exit; + } + *enum_status |= STARTUP_STATUS_GET_DESC; + } + + pt_init_pip_report_fields(cd); + *enum_status |= STARTUP_STATUS_GET_RPT_DESC; + if (!cd->features.easywake) + cd->easy_wakeup_gesture = PT_CORE_EWG_NONE; + + pt_debug(cd->dev, DL_INFO, "%s: Reading sysinfo\n", __func__); + rc = pt_hid_output_get_sysinfo_(cd); + if (rc) { + pt_debug(cd->dev, DL_ERROR, + "%s: Error on getting sysinfo r=%d\n", __func__, rc); + if (try++ < PT_CORE_STARTUP_RETRY_COUNT) + goto reset; + goto exit; + } + *enum_status |= STARTUP_STATUS_GET_SYS_INFO; + /* FW cannot handle most PIP cmds when it is still in BOOT mode */ + rc = _pt_poll_for_fw_exit_boot_mode(cd, 10000, &wait_time); + if (!rc) { + *enum_status |= STARTUP_STATUS_FW_OUT_OF_BOOT; + pt_debug(cd->dev, DL_WARN, + "%s: Exit FW BOOT Mode after %dms\n", + __func__, wait_time); + } else { + pt_debug(cd->dev, DL_WARN, + "%s: FW stuck in BOOT Mode after %dms\n", + __func__, wait_time); + goto exit; + } + + pt_debug(cd->dev, DL_INFO, "%s pt Prot Version: %d.%d\n", + __func__, + cd->sysinfo.ttdata.pip_ver_major, + cd->sysinfo.ttdata.pip_ver_minor); + rc = pt_get_ic_crc_(cd, PT_TCH_PARM_EBID); + if (rc) { + pt_debug(cd->dev, DL_ERROR, + "%s: DUT Config block CRC failure rc=%d\n", + __func__, rc); + if (try++ < PT_CORE_STARTUP_RETRY_COUNT) + goto reset; + } else { + _pt_get_fw_sys_mode(cd, &sys_mode, NULL); + if (sys_mode != FW_SYS_MODE_SCANNING) { + pt_debug(cd->dev, DL_ERROR, + "%s: scan state: %d, retry: %d\n", + __func__, sys_mode, try); + if (try++ < PT_CORE_STARTUP_RETRY_COUNT) + goto reset; + } else + *enum_status |= STARTUP_STATUS_GET_CFG_CRC; + } + rc = pt_restore_parameters_(cd); + if (rc) { + pt_debug(cd->dev, DL_ERROR, + "%s: Failed to restore parameters rc=%d\n", + __func__, rc); + } else + *enum_status |= STARTUP_STATUS_RESTORE_PARM; + call_atten_cb(cd, PT_ATTEN_STARTUP, 0); + cd->watchdog_interval = PT_WATCHDOG_TIMEOUT; + cd->startup_retry_count = 0; + +exit: + /* Generate the HW Version of the connected DUT and store in cd */ + pt_generate_hw_version(cd, cd->hw_version); + pt_debug(cd->dev, DL_WARN, "%s: HW Version: %s\n", __func__, + cd->hw_version); + + pt_start_wd_timer(cd); + + if (!detected) + rc = -ENODEV; + +#ifdef TTHE_TUNER_SUPPORT + tthe_print(cd, NULL, 0, "exit startup"); +#endif + + pt_debug(cd->dev, DL_WARN, + "%s: Completed Enumeration rc=%d On Attempt %d\n", + __func__, rc, try); + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_enum_with_dut + * + * SUMMARY: This is the safe function wrapper for pt_enum_with_dut_() by + * requesting exclusive access around the call. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer the core data structure + * reset - Flag to reset the DUT before attempting to enumerate + * *status - pointer to store the enum status bitmask flags + ******************************************************************************/ +static int pt_enum_with_dut(struct pt_core_data *cd, bool reset, u32 *status) +{ + int rc = 0; + + mutex_lock(&cd->system_lock); + cd->startup_state = STARTUP_RUNNING; + mutex_unlock(&cd->system_lock); + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + goto exit; + } + + rc = pt_enum_with_dut_(cd, reset, status); + + if (release_exclusive(cd, cd->dev) < 0) + /* Don't return fail code, mode is already changed. */ + pt_debug(cd->dev, DL_ERROR, "%s: fail to release exclusive\n", + __func__); + else + pt_debug(cd->dev, DL_DEBUG, "%s: pass release exclusive\n", + __func__); +exit: + mutex_lock(&cd->system_lock); + /* Clear startup state for any tasks waiting for startup completion */ + cd->startup_state = STARTUP_NONE; + mutex_unlock(&cd->system_lock); + + /* Set STATUS_COMPLETE bit to indicate the status is ready to be read */ + *status |= STARTUP_STATUS_COMPLETE; + + /* Wake the waiters for end of startup */ + wake_up(&cd->wait_q); + + return rc; +} + +static int add_sysfs_interfaces(struct device *dev); +static void remove_sysfs_interfaces(struct device *dev); +static void remove_sysfs_and_modules(struct device *dev); +static void pt_release_modules(struct pt_core_data *cd); +static void pt_probe_modules(struct pt_core_data *cd); +/******************************************************************************* + * FUNCTION: _pt_ttdl_restart + * + * SUMMARY: Restarts TTDL enumeration with the DUT and re-probes all modules + * + * NOTE: The function DOSE NOT remove sysfs and modules. Trying to create + * existing sysfs nodes will produce an error. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to core device + ******************************************************************************/ +static int _pt_ttdl_restart(struct device *dev) +{ + int rc = 0; + struct pt_core_data *cd = dev_get_drvdata(dev); +#ifdef CONFIG_TOUCHSCREEN_PARADE_I2C + struct i2c_client *client = + (struct i2c_client *)container_of(dev, struct i2c_client, dev); +#endif + /* + * Make sure the device is awake, pt_mt_release function will + * cause pm sleep function and lead to deadlock. + */ + pm_runtime_get_sync(dev); + /* Use ttdl_restart_lock to avoid reentry */ + mutex_lock(&cd->ttdl_restart_lock); + + remove_sysfs_and_modules(cd->dev); + +#ifdef CONFIG_TOUCHSCREEN_PARADE_I2C + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + pt_debug(dev, DL_ERROR, + "%s I2C functionality not Supported\n", __func__); + rc = -EIO; + goto ttdl_no_error; + } +#endif + + if (cd->active_dut_generation == DUT_UNKNOWN) { + rc = _pt_detect_dut_generation(cd->dev, + &cd->startup_status, &cd->active_dut_generation, + &cd->mode); + if ((cd->active_dut_generation == DUT_UNKNOWN) || (rc)) { + pt_debug(dev, DL_ERROR, + "%s: Error, Unknown DUT Generation rc=%d\n", + __func__, rc); + } + } + + rc = add_sysfs_interfaces(cd->dev); + if (rc < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: Error, failed sysfs nodes rc=%d\n", + __func__, rc); + + if (!(cd->startup_status & STARTUP_STATUS_BL_RESET_SENTINEL)) { + pt_debug(dev, DL_INFO, "%s: Call pt_enum_with_dut\n", __func__); + rc = pt_enum_with_dut(cd, true, &cd->startup_status); + if (rc < 0) + pt_debug(dev, DL_ERROR, + "%s: Error, Failed to Enumerate\n", __func__); + } + + rc = pt_mt_probe(dev); + if (rc < 0) { + pt_debug(dev, DL_ERROR, + "%s: Error, fail mt probe\n", __func__); + } + + rc = pt_btn_probe(dev); + if (rc < 0) { + pt_debug(dev, DL_ERROR, + "%s: Error, fail btn probe\n", __func__); + } + + pt_probe_modules(cd); + + pt_debug(cd->dev, DL_WARN, + "%s: Well Done! TTDL Restart Completed\n", __func__); + rc = 0; + +#ifdef CONFIG_TOUCHSCREEN_PARADE_I2C +ttdl_no_error: +#endif + mutex_unlock(&cd->ttdl_restart_lock); + + mutex_lock(&cd->system_lock); + cd->startup_status |= STARTUP_STATUS_COMPLETE; + cd->startup_state = STARTUP_NONE; + mutex_unlock(&cd->system_lock); + + pm_runtime_put(dev); + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_restart_work_function + * + * SUMMARY: This is the wrapper function placed in a work queue to call + * _pt_ttdl_restart() + * + * RETURN: none + * + * PARAMETERS: + * *work - pointer to the work_struct + ******************************************************************************/ +static void pt_restart_work_function(struct work_struct *work) +{ + struct pt_core_data *cd = container_of(work, + struct pt_core_data, ttdl_restart_work); + int rc = 0; + + rc = _pt_ttdl_restart(cd->dev); + if (rc < 0) + pt_debug(cd->dev, DL_ERROR, "%s: Fail queued startup r=%d\n", + __func__, rc); +} + +/******************************************************************************* + * FUNCTION: pt_enum_work_function + * + * SUMMARY: This is the wrapper function placed in a work queue to call + * pt_enum_with_dut() + * + * RETURN: none + * + * PARAMETERS: + * *work - pointer to the work_struct + ******************************************************************************/ +static void pt_enum_work_function(struct work_struct *work) +{ + struct pt_core_data *cd = container_of(work, + struct pt_core_data, enum_work); + int rc; + + rc = pt_enum_with_dut(cd, false, &cd->startup_status); + if (rc < 0) + pt_debug(cd->dev, DL_ERROR, "%s: Fail queued startup r=%d\n", + __func__, rc); +} + +static int pt_get_regulator(struct pt_core_data *cd, bool get) +{ + int rc; + + if (!get) { + rc = 0; + goto regulator_put; + } + + cd->vdd = regulator_get(cd->dev, "vdd"); + if (IS_ERR(cd->vdd)) { + rc = PTR_ERR(cd->vdd); + dev_err(cd->dev, + "Regulator get failed vdd rc=%d\n", rc); + goto regulator_put; + } + + cd->vcc_i2c = regulator_get(cd->dev, "vcc_i2c"); + if (IS_ERR(cd->vcc_i2c)) { + rc = PTR_ERR(cd->vcc_i2c); + dev_err(cd->dev, + "Regulator get failed vcc_i2c rc=%d\n", rc); + goto regulator_put; + } + + return 0; + +regulator_put: + if (cd->vdd) { + regulator_put(cd->vdd); + cd->vdd = NULL; + } + + if (cd->vcc_i2c) { + regulator_put(cd->vcc_i2c); + cd->vcc_i2c = NULL; + } + + return rc; +} + +#ifdef ENABLE_I2C_REG_ONLY +static int pt_enable_i2c_regulator(struct pt_core_data *cd, bool en) +{ + int rc = 0; + + pt_debug(cd->dev, DL_INFO, "%s: Enter flag = %d\n", __func__, en); + if (!en) { + rc = 0; + goto disable_vcc_i2c_reg_only; + } + + if (cd->vcc_i2c) { + rc = regulator_set_load(cd->vcc_i2c, I2C_ACTIVE_LOAD_MA); + if (rc < 0) + pt_debug(cd->dev, DL_INFO, + "%s: I2c unable to set active current rc = %d\n", __func__, rc); + + pt_debug(cd->dev, DL_INFO, "%s: i2c set I2C_ACTIVE_LOAD_MA rc = %d\n", + __func__, rc); + } + + return 0; + +disable_vcc_i2c_reg_only: + if (cd->vcc_i2c) { + rc = regulator_set_load(cd->vcc_i2c, I2C_SUSPEND_LOAD_UA); + if (rc < 0) + pt_debug(cd->dev, DL_INFO, "%s: i2c unable to set 0 uAm rc = %d\n", + __func__, rc); + + } + pt_debug(cd->dev, DL_INFO, "%s: Exit rc = %d I2C_SUSPEND_LOAD_UA\n", __func__, rc); + + return rc; +} + +#endif + +static int pt_enable_regulator(struct pt_core_data *cd, bool en) +{ + int rc; + + if (!en) { + rc = 0; + goto disable_vcc_i2c_reg; + } + + if (cd->vdd) { + if (regulator_count_voltages(cd->vdd) > 0) { + rc = regulator_set_voltage(cd->vdd, FT_VTG_MIN_UV, + FT_VTG_MAX_UV); + if (rc) { + dev_err(cd->dev, + "Regulator set_vtg failed vdd rc=%d\n", rc); + goto exit; + } + } + + rc = regulator_enable(cd->vdd); + if (rc) { + dev_err(cd->dev, + "Regulator vdd enable failed rc=%d\n", rc); + goto exit; + } + dev_info(cd->dev, "%s: VDD regulator enabled:\n", __func__); + } + + if (cd->vcc_i2c) { + if (regulator_count_voltages(cd->vcc_i2c) > 0) { + rc = regulator_set_voltage(cd->vcc_i2c, FT_I2C_VTG_MIN_UV, + FT_I2C_VTG_MAX_UV); + if (rc) { + dev_err(cd->dev, + "Regulator set_vtg failed vcc_i2c rc=%d\n", rc); + goto disable_vdd_reg; + } + } + + rc = regulator_enable(cd->vcc_i2c); + if (rc) { + dev_err(cd->dev, + "Regulator vcc_i2c enable failed rc=%d\n", rc); + goto disable_vdd_reg; + } + dev_info(cd->dev, "%s: VCC I2C regulator enabled:\n", __func__); + } + + return 0; + +disable_vcc_i2c_reg: + if (cd->vcc_i2c) { + if (regulator_count_voltages(cd->vcc_i2c) > 0) + regulator_set_voltage(cd->vcc_i2c, FT_I2C_VTG_MIN_UV, + FT_I2C_VTG_MAX_UV); + + regulator_disable(cd->vcc_i2c); + dev_info(cd->dev, "%s: VCC I2C regulator disabled:\n", __func__); + + } + +disable_vdd_reg: + if (cd->vdd) { + if (regulator_count_voltages(cd->vdd) > 0) + regulator_set_voltage(cd->vdd, FT_VTG_MIN_UV, + FT_VTG_MAX_UV); + + regulator_disable(cd->vdd); + dev_info(cd->dev, "%s: VDD regulator disabled:\n", __func__); + } + +exit: + return rc; +} + + +#if (KERNEL_VERSION(3, 19, 0) <= LINUX_VERSION_CODE) +#define KERNEL_VER_GT_3_19 +#endif + +#if defined(CONFIG_PM_RUNTIME) || defined(KERNEL_VER_GT_3_19) +/* CONFIG_PM_RUNTIME option is removed in 3.19.0 */ +#if defined(CONFIG_PM_SLEEP) +/******************************************************************************* + * FUNCTION: pt_core_rt_suspend + * + * SUMMARY: Wrapper function with PM Runtime stratergy to call pt_core_sleep. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to core device + ******************************************************************************/ +static int pt_core_rt_suspend(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + pt_debug(dev, DL_DEBUG, "%s Skip - probe state %d\n", + __func__, cd->core_probe_complete); + + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_core_rt_resume + * + * SUMMARY: Wrapper function with PM Runtime stratergy to call pt_core_wake. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to core device + ******************************************************************************/ +static int pt_core_rt_resume(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + pt_debug(dev, DL_DEBUG, "%s Skip - probe state %d\n", + __func__, cd->core_probe_complete); + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ +#endif /* CONFIG_PM_RUNTIME || LINUX_VERSION_CODE */ + +#if defined(CONFIG_PM_SLEEP) +/******************************************************************************* + * FUNCTION: pt_core_suspend_ + * + * SUMMARY: Wrapper function with device suspend/resume stratergy to call + * pt_core_sleep. This function may disable IRQ during sleep state. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to core device + ******************************************************************************/ +static int pt_core_suspend_(struct device *dev) +{ + int rc; + struct pt_core_data *cd = dev_get_drvdata(dev); + + pt_debug(dev, DL_INFO, "%s: Enter\n", __func__); + rc = pt_core_sleep(cd); + if (rc) { + pt_debug(dev, DL_ERROR, "%s: Error on sleep rc =%d\n", + __func__, rc); + return -EAGAIN; + } + + rc = pt_enable_regulator(cd, false); + if (!IS_EASY_WAKE_CONFIGURED(cd->easy_wakeup_gesture)) + return 0; + + /* Required to prevent interrupts before bus awake */ + disable_irq(cd->irq); + cd->irq_disabled = 1; + + if (device_may_wakeup(dev)) { + pt_debug(dev, DL_WARN, "%s Device MAY wakeup\n", + __func__); + if (!enable_irq_wake(cd->irq)) + cd->irq_wake = 1; + } else { + pt_debug(dev, DL_WARN, "%s Device MAY NOT wakeup\n", + __func__); + } + + pt_debug(dev, DL_INFO, "%s: Exit :\n", __func__); + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_core_suspend + * + * SUMMARY: Wrapper function of pt_core_suspend_() to help avoid TP from being + * woke up or put to sleep based on Kernel power state even when the display + * is off based on the check of TTDL core platform flag. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to core device + ******************************************************************************/ +static int pt_core_suspend(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + int rc = 0; + + if (cd->drv_debug_suspend || (cd->cpdata->flags & PT_CORE_FLAG_SKIP_SYS_SLEEP)) + return 0; + + if (pt_core_state == STATE_SUSPEND) + { + pt_debug(cd->dev, DL_INFO, "%s Already in Suspend state\n", __func__); + return 0; + } + + pt_debug(cd->dev, DL_INFO, "%s Suspend start\n", __func__); + cancel_work_sync(&cd->resume_work); + cancel_work_sync(&cd->suspend_work); + + mutex_lock(&cd->system_lock); + cd->wait_until_wake = 0; + mutex_unlock(&cd->system_lock); + + if (pm_suspend_via_firmware() || cd->touch_offload) { + rc = pt_core_suspend_(cd->dev); + cd->quick_boot = true; + } else { + rc = pt_enable_i2c_regulator(cd, false); + if (rc < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: Error on disabling i2c regulator\n", __func__); + } + pt_debug(cd->dev, DL_INFO, "%s Suspend exit - rc = %d\n", __func__, rc); + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_core_resume_ + * + * SUMMARY: Wrapper function with device suspend/resume stratergy to call + * pt_core_wake. This function may enable IRQ before wake up. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to core device + ******************************************************************************/ +static int pt_core_resume_(struct device *dev) +{ + int rc = 0; + struct pt_core_data *cd = dev_get_drvdata(dev); + + dev_info(dev, "%s: Entering into resume mode:\n", + __func__); + rc = pt_enable_regulator(cd, true); + dev_info(dev, "%s: Voltage regulator enabled: rc=%d\n", + __func__, rc); + + if (!IS_EASY_WAKE_CONFIGURED(cd->easy_wakeup_gesture)) + goto exit; + + /* + * Bus pm does not call suspend if device runtime suspended + * This flag is covers that case + */ + if (cd->irq_disabled) { + enable_irq(cd->irq); + cd->irq_disabled = 0; + } + + if (device_may_wakeup(dev)) { + pt_debug(dev, DL_WARN, "%s Device MAY wakeup\n", + __func__); + if (cd->irq_wake) { + disable_irq_wake(cd->irq); + cd->irq_wake = 0; + } + } else { + pt_debug(dev, DL_WARN, "%s Device MAY NOT wakeup\n", + __func__); + } + +exit: + rc = pt_core_wake(cd); + if (rc) { + dev_err(dev, "%s: Failed to wake up: rc=%d\n", + __func__, rc); + return -EAGAIN; + } + + return rc; +} +/******************************************************************************* + * FUNCTION: pt_core_restore + * + * SUMMARY: Wrapper function with device suspend/resume stratergy to call + * pt_core_wake. This function may enable IRQ before wake up. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to core device + ******************************************************************************/ +static int pt_core_restore(struct device *dev) +{ + int rc = 0; + struct pt_core_data *cd = dev_get_drvdata(dev); + + dev_info(dev, "%s: Entering into resume mode:\n", + __func__); + + queue_work(cd->pt_workqueue, &cd->resume_offload_work); + return rc; +} + +/******************************************************************************* + * FUNCTION: suspend_offload_work + * + * SUMMARY: Wrapper function of pt_core_suspend() to avoid TP to be waken/slept + * along with kernel power state even the display is off based on the check of + * TTDL core platform flag. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to core device + ******************************************************************************/ +static void pt_suspend_offload_work(struct work_struct *work) + +{ + int rc = 0; + struct pt_core_data *cd = container_of(work, struct pt_core_data, + suspend_offload_work); + + pt_debug(cd->dev, DL_INFO, "%s start\n", __func__); + if (cd->cpdata->flags & PT_CORE_FLAG_SKIP_SYS_SLEEP) + return; + + rc = pt_core_suspend_(cd->dev); + pt_debug(cd->dev, DL_WARN, "%s Exit - rc = %d\n", __func__, rc); +} + +/******************************************************************************* + * FUNCTION: resume_offload_work + * + * SUMMARY: Wrapper function of pt_core_resume_() to avoid TP to be waken/slept + * along with kernel power state even the display is off based on the check of + * TTDL core platform flag. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to core device + ******************************************************************************/ +static void pt_resume_offload_work(struct work_struct *work) + +{ + int rc = 0; + int retry_count = 1000; + struct pt_core_data *pt_data = container_of(work, struct pt_core_data, + resume_offload_work); + + pt_debug(pt_data->dev, DL_INFO, "%s start\n", __func__); + do { + retry_count--; + rc = pt_core_resume_(pt_data->dev); + if (rc < 0) + pt_debug(pt_data->dev, DL_ERROR, + "%s: Error on wake\n", __func__); + } while (retry_count && rc < 0); + + if (rc < 0){ + pt_debug(pt_data->dev, DL_ERROR, "%s: Error on wake\n", __func__); + return; + } + +#ifdef TOUCH_TO_WAKE_POWER_FEATURE_WORK_AROUND + rc = pt_core_easywake_on(pt_data); + if (rc < 0) { + pt_debug(pt_data->dev, DL_ERROR, + "%s: Error on enable touch to wake key\n", + __func__); + return; + } + pt_data->fb_state = FB_OFF; + pt_debug(pt_data->dev, DL_INFO, "%s End\n", __func__); +#endif + pt_data->quick_boot = false; + pt_debug(pt_data->dev, DL_INFO, "%s Exit\n", __func__); +} + + +/******************************************************************************* + * FUNCTION: pt_core_resume + * + * SUMMARY: Wrapper function of pt_core_resume_() to avoid TP to be waken/slept + * along with kernel power state even the display is off based on the check of + * TTDL core platform flag. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to core device + ******************************************************************************/ +static int pt_core_resume(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + int rc = 0; + + if (cd->drv_debug_suspend || (cd->cpdata->flags & PT_CORE_FLAG_SKIP_SYS_SLEEP)) + return 0; + + + if (pm_suspend_via_firmware() || cd->touch_offload) { + rc = pt_core_restore(cd->dev); + } else { + pt_debug(cd->dev, DL_INFO, "%s start\n", __func__); + rc = pt_enable_i2c_regulator(cd, true); + pt_debug(cd->dev, DL_INFO, "%s i2c regulator rc %d\n", __func__, rc); + } + + mutex_lock(&cd->system_lock); + cd->wait_until_wake = 1; + mutex_unlock(&cd->system_lock); + wake_up(&cd->wait_q); + + pt_debug(cd->dev, DL_INFO, "%s End rc = %d\n", __func__, rc); + return rc; +} +#endif + +#ifdef NEED_SUSPEND_NOTIFIER +/******************************************************************************* + * FUNCTION: pt_pm_notifier + * + * SUMMARY: This function is registered to notifier chain and will perform + * suspend operation if match event PM_SUSPEND_PREPARE. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *nb - pointer to notifier_block structure + * action - notifier event type + * *data - void pointer + ******************************************************************************/ +static int pt_pm_notifier(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct pt_core_data *cd = container_of(nb, + struct pt_core_data, pm_notifier); + if (action == PM_SUSPEND_PREPARE) { + pt_debug(cd->dev, DL_INFO, "%s: Suspend prepare\n", + __func__); + + /* + * If PM runtime is not suspended, either call runtime + * PM suspend callback or wait until it finishes + */ + if (!pm_runtime_suspended(cd->dev)) + pm_runtime_suspend(cd->dev); + + (void) pt_core_suspend(cd->dev); + } + + return NOTIFY_DONE; +} +#endif + +const struct dev_pm_ops pt_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pt_core_suspend, pt_core_resume) + .freeze = pt_core_suspend, + .restore = pt_core_restore, + SET_RUNTIME_PM_OPS(pt_core_rt_suspend, pt_core_rt_resume, + NULL) +}; +EXPORT_SYMBOL_GPL(pt_pm_ops); + +/******************************************************************************* + * FUNCTION: _pt_request_pip2_enter_bl + * + * SUMMARY: Force the DUT to enter the BL by resetting the DUT by use of the + * XRES pin or a soft reset. + * + * NOTE: The WD MUST be stopped/restarted by the calling Function. Having + * the WD active could cause this function to fail! + * NOTE: If start_mode is passed in as PT_MODE_IGNORE, this function + * will not try to determine the current mode but will proceed with + * resetting the DUT and entering the BL. + * + * NOTE: The definition of result code: + * PT_ENTER_BL_PASS (0) + * PT_ENTER_BL_ERROR (1) + * PT_ENTER_BL_RESET_FAIL (2) + * PT_ENTER_BL_HID_START_BL_FAIL (3) + * PT_ENTER_BL_CONFIRM_FAIL (4) + * PT_ENTER_BL_GET_FLASH_INFO_FAIL (5) + * + * RETURNS: + * 0 = success + * !0 = failure + * + * + * PARAMETERS: + * *dev - pointer to device structure + * *start_mode - pointer to the mode the DUT was in when this function + * starts + * *result - pointer to store the result when to enter BL + ******************************************************************************/ +int _pt_request_pip2_enter_bl(struct device *dev, u8 *start_mode, int *result) +{ + int rc = 0; + int t; + int tmp_result = PT_ENTER_BL_ERROR; + int flash_info_retry = 2; + u8 mode = PT_MODE_UNKNOWN; + u8 sys_mode = FW_SYS_MODE_UNDEFINED; + u8 read_buf[32]; + u16 actual_read_len; + struct pt_core_data *cd = dev_get_drvdata(dev); + u8 host_mode_cmd[4] = {0xA5, 0xA5, 0xA5, 0xA5}; + u8 time = 0; + u8 saved_flashless_auto_bl_mode = cd->flashless_auto_bl; + + if (cd->watchdog_enabled) { + pt_debug(dev, DL_WARN, + "%s: Watchdog must be stopped before entering BL\n", + __func__); + goto exit; + } + + cancel_work_sync(&cd->enum_work); + cancel_work_sync(&cd->watchdog_work); + + /* if undefined assume operational/test to bypass all checks */ + if (*start_mode == PT_MODE_IGNORE) { + mode = PT_MODE_OPERATIONAL; + sys_mode = FW_SYS_MODE_TEST; + pt_debug(dev, DL_INFO, "%s: Assume Mode = %d", __func__, mode); + } else if (*start_mode == PT_MODE_UNKNOWN) { + rc = pt_pip2_get_mode_sysmode_(cd, &mode, &sys_mode); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Get mode failed, mode unknown\n", + __func__); + } + *start_mode = mode; + pt_debug(dev, DL_INFO, "%s: Get Mode = %d", __func__, mode); + } else if (*start_mode == PT_MODE_OPERATIONAL) { + /* Assume SCANNIING mode to avoid doing an extra get_mode */ + sys_mode = FW_SYS_MODE_SCANNING; + } + +_retry: + /* For Flashless DUTs - Suppress auto BL on next BL sentinel */ + pt_debug(dev, DL_INFO, "%s: Flashless Auto_BL - SUPPRESS\n", __func__); + cd->flashless_auto_bl = PT_SUPPRESS_AUTO_BL; + + switch (mode) { + case PT_MODE_UNKNOWN: + /* + * When the mode could not be determined the DUT could be + * in App mode running corrupted FW or FW that is not + * responding to the mode request, assume no communication + * and do a hard reset + */ + mutex_lock(&cd->system_lock); + cd->startup_status = STARTUP_STATUS_START; + pt_debug(dev, DL_DEBUG, "%s: Startup Status Reset\n", __func__); + mutex_unlock(&cd->system_lock); + + rc = pt_dut_reset(cd, PT_CORE_CMD_UNPROTECTED); + if (rc) { + tmp_result = PT_ENTER_BL_RESET_FAIL; + goto exit; + } + break; + + case PT_MODE_OPERATIONAL: + if (sys_mode == FW_SYS_MODE_SCANNING) { + pt_debug(dev, DL_INFO, "%s: Suspend Scanning\n", + __func__); + rc = pt_pip_suspend_scanning_(cd); + if (rc) { + /* + * Print to log but don't exit, the FW could be + * running but be hung or fail to respond to + * this request + */ + pt_debug(dev, DL_ERROR, + "%s Suspend Scan Failed\n", __func__); + } + /* sleep to allow the suspend scan to be processed */ + usleep_range(1000, 2000); + } + + mutex_lock(&cd->system_lock); + cd->startup_status = STARTUP_STATUS_START; + pt_debug(dev, DL_DEBUG, "%s: Startup Status Reset\n", __func__); + mutex_unlock(&cd->system_lock); + + /* Reset device to enter the BL */ + rc = pt_dut_reset(cd, PT_CORE_CMD_UNPROTECTED); + if (rc) { + tmp_result = PT_ENTER_BL_RESET_FAIL; + goto exit; + } + break; + + case PT_MODE_BOOTLOADER: + /* Do nothing as we are already in the BL */ + tmp_result = PT_ENTER_BL_PASS; + goto exit; + + default: + /* Should NEVER get here */ + tmp_result = PT_ENTER_BL_ERROR; + pt_debug(dev, DL_ERROR, "%s: Unknown mode code\n", __func__); + goto exit; + } + + if (!cd->flashless_dut && + (mode == PT_MODE_UNKNOWN || mode == PT_MODE_OPERATIONAL)) { + /* + * Sending the special "Host Mode" command will instruct the + * BL to not execute the FW it has loaded into RAM. + * The command must be sent within a 40ms window from releasing + * the XRES pin. If the messages is sent too early it will NAK, + * so keep sending it every 2ms until it is accepted by the BL. + * A no-flash DUT does not require this command as there is no + * FW for the BL to load and execute. + */ + usleep_range(4000, 6000); + pt_debug(cd->dev, DL_INFO, + ">>> %s: Write Buffer Size[%d] Stay in BL\n", + __func__, (int)sizeof(host_mode_cmd)); + + pt_pr_buf(cd->dev, DL_DEBUG, host_mode_cmd, + (int)sizeof(host_mode_cmd), ">>> User CMD"); + rc = 1; + while (rc && time < 34) { + rc = pt_adap_write_read_specific(cd, 4, + host_mode_cmd, NULL); + usleep_range(1800, 2000); + time += 2; + } + + /* Sleep to allow the BL to come up */ + usleep_range(1000, 2000); + } + + /* + * To avoid the case that next PIP command can be confused by BL/FW + * sentinel's "wakeup" event, chekcing hid_reset_cmd_state which is + * followed by "wakeup event" function can lower the failure rate. + */ + t = wait_event_timeout(cd->wait_q, + ((cd->startup_status != STARTUP_STATUS_START) + && (cd->hid_reset_cmd_state == 0)), + msecs_to_jiffies(300)); + if (IS_TMO(t)) { + pt_debug(cd->dev, DL_ERROR, + "%s: TMO waiting for BL sentinel\n", __func__); + } + + /* Check if device is now in BL mode */ + rc = pt_pip2_get_mode_sysmode_(cd, &mode, NULL); + pt_debug(dev, DL_INFO, "%s: Mode=%d, Status=0x%04X\n", __func__, mode, + cd->startup_status); + if (!rc && mode == PT_MODE_BOOTLOADER) { + pt_debug(dev, DL_INFO, "%s In bootloader mode now\n", __func__); + mutex_lock(&cd->system_lock); + cd->pip2_prot_active = true; + cd->mode = PT_MODE_BOOTLOADER; + mutex_unlock(&cd->system_lock); + tmp_result = PT_ENTER_BL_PASS; + } else { + /* + * If the device doesn't enter BL mode as expected and rc is + * tested pass by above function pt_pip2_get_mode_sysmode_(), + * the function should return an error code to indicate this + * failure PT_ENTER_BL_CONFIRM_FAIL. + */ + if (!rc) + rc = -EINVAL; + tmp_result = PT_ENTER_BL_CONFIRM_FAIL; + mutex_lock(&cd->system_lock); + cd->mode = mode; + mutex_unlock(&cd->system_lock); + pt_debug(dev, DL_ERROR, + "%s ERROR: Not in BL as expected", __func__); + } + +exit: + if (!cd->flashless_dut && (tmp_result == PT_ENTER_BL_PASS)) { + /* Check to get (buffered) flash information */ + rc = _pt_request_pip2_send_cmd(dev, PT_CORE_CMD_UNPROTECTED, + PIP2_CMD_ID_FLASH_INFO, NULL, 0, + read_buf, &actual_read_len); + if (!rc) { + if (read_buf[PIP2_RESP_BODY_OFFSET] == 0) { + pt_debug( + dev, DL_WARN, + "%s Unavailable Manufacturer ID: 0x%02x\n", + __func__, + read_buf[PIP2_RESP_BODY_OFFSET]); + /* + * If the BL was unable to cache the correct + * values when entering the first time due to + * the Flash part not having been powered up + * long enough, re-enter the BL to trigger the + * BL to re-attempt to cache the values. + */ + if (flash_info_retry-- > 0) { + mode = PT_MODE_UNKNOWN; + pt_debug(dev, DL_WARN, + "%s Assume mode to UNKNOWN to enter BL again, retry=%d\n", + __func__, flash_info_retry); + goto _retry; + } else { + pt_debug(dev, DL_WARN, + "%s Manufacturer ID Unknown\n", + __func__); + tmp_result = PT_ENTER_BL_PASS; + } + } + } else { + tmp_result = PT_ENTER_BL_GET_FLASH_INFO_FAIL; + pt_debug( + dev, DL_ERROR, + "%s: Failed to send PIP2 READ_FLASH_INFO cmd\n", + __func__); + } + } + + pt_debug(dev, DL_INFO, "%s Flashless Auto_BL - %s\n", __func__, + saved_flashless_auto_bl_mode == PT_ALLOW_AUTO_BL ? "ALLOW" : + "SUPPRESS"); + cd->flashless_auto_bl = saved_flashless_auto_bl_mode; + if (result) + *result = tmp_result; + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_pip2_file_open + * + * SUMMARY: Using the BL PIP2 commands open a file and return the file handle + * + * NOTE: The DUT must be in BL mode for this command to work + * + * RETURNS: + * <0 = Error + * >0 = file handle opened + * + * PARAMETERS: + * *dev - pointer to device structure + * file_no - PIP2 file number to open + ******************************************************************************/ +int _pt_pip2_file_open(struct device *dev, u8 file_no) +{ + int ret = 0; + u16 status; + u16 actual_read_len; + u8 file_handle; + u8 data[2]; + u8 read_buf[10]; + + pt_debug(dev, DL_DEBUG, "%s: OPEN file %d\n", __func__, file_no); + data[0] = file_no; + ret = _pt_request_pip2_send_cmd(dev, + PT_CORE_CMD_UNPROTECTED, PIP2_CMD_ID_FILE_OPEN, + data, 1, read_buf, &actual_read_len); + if (ret) { + pt_debug(dev, DL_ERROR, + "%s ROM BL FILE_OPEN timeout for file = %d\n", + __func__, file_no); + return -PIP2_RSP_ERR_NOT_OPEN; + } + + status = read_buf[PIP2_RESP_STATUS_OFFSET]; + file_handle = read_buf[PIP2_RESP_BODY_OFFSET]; + if (ret || ((status != 0x00) && (status != 0x03)) || + (file_handle != file_no)) { + pt_debug(dev, DL_ERROR, + "%s ROM BL FILE_OPEN failure: 0x%02X for file = %d\n", + __func__, status, file_handle); + return -status; + } + return file_handle; +} + +/******************************************************************************* + * FUNCTION: _pt_pip2_file_close + * + * SUMMARY: Using the BL PIP2 commands close a file + * + * NOTE: The DUT must be in BL mode for this command to work + * + * RETURNS: + * <0 = Error + * >0 = file handle closed + * + * PARAMETERS: + * *dev - pointer to device structure + * file_handle - handle to the file to be closed + ******************************************************************************/ +int _pt_pip2_file_close(struct device *dev, u8 file_handle) +{ + int ret = 0; + u16 status; + u16 actual_read_len; + u8 data[2]; + u8 read_buf[32]; + + pt_debug(dev, DL_DEBUG, "%s: CLOSE file %d\n", __func__, file_handle); + data[0] = file_handle; + ret = _pt_request_pip2_send_cmd(dev, + PT_CORE_CMD_UNPROTECTED, PIP2_CMD_ID_FILE_CLOSE, + data, 1, read_buf, &actual_read_len); + if (ret) { + pt_debug(dev, DL_ERROR, + "%s ROM BL FILE_CLOSE timeout for file = %d\n", + __func__, file_handle); + return -ETIME; + } + + status = read_buf[PIP2_RESP_STATUS_OFFSET]; + if (status != 0x00) { + pt_debug(dev, DL_ERROR, + "%s ROM BL FILE_CLOSE failure: 0x%02X for file = %d\n", + __func__, status, file_handle); + return -status; + } + return file_handle; +} + +/******************************************************************************* + * FUNCTION: _pt_pip2_file_erase + * + * SUMMARY: Using the BL PIP2 commands erase a file + * + * NOTE: The DUT must be in BL mode for this command to work + * NOTE: Some FLASH parts can time out while erasing one or more sectors, + * one retry is attempted for each sector in a file. + * + * RETURNS: + * <0 = Error + * >0 = file handle closed + * + * PARAMETERS: + * *dev - pointer to device structure + * file_handle - handle to the file to be erased + * *status - PIP2 erase status code + ******************************************************************************/ +static int _pt_pip2_file_erase(struct device *dev, u8 file_handle, int *status) +{ + int ret = 0; + int max_retry = PT_PIP2_MAX_FILE_SIZE/PT_PIP2_FILE_SECTOR_SIZE; + int retry = 1; + u16 actual_read_len; + u8 data[2]; + u8 read_buf[10]; + struct pt_core_data *cd = dev_get_drvdata(dev); + + pt_debug(dev, DL_DEBUG, "%s: ERASE file %d\n", __func__, file_handle); + data[0] = file_handle; + data[1] = PIP2_FILE_IOCTL_CODE_ERASE_FILE; + *status = PIP2_RSP_ERR_TIMEOUT; + + /* Increase waiting time for large file erase */ + mutex_lock(&cd->system_lock); + cd->pip_cmd_timeout = PT_PIP2_CMD_FILE_ERASE_TIMEOUT; + mutex_unlock(&cd->system_lock); + while (*status == PIP2_RSP_ERR_TIMEOUT && retry < max_retry) { + ret = _pt_request_pip2_send_cmd(dev, + PT_CORE_CMD_UNPROTECTED, PIP2_CMD_ID_FILE_IOCTL, + data, 2, read_buf, &actual_read_len); + if (ret) + break; + *status = read_buf[PIP2_RESP_STATUS_OFFSET]; + if (*status == PIP2_RSP_ERR_TIMEOUT) { +#ifdef TTDL_DIAGNOSTICS + cd->file_erase_timeout_count++; +#endif + pt_debug(dev, DL_WARN, + "%s: ERASE timeout %d for file = %d\n", + __func__, retry, file_handle); + } + retry++; + } + mutex_lock(&cd->system_lock); + cd->pip_cmd_timeout = cd->pip_cmd_timeout_default; + mutex_unlock(&cd->system_lock); + if (ret) { + pt_debug(dev, DL_ERROR, + "%s ROM FILE_ERASE cmd failure: %d for file = %d\n", + __func__, ret, file_handle); + return -EIO; + } + + if (*status != 0x00) { + pt_debug(dev, DL_ERROR, + "%s ROM FILE_ERASE failure: 0x%02X for file = %d\n", + __func__, *status, file_handle); + return -EIO; + } + return file_handle; +} + +/******************************************************************************* + * FUNCTION: _pt_pip2_file_read + * + * SUMMARY: Using the BL PIP2 commands read n bytes from a already opened file + * + * NOTE: The DUT must be in BL mode for this command to work + * + * RETURNS: + * <0 = Error + * >0 = number of bytes read + * + * PARAMETERS: + * *dev - pointer to device structure + * file_handle - File handle to read from + * num_bytes - number of bytes to read + ******************************************************************************/ +int _pt_pip2_file_read(struct device *dev, u8 file_handle, u16 num_bytes, + u8 *read_buf) +{ + int ret = 0; + u16 status; + u16 actual_read_len; + u8 data[3]; + + data[0] = file_handle; + data[1] = (num_bytes & 0x00FF); + data[2] = (num_bytes & 0xFF00) >> 8; + + ret = _pt_request_pip2_send_cmd(dev, + PT_CORE_CMD_UNPROTECTED, PIP2_CMD_ID_FILE_READ, + data, 3, read_buf, &actual_read_len); + status = read_buf[PIP2_RESP_STATUS_OFFSET]; + if (ret || ((status != 0x00) && (status != 0x03))) { + pt_debug(dev, DL_ERROR, + "%s File open failure with error code = %d\n", + __func__, status); + return -EPERM; + } + ret = num_bytes; + + return ret; +} + +/******************************************************************************* + * FUNCTION: _pt_read_us_file + * + * SUMMARY: Open a user space file and read 'size' bytes into buf. If size = 0 + * then read the entire file. + * NOTE: The file size must be less than PT_PIP2_MAX_FILE_SIZE + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *file_path - pointer to the file path + * *buf - pointer to the buffer to store the file contents + * *size - pointer to the size of the file + ******************************************************************************/ +int _pt_read_us_file(struct device *dev, u8 *file_path, u8 *buf, int *size) +{ + struct file *filp = NULL; + struct inode *inode = NULL; + unsigned int file_len = 0; + unsigned int read_len = 0; + mm_segment_t oldfs; + int rc = 0; + + if (file_path == NULL || buf == NULL) { + pt_debug(dev, DL_ERROR, "%s: path || buf is NULL.\n", __func__); + return -EINVAL; + } + pt_debug(dev, DL_WARN, "%s: path = %s\n", __func__, file_path); + + oldfs = force_uaccess_begin(); + filp = filp_open_block(file_path, O_RDONLY, 0400); + + if (IS_ERR(filp)) { + pt_debug(dev, DL_ERROR, "%s: Failed to open %s\n", __func__, + file_path); + rc = -ENOENT; + goto err; + } + + if (filp->f_op == NULL) { + pt_debug(dev, DL_ERROR, "%s: File Operation Method Error\n", + __func__); + rc = -EINVAL; + goto exit; + } + inode = filp->f_path.dentry->d_inode; + if (inode == NULL) { + pt_debug(dev, DL_ERROR, "%s: Get inode from filp failed\n", + __func__); + rc = -EINVAL; + goto exit; + } + file_len = i_size_read(inode->i_mapping->host); + if (file_len == 0) { + pt_debug(dev, DL_ERROR, "%s: file size error,file_len = %d\n", + __func__, file_len); + rc = -EINVAL; + goto exit; + } + + if (*size == 0) + read_len = file_len; + else + read_len = *size; + + if (read_len > PT_PIP2_MAX_FILE_SIZE) { + pt_debug(dev, DL_ERROR, "%s: file size ( %d ) exception.\n", + __func__, read_len); + rc = -EINVAL; + goto exit; + } + filp->private_data = inode->i_private; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0)) + if (filp->f_op->read(filp, buf, read_len, &(filp->f_pos)) != read_len) { +#else + if (vfs_read(filp, buf, read_len, &(filp->f_pos)) != read_len) { +#endif + pt_debug(dev, DL_ERROR, "%s: file read error.\n", __func__); + rc = -EINVAL; + goto exit; + } + *size = read_len; + +exit: + if (filp_close(filp, NULL) != 0) + pt_debug(dev, DL_ERROR, "%s: file close error.\n", __func__); +err: + force_uaccess_end(oldfs); + return rc; + +} + +/******************************************************************************* + * FUNCTION: _pt_request_pip2_bin_hdr + * + * SUMMARY: Read the stored bin file header from Flash or the User Space file + * in the case of a flashless DUT, and parse the contents + * + * RETURNS: + * 0 = Success + * !0 = Error condition + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +int _pt_request_pip2_bin_hdr(struct device *dev, struct pt_bin_file_hdr *hdr) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + u8 file_handle; + u8 read_buf[255]; + u8 hdr_len = 0; + u8 i; + int bytes_read; + int read_size; + int ret = 0; + int rc = 0; + bool load_hdr_struct = false; + + if (cd->flashless_dut) { + read_size = sizeof(read_buf); + rc = _pt_read_us_file(dev, cd->pip2_us_file_path, + read_buf, &read_size); + if (rc) { + ret = rc; + pt_debug(dev, DL_ERROR, + "%s Failed to read fw image from US, rc=%d\n", + __func__, rc); + goto exit; + } + load_hdr_struct = true; + hdr_len = read_buf[0]; + i = 0; + } else { + if (cd->mode != PT_MODE_BOOTLOADER) { + ret = -EPERM; + goto exit; + } + + /* Open the bin file in Flash */ + pt_debug(dev, DL_INFO, "%s Open File 1\n", __func__); + file_handle = _pt_pip2_file_open(dev, PIP2_FW_FILE); + if (file_handle != PIP2_FW_FILE) { + ret = -ENOENT; + pt_debug(dev, DL_ERROR, + "%s Failed to open bin file\n", __func__); + goto exit; + } + + /* Read the header length from the file */ + pt_debug(dev, DL_INFO, "%s Read length of header\n", __func__); + read_size = 1; + bytes_read = _pt_pip2_file_read(dev, file_handle, read_size, + read_buf); + if (bytes_read != read_size) { + ret = -EX_ERR_FREAD; + pt_debug(dev, DL_ERROR, + "%s Failed to bin file header len\n", __func__); + goto exit_close_file; + } + hdr_len = read_buf[PIP2_RESP_BODY_OFFSET]; + if (hdr_len == 0xFF) { + ret = -EX_ERR_FLEN; + pt_debug(dev, DL_ERROR, + "%s Bin header len is invalid\n", __func__); + goto exit_close_file; + } + + /* Read the rest of the header from the bin file */ + pt_debug(dev, DL_INFO, "%s Read bin file header\n", __func__); + memset(read_buf, 0, sizeof(read_buf)); + bytes_read = _pt_pip2_file_read(dev, file_handle, hdr_len, + read_buf); + if (bytes_read != hdr_len) { + ret = -EX_ERR_FREAD; + pt_debug(dev, DL_ERROR, + "%s Failed to read bin file\n", __func__); + goto exit_close_file; + } + load_hdr_struct = true; + +exit_close_file: + /* Close the file */ + if (file_handle != _pt_pip2_file_close(dev, file_handle)) { + ret = -EX_ERR_FCLOSE; + pt_debug(dev, DL_ERROR, + "%s Failed to close bin file\n", __func__); + } + + /* + * The length was already read so subtract 1 to make the rest of + * the offsets match the spec + */ + i = PIP2_RESP_BODY_OFFSET - 1; + } + + if (load_hdr_struct) { + hdr->length = hdr_len; + hdr->ttpid = (read_buf[i+1] << 8) | read_buf[i+2]; + hdr->fw_major = (read_buf[i+3]); + hdr->fw_minor = (read_buf[i+4]); + hdr->fw_crc = (read_buf[i+5] << 24) | + (read_buf[i+6] << 16) | + (read_buf[i+7] << 8) | + (read_buf[i+8]); + hdr->fw_rev_ctrl = (read_buf[i+9] << 24) | + (read_buf[i+10] << 16) | + (read_buf[i+11] << 8) | + (read_buf[i+12]); + hdr->si_rev = (read_buf[i+14] << 8) | (read_buf[i+13]); + hdr->si_id = (read_buf[i+16] << 8) | (read_buf[i+15]); + hdr->config_ver = (read_buf[i+17] << 8) | (read_buf[i+18]); + if (hdr_len >= 22) { + hdr->hex_file_size = (read_buf[i+19] << 24) | + (read_buf[i+20] << 16) | + (read_buf[i+21] << 8) | + (read_buf[i+22]); + } else { + hdr->hex_file_size = 0; + } + } + +exit: + return ret; +} + +/******************************************************************************* + * FUNCTION: _pt_pip2_file_get_stats + * + * SUMMARY: Using the BL PIP2 commands get file information from an already + * opened file + * + * NOTE: The DUT must be in BL mode for this command to work + * + * RETURNS: + * !0 = Error + * 0 = Success + * + * PARAMETERS: + * *dev - pointer to device structure + * file_handle - File handle to read from + * *address - pointer to store address of file + * *file_size _ pointer to store size of file + ******************************************************************************/ +int _pt_pip2_file_get_stats(struct device *dev, u8 file_handle, u32 *address, + u32 *file_size) +{ + int ret = 1; + u16 status; + u16 actual_read_len; + u8 data[2]; + u8 read_buf[16]; + + data[0] = file_handle; + data[1] = PIP2_FILE_IOCTL_CODE_FILE_STATS; + + ret = _pt_request_pip2_send_cmd(dev, + PT_CORE_CMD_UNPROTECTED, PIP2_CMD_ID_FILE_IOCTL, + data, 2, read_buf, &actual_read_len); + + status = read_buf[PIP2_RESP_STATUS_OFFSET]; + if (ret || (status != 0x00)) { + pt_debug(dev, DL_ERROR, + "%s ROM FILE_STATS failure: 0x%02X for file = %d, ret = %d\n", + __func__, status, file_handle, ret); + ret = -EIO; + goto exit; + } + + pt_debug(dev, DL_DEBUG, + "%s --- FILE %d Information ---\n", __func__, file_handle); + + if (address) { + *address = read_buf[PIP2_RESP_BODY_OFFSET] + + (read_buf[PIP2_RESP_BODY_OFFSET + 1] << 8) + + (read_buf[PIP2_RESP_BODY_OFFSET + 2] << 16) + + (read_buf[PIP2_RESP_BODY_OFFSET + 3] << 24); + pt_debug(dev, DL_DEBUG, "%s Address: 0x%08x\n", + __func__, *address); + } + + if (file_size) { + *file_size = read_buf[PIP2_RESP_BODY_OFFSET + 4] + + (read_buf[PIP2_RESP_BODY_OFFSET + 5] << 8) + + (read_buf[PIP2_RESP_BODY_OFFSET + 6] << 16) + + (read_buf[PIP2_RESP_BODY_OFFSET + 7] << 24); + pt_debug(dev, DL_DEBUG, "%s Size : 0x%08x\n", + __func__, *file_size); + } + +exit: + return ret; +} + +/******************************************************************************* + * FUNCTION: _pt_pip2_file_seek_offset + * + * SUMMARY: Using the BL PIP2 commands seek read/write offset for an already + * opened file + * + * NOTE: The DUT must be in BL mode for this command to work + * NOTE: File open/erase command can reset the offset + * + * RETURNS: + * !0 = Error + * 0 = Success + * + * PARAMETERS: + * *dev - pointer to device structure + * file_handle - File handle to read from + * read_offset - read offset of file + * write_offset - write offset of file + ******************************************************************************/ +int _pt_pip2_file_seek_offset(struct device *dev, u8 file_handle, + u32 read_offset, u32 write_offset) +{ + int ret = 1; + u16 status; + u16 actual_read_len; + u8 data[10]; + u8 read_buf[16]; + + data[0] = file_handle; + data[1] = PIP2_FILE_IOCTL_CODE_SEEK_POINTER; + data[2] = 0xff & read_offset; + data[3] = 0xff & (read_offset >> 8); + data[4] = 0xff & (read_offset >> 16); + data[5] = 0xff & (read_offset >> 24); + data[6] = 0xff & write_offset; + data[7] = 0xff & (write_offset >> 8); + data[8] = 0xff & (write_offset >> 16); + data[9] = 0xff & (write_offset >> 24); + + ret = _pt_request_pip2_send_cmd(dev, + PT_CORE_CMD_UNPROTECTED, PIP2_CMD_ID_FILE_IOCTL, + data, 10, read_buf, &actual_read_len); + + status = read_buf[PIP2_RESP_STATUS_OFFSET]; + if (ret || (status != 0x00)) { + pt_debug(dev, DL_ERROR, + "%s ROM FILE_SEEK failure: 0x%02X for file = %d, ret = %d\n", + __func__, status, ret, file_handle); + ret = -EIO; + } + return ret; +} + +/******************************************************************************* + * FUNCTION: _pt_pip2_file_crc + * + * SUMMARY: Using the BL PIP2 commands to calculate CRC for a file or portion of + * the file. + * + * NOTE: The DUT must be in BL mode for this command to work + * NOTE: This command only can be used for BL version 1.8 or greater. + * BL version 1.8 added this change according to PGV-173. + * + * RETURNS: + * !0 = Error + * 0 = Success + * + * PARAMETERS: + * *dev - pointer to device structure + * file_handle - File handle to read from + * offset - start offset for CRC calculation + * length - number of bytes to calculate CRC over + * read_buf - pointer to the read buffer + ******************************************************************************/ +int _pt_pip2_file_crc(struct device *dev, u8 file_handle, + u32 offset, u32 length, u8 *read_buf) +{ + int rc = 1; + u16 actual_read_len; + u8 data[10]; + + data[0] = file_handle; + data[1] = PIP2_FILE_IOCTL_CODE_FILE_CRC; + data[2] = 0xff & offset; + data[3] = 0xff & (offset >> 8); + data[4] = 0xff & (offset >> 16); + data[5] = 0xff & (offset >> 24); + data[6] = 0xff & length; + data[7] = 0xff & (length >> 8); + data[8] = 0xff & (length >> 16); + data[9] = 0xff & (length >> 24); + + rc = _pt_request_pip2_send_cmd(dev, + PT_CORE_CMD_UNPROTECTED, PIP2_CMD_ID_FILE_IOCTL, + data, 10, read_buf, &actual_read_len); + + if (rc) + pt_debug(dev, DL_ERROR, + "%s Return FILE_CRC failure, rc = %d\n", + __func__, rc); + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_pip2_ping_test + * + * SUMMARY: BIST type test that uses the PIP2 PING command and ramps up the + * optional payload from 0 bytes to max_bytes and ensures the PIP2 + * response payload matches what was sent. + * The max payload size is 247: + * (255 - 2 byte reg address - 4 byte header - 2 byte CRC) + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to output buffer + ******************************************************************************/ +int pt_pip2_ping_test(struct device *dev, int max_bytes, int *last_packet_size) +{ + u16 actual_read_len; + u8 *read_buf = NULL; + u8 *data = NULL; + int data_offset = PIP2_RESP_STATUS_OFFSET; + int i = 1; + int j = 0; + int rc = 0; + + read_buf = kzalloc(PT_MAX_PIP2_MSG_SIZE, GFP_KERNEL); + if (!read_buf) + goto ping_test_exit; + + data = kzalloc(PT_MAX_PIP2_MSG_SIZE, GFP_KERNEL); + if (!data) + goto ping_test_exit; + + /* Load data payload with an array of walking 1's */ + for (i = 0; i < 255; i++) + data[i] = 0x01 << (i % 8); + + /* Send 'max_bytes' PING cmds using 'i' bytes as payload for each */ + for (i = 0; i <= max_bytes; i++) { + rc = _pt_request_pip2_send_cmd(dev, PT_CORE_CMD_UNPROTECTED, + PIP2_CMD_ID_PING, data, i, read_buf, + &actual_read_len); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: PING failed with %d byte payload\n", + __func__, i); + break; + } + /* Verify data returned matches data sent */ + for (j = 0; j < i; j++) { + if (read_buf[data_offset + j] != data[j]) { + pt_debug(dev, DL_DEBUG, + "%s: PING packet size %d: sent[%d]=0x%02X recv[%d]=0x%02X\n", + __func__, i, j, data[j], j, + read_buf[data_offset + j]); + goto ping_test_exit; + } + } + } + +ping_test_exit: + *last_packet_size = i - 1; + kfree(read_buf); + kfree(data); + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_ic_parse_input_hex + * + * SUMMARY: Parse a char data array as space delimited hex values into + * an int array. + * + * NOTE: _pt_ic_parse_input() function may have similar work while the type of + * buffer to store data is "u32". This function is still needed by the + * "command" sysfs node because the buffer type to store data is "u8". + * + * RETURN: Length of parsed data + * + * PARAMETERS: + * *dev - pointer to device structure + * *buf - pointer to buffer that holds the input array to parse + * buf_size - size of buf + * *ic_buf - pointer to array to store parsed data + * ic_buf_size - max size of ic_buf + ******************************************************************************/ +static int _pt_ic_parse_input_hex(struct device *dev, const char *buf, + size_t buf_size, u8 *ic_buf, size_t ic_buf_size) +{ + const char *pbuf = buf; + unsigned long value; + char scan_buf[PT_MAX_PIP2_MSG_SIZE + 1]; + u32 i = 0; + u32 j; + int last = 0; + int ret; + + pt_debug(dev, DL_DEBUG, + "%s: pbuf=%p buf=%p size=%zu %s=%d buf=%s\n", + __func__, pbuf, buf, buf_size, "scan buf size", + PT_MAX_PIP2_MSG_SIZE, buf); + + while (pbuf <= (buf + buf_size)) { + if (i >= PT_MAX_PIP2_MSG_SIZE) { + pt_debug(dev, DL_ERROR, "%s: %s size=%d max=%d\n", + __func__, "Max cmd size exceeded", i, + PT_MAX_PIP2_MSG_SIZE); + return -EINVAL; + } + if (i >= ic_buf_size) { + pt_debug(dev, DL_ERROR, "%s: %s size=%d buf_size=%zu\n", + __func__, "Buffer size exceeded", i, + ic_buf_size); + return -EINVAL; + } + while (((*pbuf == ' ') || (*pbuf == ',')) + && (pbuf < (buf + buf_size))) { + last = *pbuf; + pbuf++; + } + + if (pbuf >= (buf + buf_size)) + break; + + memset(scan_buf, 0, PT_MAX_PIP2_MSG_SIZE); + if ((last == ',') && (*pbuf == ',')) { + pt_debug(dev, DL_ERROR, "%s: %s \",,\" not allowed.\n", + __func__, "Invalid data format."); + return -EINVAL; + } + for (j = 0; j < (PT_MAX_PIP2_MSG_SIZE - 1) + && (pbuf < (buf + buf_size)) + && (*pbuf != ' ') + && (*pbuf != ','); j++) { + last = *pbuf; + scan_buf[j] = *pbuf++; + } + ret = kstrtoul(scan_buf, 16, &value); + if (ret < 0) { + pt_debug(dev, DL_ERROR, + "%s: %s '%s' %s%s i=%d r=%d\n", __func__, + "Invalid data format. ", scan_buf, + "Use \"0xHH,...,0xHH\"", " instead.", + i, ret); + return ret; + } + + ic_buf[i] = value; + pt_debug(dev, DL_DEBUG, "%s: item = %d, value = 0x%02lx", + __func__, i, value); + i++; + } + + return i; +} + +/******************************************************************************* + * FUNCTION: _pt_ic_parse_input + * + * SUMMARY: Parse user sysfs input data as a space or comma delimited list of + * hex values or dec values into an int array with the following rules: + * 1) Hex values must have a "0x" prefix for each element or the first element + * only + * 2) Dec values do not have any prefix + * 3) It is not allowed to have a mix of dec and hex values in the user input + * string + * + * RETURN: Number of values parsed + * + * PARAMETERS: + * *dev - pointer to device structure + * *buf - pointer to buffer that holds the input array to parse + * buf_size - size of buf + * *out_buf - pointer to array to store parsed data + * out_buf_size - max size of buffer to be stored + ******************************************************************************/ +static int _pt_ic_parse_input(struct device *dev, + const char *buf, size_t buf_size, + u32 *out_buf, size_t out_buf_size) +{ + const char *pbuf = buf; + unsigned long value; + char scan_buf[PT_MAX_PIP2_MSG_SIZE + 1]; + u32 i = 0; + u32 j; + int last = 0; + int ret = 0; + u8 str_base = 0; + + pt_debug(dev, DL_DEBUG, + "%s: in_buf_size=%zu out_buf_size=%zu %s=%d buf=%s\n", + __func__, buf_size, out_buf_size, "scan buf size", + PT_MAX_PIP2_MSG_SIZE, buf); + + while (pbuf <= (buf + buf_size)) { + if (i >= PT_MAX_PIP2_MSG_SIZE) { + pt_debug(dev, DL_ERROR, "%s: %s size=%d max=%d\n", + __func__, "Max cmd size exceeded", i, + PT_MAX_PIP2_MSG_SIZE); + ret = -EINVAL; + goto error; + } + if (i >= out_buf_size) { + pt_debug(dev, DL_ERROR, "%s: %s size=%d buf_size=%zu\n", + __func__, "Buffer size exceeded", i, + out_buf_size); + ret = -EINVAL; + goto error; + } + while (((*pbuf == ' ') || (*pbuf == ',')) + && (pbuf < (buf + buf_size))) { + last = *pbuf; + pbuf++; + } + + if (pbuf >= (buf + buf_size)) + break; + + memset(scan_buf, 0, PT_MAX_PIP2_MSG_SIZE); + if ((last == ',') && (*pbuf == ',')) { + pt_debug(dev, DL_ERROR, "%s: %s \",,\" not allowed.\n", + __func__, "Invalid data format."); + ret = -EINVAL; + goto error; + } + for (j = 0; j < (PT_MAX_PIP2_MSG_SIZE - 1) + && (pbuf < (buf + buf_size)) + && (*pbuf != ' ') + && (*pbuf != ','); j++) { + last = *pbuf; + scan_buf[j] = *pbuf++; + } + + if (i == 0) { + if ((strncmp(scan_buf, "0x", 2) == 0) || + (strncmp(scan_buf, "0X", 2) == 0)) + str_base = 16; + else + str_base = 10; + } else { + if (((strncmp(scan_buf, "0x", 2) == 0) || + (strncmp(scan_buf, "0X", 2) == 0)) && + (str_base == 10)) { + pt_debug(dev, DL_ERROR, + "%s: Decimal and Heximal data mixed\n", + __func__); + ret = -EINVAL; + goto error; + } + } + ret = kstrtoul(scan_buf, str_base, &value); + if (ret < 0) { + pt_debug(dev, DL_ERROR, + "%s: %s '%s' %s%s i=%d r=%d\n", __func__, + "Invalid data format. ", scan_buf, + "Use \"0xHH,...,0xHH\" or \"DD DD DD ... DD\"", + " instead.", i, ret); + goto error; + } + + out_buf[i] = value; + pt_debug(dev, DL_DEBUG, "%s: item = %d, value = 0x%02lx(%lu)", + __func__, i, value, value); + i++; + } + ret = i; +error: + return ret; +} + +#ifdef TTHE_TUNER_SUPPORT +/******************************************************************************* + * FUNCTION: tthe_debugfs_open + * + * SUMMARY: Open method for tthe_tuner debugfs node. On some hosts the size of + * PT_MAX_PRBUF_SIZE (equal to PAGE_SIZE) is not large enough to handle + * printing a large number of fingers and sensor data without overflowing + * the buffer. tthe_tuner needs ~4K and so the buffer is sized to some + * even multiple of PAGE_SIZE + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *inode - file inode number + * *filp - file pointer to debugfs file + ******************************************************************************/ +static int tthe_debugfs_open(struct inode *inode, struct file *filp) +{ + struct pt_core_data *cd = inode->i_private; + u32 buf_size = PT_MAX_PRBUF_SIZE; + + filp->private_data = inode->i_private; + if (cd->tthe_buf) + return -EBUSY; + + while (buf_size < 4096) + buf_size = buf_size << 1; + + pt_debug(cd->dev, DL_INFO, "%s:PT_MAX_BRBUF_SIZE=%d buf_size=%d\n", + __func__, (int)PT_MAX_PRBUF_SIZE, (int)buf_size); + + cd->tthe_buf_size = buf_size; + cd->tthe_buf = kzalloc(cd->tthe_buf_size, GFP_KERNEL); + if (!cd->tthe_buf) + return -ENOMEM; + + return 0; +} + +/******************************************************************************* + * FUNCTION: tthe_debugfs_close + * + * SUMMARY: Close method for tthe_tuner debugfs node. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *inode - file inode number + * *filp - file pointer to debugfs file + ******************************************************************************/ +static int tthe_debugfs_close(struct inode *inode, struct file *filp) +{ + struct pt_core_data *cd = filp->private_data; + + filp->private_data = NULL; + kfree(cd->tthe_buf); + cd->tthe_buf = NULL; + + return 0; +} + +/******************************************************************************* + * FUNCTION: tthe_debugfs_read + * + * SUMMARY: Read method for tthe_tuner debugfs node. This function prints + * tthe_buf to user buffer. + * + * RETURN: Size of debugfs data print + * + * PARAMETERS: + * *filp - file pointer to debugfs file + * *buf - the user space buffer to read to + * count - the maximum number of bytes to read + * *ppos - the current position in the buffer + ******************************************************************************/ +static ssize_t tthe_debugfs_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct pt_core_data *cd = filp->private_data; + int size; + int ret; + static int partial_read; + + wait_event_interruptible(cd->wait_q, + cd->tthe_buf_len != 0 || cd->tthe_exit); + mutex_lock(&cd->tthe_lock); + if (cd->tthe_exit) { + mutex_unlock(&cd->tthe_lock); + return 0; + } + if (count > cd->tthe_buf_len) + size = cd->tthe_buf_len; + else + size = count; + if (!size) { + mutex_unlock(&cd->tthe_lock); + return 0; + } + + if (partial_read) { + ret = copy_to_user(buf, cd->tthe_buf + partial_read, size); + partial_read = 0; + } else { + ret = copy_to_user(buf, cd->tthe_buf, size); + } + if (size == count) + partial_read = count; + + if (ret == size) + return -EFAULT; + size -= ret; + cd->tthe_buf_len -= size; + mutex_unlock(&cd->tthe_lock); + *ppos += size; + return size; +} + +static const struct file_operations tthe_debugfs_fops = { + .open = tthe_debugfs_open, + .release = tthe_debugfs_close, + .read = tthe_debugfs_read, +}; +#endif + +static struct pt_core_nonhid_cmd _pt_core_nonhid_cmd = { + .start_bl = _pt_request_pip_start_bl, + .suspend_scanning = _pt_request_pip_suspend_scanning, + .resume_scanning = _pt_request_pip_resume_scanning, + .get_param = _pt_request_pip_get_param, + .set_param = _pt_request_pip_set_param, + .verify_cfg_block_crc = _pt_request_pip_verify_config_block_crc, + .get_config_row_size = _pt_request_pip_get_config_row_size, + .get_data_structure = _pt_request_pip_get_data_structure, + .run_selftest = _pt_request_pip_run_selftest, + .get_selftest_result = _pt_request_pip_get_selftest_result, + .load_self_test_param = _pt_request_pip_load_self_test_param, + .calibrate_idacs = _pt_request_pip_calibrate_idacs, + .calibrate_ext = _pt_request_pip_calibrate_ext, + .initialize_baselines = _pt_request_pip_initialize_baselines, + .exec_panel_scan = _pt_request_pip_exec_panel_scan, + .retrieve_panel_scan = _pt_request_pip_retrieve_panel_scan, + .read_data_block = _pt_request_pip_read_data_block, + .write_data_block = _pt_request_pip_write_data_block, + .user_cmd = _pt_request_pip_user_cmd, + .get_bl_info = _pt_request_pip_bl_get_information, + .initiate_bl = _pt_request_pip_bl_initiate_bl, + .launch_app = _pt_request_pip_launch_app, + .prog_and_verify = _pt_request_pip_bl_program_and_verify, + .verify_app_integrity = _pt_request_pip_bl_verify_app_integrity, + .get_panel_id = _pt_request_pip_bl_get_panel_id, + .pip2_send_cmd = _pt_request_pip2_send_cmd, + .pip2_send_cmd_no_int = _pt_pip2_send_cmd_no_int, + .pip2_file_open = _pt_pip2_file_open, + .pip2_file_close = _pt_pip2_file_close, + .pip2_file_erase = _pt_pip2_file_erase, + .read_us_file = _pt_read_us_file, + .manage_cal_data = _pt_manage_local_cal_data, + .calc_crc = crc_ccitt_calculate, +#ifdef TTDL_DIAGNOSTICS + .pip2_file_read = _pt_pip2_file_read, + .pip2_file_seek_offset = _pt_pip2_file_seek_offset, + .pip2_file_get_stats = _pt_pip2_file_get_stats, + .pip2_file_crc = _pt_pip2_file_crc, +#endif +}; + +static struct pt_core_commands _pt_core_commands = { + .subscribe_attention = _pt_subscribe_attention, + .unsubscribe_attention = _pt_unsubscribe_attention, + .request_exclusive = _pt_request_exclusive, + .release_exclusive = _pt_release_exclusive, + .request_reset = _pt_request_reset, + .request_pip2_launch_app = _pt_request_pip2_launch_app, + .request_enum = _pt_request_enum, + .request_sysinfo = _pt_request_sysinfo, + .request_loader_pdata = _pt_request_loader_pdata, + .request_stop_wd = _pt_request_stop_wd, + .request_start_wd = _pt_request_start_wd, + .request_get_mode = _pt_request_get_mode, + .request_active_pip_prot = _pt_request_active_pip_protocol, + .request_pip2_get_mode_sysmode = _pt_request_pip2_get_mode_sysmode, + .request_pip2_enter_bl = _pt_request_pip2_enter_bl, + .request_pip2_bin_hdr = _pt_request_pip2_bin_hdr, + .request_dut_generation = _pt_request_dut_generation, + .request_hw_version = _pt_request_hw_version, + .parse_sysfs_input = _pt_ic_parse_input, +#ifdef TTHE_TUNER_SUPPORT + .request_tthe_print = _pt_request_tthe_print, +#endif +#ifdef TTDL_DIAGNOSTICS + .request_toggle_err_gpio = _pt_request_toggle_err_gpio, +#endif + .nonhid_cmd = &_pt_core_nonhid_cmd, + .request_get_fw_mode = _pt_request_get_fw_sys_mode, +}; + +struct pt_core_commands *pt_get_commands(void) +{ + return &_pt_core_commands; +} +EXPORT_SYMBOL_GPL(pt_get_commands); + +static DEFINE_MUTEX(core_list_lock); +static LIST_HEAD(core_list); +static DEFINE_MUTEX(module_list_lock); +static LIST_HEAD(module_list); +static int core_number; + +/******************************************************************************* + * FUNCTION: pt_probe_module + * + * SUMMARY: Add the module pointer to module_node and call the probe pointer. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to core data + * *module - pointer to module structure + ******************************************************************************/ +static int pt_probe_module(struct pt_core_data *cd, + struct pt_module *module) +{ + struct module_node *module_node; + int rc = 0; + + module_node = kzalloc(sizeof(*module_node), GFP_KERNEL); + if (!module_node) + return -ENOMEM; + + module_node->module = module; + + mutex_lock(&cd->module_list_lock); + list_add(&module_node->node, &cd->module_list); + mutex_unlock(&cd->module_list_lock); + + rc = module->probe(cd->dev, &module_node->data); + if (rc) { + /* + * Remove from the list when probe fails + * in order not to call release + */ + mutex_lock(&cd->module_list_lock); + list_del(&module_node->node); + mutex_unlock(&cd->module_list_lock); + kfree(module_node); + goto exit; + } + +exit: + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_release_module + * + * SUMMARY: Call the release pointer and remove the module pointer from + * module_list. + * + * PARAMETERS: + * *cd - pointer to core data + * *module - pointer to module structure + ******************************************************************************/ +static void pt_release_module(struct pt_core_data *cd, + struct pt_module *module) +{ + struct module_node *m, *m_n; + + mutex_lock(&cd->module_list_lock); + list_for_each_entry_safe(m, m_n, &cd->module_list, node) + if (m->module == module) { + module->release(cd->dev, m->data); + list_del(&m->node); + kfree(m); + break; + } + mutex_unlock(&cd->module_list_lock); +} + +/******************************************************************************* + * FUNCTION: pt_probe_modules + * + * SUMMARY: Iterate module_list and probe each module. + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static void pt_probe_modules(struct pt_core_data *cd) +{ + struct pt_module *m; + int rc = 0; + + mutex_lock(&module_list_lock); + list_for_each_entry(m, &module_list, node) { + pt_debug(cd->dev, DL_ERROR, "%s: Probe module %s\n", + __func__, m->name); + rc = pt_probe_module(cd, m); + if (rc) + pt_debug(cd->dev, DL_ERROR, + "%s: Probe fails for module %s\n", + __func__, m->name); + } + mutex_unlock(&module_list_lock); +} + +/******************************************************************************* + * FUNCTION: pt_release_modules + * + * SUMMARY: Iterate module_list and remove each module. + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static void pt_release_modules(struct pt_core_data *cd) +{ + struct pt_module *m; + + mutex_lock(&module_list_lock); + list_for_each_entry(m, &module_list, node) + pt_release_module(cd, m); + mutex_unlock(&module_list_lock); +} + +/******************************************************************************* + * FUNCTION: pt_get_core_data + * + * SUMMARY: Iterate core_list and get core data. + * + * RETURN: + * pointer to core data or null pointer if fail + * + * PARAMETERS: + * *id - pointer to core id + ******************************************************************************/ +struct pt_core_data *pt_get_core_data(char *id) +{ + struct pt_core_data *d; + + list_for_each_entry(d, &core_list, node) + if (!strncmp(d->core_id, id, 20)) + return d; + return NULL; +} +EXPORT_SYMBOL_GPL(pt_get_core_data); + +/******************************************************************************* + * FUNCTION: pt_add_core + * + * SUMMARY: Add core data to the core_list. + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static void pt_add_core(struct device *dev) +{ + struct pt_core_data *d; + struct pt_core_data *cd = dev_get_drvdata(dev); + + mutex_lock(&core_list_lock); + list_for_each_entry(d, &core_list, node) + if (d->dev == dev) + goto unlock; + + list_add(&cd->node, &core_list); +unlock: + mutex_unlock(&core_list_lock); +} + +/******************************************************************************* + * FUNCTION: pt_del_core + * + * SUMMARY: Remove core data from the core_list. + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static void pt_del_core(struct device *dev) +{ + struct pt_core_data *d, *d_n; + + mutex_lock(&core_list_lock); + list_for_each_entry_safe(d, d_n, &core_list, node) + if (d->dev == dev) { + list_del(&d->node); + goto unlock; + } +unlock: + mutex_unlock(&core_list_lock); +} + +/******************************************************************************* + * FUNCTION: pt_register_module + * + * SUMMARY: Register the module to module_list and probe the module for each + * core. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *module - pointer to module structure + ******************************************************************************/ +int pt_register_module(struct pt_module *module) +{ + struct pt_module *m; + struct pt_core_data *cd; + + int rc = 0; + + if (!module || !module->probe || !module->release) + return -EINVAL; + + mutex_lock(&module_list_lock); + list_for_each_entry(m, &module_list, node) + if (m == module) { + rc = -EEXIST; + goto unlock; + } + + list_add(&module->node, &module_list); + + /* Probe the module for each core */ + mutex_lock(&core_list_lock); + list_for_each_entry(cd, &core_list, node) + pt_probe_module(cd, module); + mutex_unlock(&core_list_lock); + +unlock: + mutex_unlock(&module_list_lock); + return rc; +} +EXPORT_SYMBOL_GPL(pt_register_module); + +/******************************************************************************* + * FUNCTION: pt_unregister_module + * + * SUMMARY: Release the module for each core and remove the module from + * module_list. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *module - pointer to module structure + ******************************************************************************/ +void pt_unregister_module(struct pt_module *module) +{ + struct pt_module *m, *m_n; + struct pt_core_data *cd; + + if (!module) + return; + + mutex_lock(&module_list_lock); + + /* Release the module for each core */ + mutex_lock(&core_list_lock); + list_for_each_entry(cd, &core_list, node) + pt_release_module(cd, module); + mutex_unlock(&core_list_lock); + + list_for_each_entry_safe(m, m_n, &module_list, node) + if (m == module) { + list_del(&m->node); + break; + } + + mutex_unlock(&module_list_lock); +} +EXPORT_SYMBOL_GPL(pt_unregister_module); + +/******************************************************************************* + * FUNCTION: pt_get_module_data + * + * SUMMARY: Get module data from module_node by module_list. + * + * RETURN: + * pointer to module data + * + * PARAMETERS: + * *dev - pointer to device structure + * *module - pointer to module structure + ******************************************************************************/ +void *pt_get_module_data(struct device *dev, struct pt_module *module) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct module_node *m; + void *data = NULL; + + mutex_lock(&cd->module_list_lock); + list_for_each_entry(m, &cd->module_list, node) + if (m->module == module) { + data = m->data; + break; + } + mutex_unlock(&cd->module_list_lock); + + return data; +} +EXPORT_SYMBOL(pt_get_module_data); + +#ifdef CONFIG_HAS_EARLYSUSPEND +/******************************************************************************* + * FUNCTION: pt_early_suspend + * + * SUMMARY: Android PM architecture function that will call "PT_ATTEN_SUSPEND" + * attention list. + * + * PARAMETERS: + * *h - pointer to early_suspend structure + ******************************************************************************/ +static void pt_early_suspend(struct early_suspend *h) +{ + struct pt_core_data *cd = + container_of(h, struct pt_core_data, es); + + call_atten_cb(cd, PT_ATTEN_SUSPEND, 0); +} +/******************************************************************************* + * FUNCTION: pt_late_resume + * + * SUMMARY: Android PM architecture function that will call "PT_ATTEN_RESUME" + * attention list. + * + * PARAMETERS: + * *h - pointer to early_suspend structure + ******************************************************************************/ +static void pt_late_resume(struct early_suspend *h) +{ + struct pt_core_data *cd = + container_of(h, struct pt_core_data, es); + + call_atten_cb(cd, PT_ATTEN_RESUME, 0); +} + +/******************************************************************************* + * FUNCTION: pt_setup_early_suspend + * + * SUMMARY: Register early/suspend function to the system. + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static void pt_setup_early_suspend(struct pt_core_data *cd) +{ + cd->es.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; + cd->es.suspend = pt_early_suspend; + cd->es.resume = pt_late_resume; + + register_early_suspend(&cd->es); +} +#elif defined(CONFIG_DRM) + +static void pt_resume_work(struct work_struct *work) +{ + struct pt_core_data *pt_data = container_of(work, struct pt_core_data, + resume_work); + int rc = 0; + + pt_debug(pt_data->dev, DL_INFO, "%s start ", __func__); + if (pt_data->cpdata->flags & PT_CORE_FLAG_SKIP_RUNTIME) + return; + + rc = pt_core_easywake_off(pt_data); + if (rc < 0) { + pt_debug(pt_data->dev, DL_ERROR, + "%s: Error on wake\n", __func__); + } + pt_debug(pt_data->dev, DL_INFO, "%s touch to wake disabled ", __func__); + return; +} + +static void pt_suspend_work(struct work_struct *work) +{ + struct pt_core_data *pt_data = container_of(work, struct pt_core_data, + suspend_work); + int rc = 0; + + pt_debug(pt_data->dev, DL_INFO, "%s start\n", __func__); + + if (pt_data->cpdata->flags & PT_CORE_FLAG_SKIP_RUNTIME) + return; + + rc = pt_core_easywake_on(pt_data); + if (rc < 0) { + pt_debug(pt_data->dev, DL_ERROR, "%s: Error on sleep\n", __func__); + return; + } + pt_debug(pt_data->dev, DL_INFO, "%s Exit touch to wake enabled\n", __func__); + return; +} + +#if defined(CONFIG_PANEL_NOTIFIER) +/******************************************************************************* + * FUNCTION: panel_event_notifier_callback + * + * SUMMARY: Call back function for Panel Event notifier to allow to call + * resume/suspend attention list. + * + * PARAMETERS: + * tag - type of input panel. + * *notification - pointer to notification details. + * *client_data - pointer to core data + ******************************************************************************/ +static void panel_event_notifier_callback(enum panel_event_notifier_tag tag, + struct panel_event_notification *notification, void *client_data) +{ + struct pt_core_data *cd = client_data; + + if(!notification) + { + pt_debug(cd->dev,DL_INFO, "%s: Invalid notification\n", __func__); + return; + } + + pt_debug(cd->dev, DL_INFO, "%s: DRM notifier called!\n", __func__); + if (cd->quick_boot || cd->drv_debug_suspend) + goto exit; + + pt_debug(cd->dev, DL_INFO, "%s: DRM event:%d,fb_state %d", + __func__, notification->notif_type, cd->fb_state); + pt_debug(cd->dev, DL_INFO, "%s: DRM Power - %s - FB state %d ", + __func__, (notification->notif_type == DRM_PANEL_EVENT_UNBLANK)?"UP":"DOWN", cd->fb_state); + + if (notification->notif_type == DRM_PANEL_EVENT_UNBLANK) { + pt_debug(cd->dev, DL_INFO, "%s: UNBLANK!\n", __func__); + if (notification->notif_data.early_trigger) { + pr_err("%s: resume: event = %d, not care\n", __func__, notification->notif_type); + pt_debug(cd->dev, DL_INFO, "%s: resume: event = %d, not care\n", + __func__, notification->notif_type); + } else { + pt_debug(cd->dev, DL_INFO, "%s: Resume notifier called!\n", + __func__); + cancel_work_sync(&cd->resume_offload_work); + cancel_work_sync(&cd->suspend_offload_work); + cancel_work_sync(&cd->resume_work); + cancel_work_sync(&cd->suspend_work); + + queue_work(cd->pt_workqueue, &cd->resume_work); +#if defined(CONFIG_PM_SLEEP) + pt_debug(cd->dev, DL_INFO, "%s: Resume notifier called!\n", + __func__); + if (cd->cpdata->flags & PT_CORE_FLAG_SKIP_RUNTIME) + pt_core_resume_(cd->dev); +#endif + cd->fb_state = FB_ON; + pt_debug(cd->dev, DL_INFO, "%s: Resume notified!\n", __func__); + } + } else if (notification->notif_type == DRM_PANEL_EVENT_BLANK) { + pt_debug(cd->dev, DL_INFO, "%s: BLANK!\n", __func__); + if (notification->notif_data.early_trigger) { +#if defined(CONFIG_PM_SLEEP) + pt_debug(cd->dev, DL_INFO, "%s: Suspend notifier called!\n", + __func__); + if (cd->cpdata->flags & PT_CORE_FLAG_SKIP_RUNTIME) + pt_core_suspend_(cd->dev); +#endif + cancel_work_sync(&cd->resume_offload_work); + cancel_work_sync(&cd->suspend_offload_work); + cancel_work_sync(&cd->resume_work); + cancel_work_sync(&cd->suspend_work); + + queue_work(cd->pt_workqueue, &cd->suspend_work); + cd->fb_state = FB_OFF; + pt_debug(cd->dev, DL_INFO, "%s: Suspend notified!\n", __func__); + } else { + pt_debug(cd->dev, DL_INFO, "%s: suspend: event = %d, not care\n", + __func__, notification->notif_type); + } + } else if (notification->notif_type == DRM_PANEL_EVENT_BLANK_LP) { + pt_debug(cd->dev, DL_INFO, "%s: LOWPOWER!\n", __func__); + if (notification->notif_data.early_trigger) { +#if defined(CONFIG_PM_SLEEP) + pt_debug(cd->dev, DL_INFO, "%s: Suspend notifier called!\n", __func__); + if (cd->cpdata->flags & PT_CORE_FLAG_SKIP_RUNTIME) + pt_core_suspend_(cd->dev); +#endif + cancel_work_sync(&cd->resume_offload_work); + cancel_work_sync(&cd->suspend_offload_work); + cancel_work_sync(&cd->resume_work); + cancel_work_sync(&cd->suspend_work); + + queue_work(cd->pt_workqueue, &cd->suspend_work); + cd->fb_state = FB_OFF; + pt_debug(cd->dev, DL_INFO, "%s: Suspend notified!\n", __func__); + } else { + pt_debug(cd->dev, DL_INFO, "%s: suspend: event = %d, not care\n", + __func__, notification->notif_type); + } + + } else { + pt_debug(cd->dev, DL_INFO, "%s: DRM BLANK(%d) do not need process\n", + __func__, notification->notif_type); + } +exit: + return; +} + +/******************************************************************************* + * FUNCTION: pt_setup_panel_event_notifier + * + * SUMMARY: Set up call back function into drm notifier. + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static void pt_setup_panel_event_notifier(struct pt_core_data *cd) +{ + void *cookie = NULL; + + if (!active_panel) + pt_debug(cd->dev, DL_ERROR, + "%s: Active panel not registered!\n", __func__); + + cd->pt_workqueue = create_singlethread_workqueue("ts_wq"); + if (!cd->pt_workqueue) { + pt_debug(cd->dev, DL_ERROR, + "%s: worker thread creation failed !\n", __func__); + } + + if (cd->pt_workqueue) { + INIT_WORK(&cd->resume_work, pt_resume_work); + INIT_WORK(&cd->suspend_work, pt_suspend_work); + } + + + cookie = panel_event_notifier_register(PANEL_EVENT_NOTIFICATION_PRIMARY, + PANEL_EVENT_NOTIFIER_CLIENT_PRIMARY_TOUCH, + active_panel,&panel_event_notifier_callback, cd); + + if (active_panel && !cookie) + { + pt_debug(cd->dev, DL_ERROR, + "%s: Register notifier failed!\n", __func__); + } + cd->entry = cookie; +} +#else + +/******************************************************************************* + * FUNCTION: drm_notifier_callback + * + * SUMMARY: Call back function for DRM notifier to allow to call + * resume/suspend attention list. + * + * RETURN: + * 0 = success + * + * PARAMETERS: + * *self - pointer to notifier_block structure + * event - event type of fb notifier + * *data - pointer to fb_event structure + ******************************************************************************/ +static int drm_notifier_callback(struct notifier_block *self, + unsigned long event, void *data) +{ + struct pt_core_data *cd = + container_of(self, struct pt_core_data, fb_notifier); + struct drm_panel_notifier *evdata = data; + int *blank; + + pt_debug(cd->dev, DL_INFO, "%s: DRM notifier called!\n", __func__); + + if (!evdata) + goto exit; + + if (!(event == DRM_PANEL_EARLY_EVENT_BLANK || + event == DRM_PANEL_EVENT_BLANK)) { + pt_debug(cd->dev, DL_INFO, "%s: Event(%lu) do not need process\n", + __func__, event); + goto exit; + } + + if (cd->quick_boot || cd->drv_debug_suspend) + goto exit; + + blank = evdata->data; + pt_debug(cd->dev, DL_INFO, "%s: DRM event:%lu,blank:%d fb_state %d sleep state %d ", + __func__, event, *blank, cd->fb_state, cd->sleep_state); + pt_debug(cd->dev, DL_INFO, "%s: DRM Power - %s - FB state %d ", + __func__, (*blank == DRM_PANEL_BLANK_UNBLANK)?"UP":"DOWN", cd->fb_state); + + if (*blank == DRM_PANEL_BLANK_UNBLANK) { + pt_debug(cd->dev, DL_INFO, "%s: UNBLANK!\n", __func__); + if (event == DRM_PANEL_EARLY_EVENT_BLANK) { + pt_debug(cd->dev, DL_INFO, "%s: resume: event = %lu, not care\n", + __func__, event); + } else if (event == DRM_PANEL_EVENT_BLANK) { + if (cd->fb_state != FB_ON) { + pt_debug(cd->dev, DL_INFO, "%s: Resume notifier called!\n", + __func__); + cancel_work_sync(&cd->resume_offload_work); + cancel_work_sync(&cd->suspend_offload_work); + cancel_work_sync(&cd->resume_work); + cancel_work_sync(&cd->suspend_work); + + queue_work(cd->pt_workqueue, &cd->resume_work); +#if defined(CONFIG_PM_SLEEP) + pt_debug(cd->dev, DL_INFO, "%s: Resume notifier called!\n", + __func__); + if (cd->cpdata->flags & PT_CORE_FLAG_SKIP_RUNTIME) + pt_core_resume_(cd->dev); +#endif + cd->fb_state = FB_ON; + pt_debug(cd->dev, DL_INFO, "%s: Resume notified!\n", __func__); + } + } + } else if (*blank == DRM_PANEL_BLANK_LP || *blank == DRM_PANEL_BLANK_POWERDOWN) { + pt_debug(cd->dev, DL_INFO, "%s: LOWPOWER!\n", __func__); + if (event == DRM_PANEL_EARLY_EVENT_BLANK) { + if (cd->fb_state != FB_OFF) { +#if defined(CONFIG_PM_SLEEP) + pt_debug(cd->dev, DL_INFO, "%s: Suspend notifier called!\n", + __func__); + if (cd->cpdata->flags & PT_CORE_FLAG_SKIP_RUNTIME) + pt_core_suspend_(cd->dev); +#endif + cancel_work_sync(&cd->resume_offload_work); + cancel_work_sync(&cd->suspend_offload_work); + cancel_work_sync(&cd->resume_work); + cancel_work_sync(&cd->suspend_work); + + queue_work(cd->pt_workqueue, &cd->suspend_work); + cd->fb_state = FB_OFF; + pt_debug(cd->dev, DL_INFO, "%s: Suspend notified!\n", __func__); + } + + } else if (event == DRM_PANEL_EVENT_BLANK) { + pt_debug(cd->dev, DL_INFO, "%s: suspend: event = %lu, not care\n", + __func__, event); + } + } else { + pt_debug(cd->dev, DL_INFO, "%s: DRM BLANK(%d) do not need process\n", + __func__, *blank); + } +exit: + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_setup_drm_notifier + * + * SUMMARY: Set up call back function into drm notifier. + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static void pt_setup_drm_notifier(struct pt_core_data *cd) +{ + cd->fb_state = FB_NONE; + cd->fb_notifier.notifier_call = drm_notifier_callback; + pt_debug(cd->dev, DL_INFO, "%s: Setting up drm notifier\n", __func__); + + if (!active_panel) + pt_debug(cd->dev, DL_ERROR, + "%s: Active panel not registered!\n", __func__); + + cd->pt_workqueue = create_singlethread_workqueue("ts_wq"); + if (!cd->pt_workqueue) { + pt_debug(cd->dev, DL_ERROR, + "%s: worker thread creation failed !\n", __func__); + } + + if (cd->pt_workqueue) { + INIT_WORK(&cd->resume_work, pt_resume_work); + INIT_WORK(&cd->suspend_work, pt_suspend_work); + } + + if (active_panel && + drm_panel_notifier_register(active_panel, + &cd->fb_notifier) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: Register notifier failed!\n", __func__); +} +#endif +#elif defined(CONFIG_FB) +/******************************************************************************* + * FUNCTION: fb_notifier_callback + * + * SUMMARY: Call back function for FrameBuffer notifier to allow to call + * resume/suspend attention list. + * + * RETURN: + * 0 = success + * + * PARAMETERS: + * *self - pointer to notifier_block structure + * event - event type of fb notifier + * *data - pointer to fb_event structure + ******************************************************************************/ +static int fb_notifier_callback(struct notifier_block *self, + unsigned long event, void *data) +{ + struct pt_core_data *cd = + container_of(self, struct pt_core_data, fb_notifier); + struct fb_event *evdata = data; + int *blank; + + if (event != FB_EVENT_BLANK || !evdata) + goto exit; + + blank = evdata->data; + if (*blank == FB_BLANK_UNBLANK) { + pt_debug(cd->dev, DL_INFO, "%s: UNBLANK!\n", __func__); + if (cd->fb_state != FB_ON) { + call_atten_cb(cd, PT_ATTEN_RESUME, 0); +#if defined(CONFIG_PM_SLEEP) + if (cd->cpdata->flags & PT_CORE_FLAG_SKIP_RUNTIME) + pt_core_resume_(cd->dev); +#endif + cd->fb_state = FB_ON; + } + } else if (*blank == FB_BLANK_POWERDOWN) { + pt_debug(cd->dev, DL_INFO, "%s: POWERDOWN!\n", __func__); + if (cd->fb_state != FB_OFF) { +#if defined(CONFIG_PM_SLEEP) + if (cd->cpdata->flags & PT_CORE_FLAG_SKIP_RUNTIME) + pt_core_suspend_(cd->dev); +#endif + call_atten_cb(cd, PT_ATTEN_SUSPEND, 0); + cd->fb_state = FB_OFF; + } + } + +exit: + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_setup_fb_notifier + * + * SUMMARY: Set up call back function into fb notifier. + * + * PARAMETERS: + * *cd - pointer to core data + ******************************************************************************/ +static void pt_setup_fb_notifier(struct pt_core_data *cd) +{ + int rc = 0; + + cd->fb_state = FB_ON; + cd->fb_notifier.notifier_call = fb_notifier_callback; + + rc = fb_register_client(&cd->fb_notifier); + if (rc) + pt_debug(cd->dev, DL_ERROR, + "Unable to register fb_notifier: %d\n", rc); +} +#endif + +/******************************************************************************* + * FUNCTION: pt_watchdog_work + * + * SUMMARY: This is where the watchdog work is done except if the DUT is + * sleeping then this function simply returns. If the DUT is awake the + * first thing is to ensure the IRQ is not stuck asserted meaning that + * somehow a response is waiting on the DUT that has not been read. If + * this occurs the message is simply consumed. If or once the IRQ is + * cleared, a PIP PING message is sent to the DUT and if the response + * is received the watchdog succeeds and exits, if no response is seen + * a startup is queued unless the maximum number of startups have already + * been attempted, in that case a BL is attempted. + * + * NOTE: pt_stop_wd_timer() cannot be called within the context of this + * work thread + * + * RETURN: void + * + * PARAMETERS: + * *work - pointer to a work structure for the watchdog work queue + ******************************************************************************/ +static void pt_watchdog_work(struct work_struct *work) +{ + int rc = 0; + struct pt_core_data *cd = container_of(work, + struct pt_core_data, watchdog_work); + /* + * if found the current sleep_state is SS_SLEEPING + * then no need to request_exclusive, directly return + */ + if (cd->sleep_state == SS_SLEEPING) + return; + +#ifdef TTDL_DIAGNOSTICS + cd->watchdog_count++; +#endif /* TTDL_DIAGNOSTICS */ + + /* + * The first WD interval was extended to allow DDI to come up. + * If the WD interval is not the default then adjust timer to the + * current setting. The user can override value with drv_debug sysfs. + */ + if (cd->watchdog_interval != PT_WATCHDOG_TIMEOUT) { + mod_timer_pending(&cd->watchdog_timer, jiffies + + msecs_to_jiffies(cd->watchdog_interval)); + } + + if (pt_check_irq_asserted(cd)) { +#ifdef TTDL_DIAGNOSTICS + cd->watchdog_irq_stuck_count++; + pt_toggle_err_gpio(cd, PT_ERR_GPIO_IRQ_STUCK); +#endif /* TTDL_DIAGNOSTICS */ + pt_debug(cd->dev, DL_ERROR, + "%s: TTDL WD found IRQ asserted, attempt to clear\n", + __func__); + pt_flush_bus(cd, PT_FLUSH_BUS_BASED_ON_LEN, NULL); + } + + rc = request_exclusive(cd, cd->dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + goto queue_startup; + } + + rc = pt_pip_null_(cd); + + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", __func__); + +queue_startup: + if (rc) { +#ifdef TTDL_DIAGNOSTICS + cd->watchdog_failed_access_count++; + pt_toggle_err_gpio(cd, PT_ERR_GPIO_EXCLUSIVE_ACCESS); +#endif /* TTDL_DIAGNOSTICS */ + pt_debug(cd->dev, DL_ERROR, + "%s: failed to access device in WD, retry count=%d\n", + __func__, cd->startup_retry_count); + + /* Already tried FW upgrade because of watchdog but failed */ + if (cd->startup_retry_count > PT_WATCHDOG_RETRY_COUNT) + return; + + if (cd->startup_retry_count++ < PT_WATCHDOG_RETRY_COUNT) { + /* + * Any wrapper function that trys to disable the + * WD killing this worker cannot be called here. + */ + rc = request_exclusive(cd, cd->dev, + PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + goto exit; + } + + cd->hw_detected = false; + cd->startup_status = STARTUP_STATUS_START; + pt_debug(cd->dev, DL_DEBUG, + "%s: Startup Status Reset\n", __func__); + rc = pt_dut_reset_and_wait(cd); + if (release_exclusive(cd, cd->dev) < 0) + pt_debug(cd->dev, DL_ERROR, + "%s: fail to release exclusive\n", + __func__); + if (!rc) { + cd->hw_detected = true; + if (!cd->flashless_dut) + pt_queue_enum(cd); + } +#ifdef TTDL_DIAGNOSTICS + cd->wd_xres_count++; + pt_debug(cd->dev, DL_ERROR, + "%s: Comm Failed - DUT reset [#%d]\n", + __func__, cd->wd_xres_count); +#endif /* TTDL_DIAGNOSTICS */ + } else { + /* + * After trying PT_WATCHDOG_RETRY_COUNT times to + * reset the part to regain communications, try to BL + */ + pt_debug(cd->dev, DL_ERROR, + "%s: WD DUT access failure, Start FW Upgrade\n", + __func__); +#ifdef TTDL_DIAGNOSTICS + /* + * When diagnostics is enabled allow TTDL to keep + * trying to find the DUT. This allows the DUT to be + * hot swap-able while the host stays running. In + * production this may not be wanted as a customer + * may have several touch drivers and any driver + * that doesn't match the current DUT should give + * up trying and give up using the bus. + */ + pt_debug(cd->dev, DL_INFO, + "%s: Resetting startup_retry_count\n", + __func__); + cd->startup_retry_count = 0; +#endif /* TTDL_DIAGNOSTICS */ + /* + * Since fw may be broken,reset sysinfo ready flag + * to let upgrade function work. + */ + mutex_lock(&cd->system_lock); + cd->sysinfo.ready = false; + mutex_unlock(&cd->system_lock); + + if (cd->active_dut_generation == DUT_UNKNOWN) { + pt_debug(cd->dev, DL_ERROR, + "%s: Queue Restart\n", __func__); + pt_queue_restart(cd); + } else + kthread_run(start_fw_upgrade, cd, "pt_loader"); + } + } else { + cd->hw_detected = true; + if (cd->startup_status <= (STARTUP_STATUS_FW_RESET_SENTINEL | + STARTUP_STATUS_BL_RESET_SENTINEL)) { + pt_debug(cd->dev, DL_ERROR, + "%s: HW detected but not enumerated\n", + __func__); + pt_queue_enum(cd); + } + } + +exit: + pt_start_wd_timer(cd); +} + +#if (KERNEL_VERSION(4, 14, 0) > LINUX_VERSION_CODE) +/******************************************************************************* + * FUNCTION: pt_watchdog_timer + * + * SUMMARY: The function that is called when the WD timer expires. If the + * watchdog work is not already busy schedule the watchdog work queue. + * + * RETURN: void + * + * PARAMETERS: + * handle - Handle to the watchdog timer + ******************************************************************************/ +static void pt_watchdog_timer(unsigned long handle) +{ + struct pt_core_data *cd = (struct pt_core_data *)handle; + if (!cd) + return; + + pt_debug(cd->dev, DL_DEBUG, "%s: Watchdog timer triggered\n", + __func__); + + if (!work_pending(&cd->watchdog_work)) + schedule_work(&cd->watchdog_work); +} +#else +/******************************************************************************* + * FUNCTION: pt_watchdog_timer + * + * SUMMARY: The function that is called when the WD timer expires. If the + * watchdog work is not already busy schedule the watchdog work queue. + * + * RETURN: void + * + * PARAMETERS: + * *t - Pointer to timer list + ******************************************************************************/ +static void pt_watchdog_timer(struct timer_list *t) +{ + struct pt_core_data *cd = from_timer(cd, t, watchdog_timer); + if (!cd) + return; + + pt_debug(cd->dev, DL_DEBUG, "%s: Watchdog timer triggered\n", + __func__); + + if (!work_pending(&cd->watchdog_work)) + schedule_work(&cd->watchdog_work); +} +#endif + + + + +/******************************************************************************* + * Core sysfs show and store functions + ******************************************************************************/ + +/******************************************************************************* + * FUNCTION: pt_hw_version_show + * + * SUMMARY: Gets the HW version for either PIP1.x or PIP2.x DUTS + * Output data format: [SiliconID].[RevID FamilyID].[PanelID] + * + * RETURN: size of data written to sysfs node + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes structure + * *buf - pointer to print output buffer + ******************************************************************************/ +static ssize_t pt_hw_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + _pt_request_hw_version(dev, cd->hw_version); + return scnprintf(buf, PT_MAX_PRBUF_SIZE, "%s\n", cd->hw_version); +} +static DEVICE_ATTR(hw_version, 0444, pt_hw_version_show, NULL); + +/******************************************************************************* + * FUNCTION: pt_drv_version_show + * + * SUMMARY: Show method for the drv_version sysfs node that will show the + * TTDL version information + * + * RETURN: Char buffer with printed TTDL version information + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to output buffer + ******************************************************************************/ +static ssize_t pt_drv_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return scnprintf(buf, PT_MAX_PRBUF_SIZE, + "Driver: %s\nVersion: %s\nDate: %s\n", + pt_driver_core_name, pt_driver_core_version, + pt_driver_core_date); +} +static DEVICE_ATTR(drv_version, 0444, pt_drv_version_show, NULL); +static DEVICE_ATTR(drv_ver, 0444, pt_drv_version_show, NULL); + +/******************************************************************************* + * FUNCTION: pt_fw_version_show + * + * SUMMARY: Show method for the fw_version sysfs node that will + * show the firmware, bootloader and PIP version information + * + * RETURN: Size of printed buffer + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to output buffer + ******************************************************************************/ +static ssize_t pt_fw_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_ttdata *ttdata; + int rc = 0; + + if (cd->mode == PT_MODE_OPERATIONAL) + rc = pt_hid_output_get_sysinfo_(cd); + + pt_debug(cd->dev, DL_INFO, "%s: mode = %d sysinfo.ready = %d\n", + __func__, cd->mode, cd->sysinfo.ready); + + if (cd->sysinfo.ready) + ttdata = &cd->sysinfo.ttdata; + else + rc = -ENODATA; + + if (!rc) { + return scnprintf(buf, strlen(buf), + "Status: %d\n" + "FW : %d.%d.%d\n" + "Config: %d\n" + "BL : %d.%d\n" + "PIP : %d.%d\n", + rc, + ttdata->fw_ver_major, ttdata->fw_ver_minor, + ttdata->revctrl, + ttdata->fw_ver_conf, + ttdata->bl_ver_major, ttdata->bl_ver_minor, + ttdata->pip_ver_major, ttdata->pip_ver_minor); + } else { + return scnprintf(buf, strlen(buf), + "Status: %d\n" + "FW : n/a\n" + "Config: n/a\n" + "BL : n/a\n" + "PIP : n/a\n", + rc); + } +} +static DEVICE_ATTR(fw_version, 0444, pt_fw_version_show, NULL); + +/******************************************************************************* + * FUNCTION: pt_sysinfo_show + * + * SUMMARY: Show method for the sysinfo sysfs node that will + * show all the information from get system information command. + * + * RETURN: Size of printed buffer + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to output buffer + ******************************************************************************/ +static ssize_t pt_sysinfo_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_sysinfo *si; + struct pt_ttdata *ttdata = NULL; + struct pt_sensing_conf_data *scd = NULL; + int rc = 0; + + if (cd->mode == PT_MODE_OPERATIONAL) { + rc = pt_hid_output_get_sysinfo_(cd); + if (cd->sysinfo.ready) { + si = &cd->sysinfo; + ttdata = &si->ttdata; + scd = &si->sensing_conf_data; + } else + rc = -ENODATA; + } else + rc = -EPERM; + + pt_debug(cd->dev, DL_INFO, "%s: mode = %d sysinfo.ready = %d\n", + __func__, cd->mode, cd->sysinfo.ready); + + if (!rc && ttdata && scd) { + return scnprintf(buf, strlen(buf), + "Status: %d\n" + "pip_ver_major: 0x%02X\n" + "pip_ver_minor: 0x%02X\n" + "fw_pid : 0x%04X\n" + "fw_ver_major : 0x%02X\n" + "fw_ver_minor : 0x%02X\n" + "revctrl : 0x%08X\n" + "fw_ver_conf : 0x%04X\n" + "bl_ver_major : 0x%02X\n" + "bl_ver_minor : 0x%02X\n" + "jtag_id_h : 0x%04X\n" + "jtag_id_l : 0x%04X\n" + "mfg_id[0] : 0x%02X\n" + "mfg_id[1] : 0x%02X\n" + "mfg_id[2] : 0x%02X\n" + "mfg_id[3] : 0x%02X\n" + "mfg_id[4] : 0x%02X\n" + "mfg_id[5] : 0x%02X\n" + "mfg_id[6] : 0x%02X\n" + "mfg_id[7] : 0x%02X\n" + "post_code : 0x%04X\n" + "electrodes_x : 0x%02X\n" + "electrodes_y : 0x%02X\n" + "len_x : 0x%04X\n" + "len_y : 0x%04X\n" + "res_x : 0x%04X\n" + "res_y : 0x%04X\n" + "max_z : 0x%04X\n" + "origin_x : 0x%02X\n" + "origin_y : 0x%02X\n" + "panel_id : 0x%02X\n" + "btn : 0x%02X\n" + "scan_mode : 0x%02X\n" + "max_num_of_tch_per_refresh_cycle: 0x%02X\n", + rc, + ttdata->pip_ver_major, + ttdata->pip_ver_minor, + ttdata->fw_pid, + ttdata->fw_ver_major, + ttdata->fw_ver_minor, + ttdata->revctrl, + ttdata->fw_ver_conf, + ttdata->bl_ver_major, + ttdata->bl_ver_minor, + ttdata->jtag_id_h, + ttdata->jtag_id_l, + ttdata->mfg_id[0], + ttdata->mfg_id[1], + ttdata->mfg_id[2], + ttdata->mfg_id[3], + ttdata->mfg_id[4], + ttdata->mfg_id[5], + ttdata->mfg_id[6], + ttdata->mfg_id[7], + ttdata->post_code, + scd->electrodes_x, + scd->electrodes_y, + scd->len_x, + scd->len_y, + scd->res_x, + scd->res_y, + scd->max_z, + scd->origin_x, + scd->origin_y, + scd->panel_id, + scd->btn, + scd->scan_mode, + scd->max_tch); + } else { + return scnprintf(buf, strlen(buf), + "Status: %d\n", + rc); + } +} +static DEVICE_ATTR(sysinfo, 0444, pt_sysinfo_show, NULL); + +/******************************************************************************* + * FUNCTION: pt_hw_reset_show + * + * SUMMARY: The show method for the hw_reset sysfs node that does a hw reset + * by toggling the XRES line and then calls the startup function to + * allow TTDL to re-enumerate the DUT. + * The printed value reflects the status of the full reset/enum. + * + * PARAMETERS: + * *dev - pointer to Device structure + * *attr - pointer to the device attribute structure + * *buf - pointer to buffer to print + ******************************************************************************/ +static ssize_t pt_hw_reset_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + int rc = 0; + int time = 0; + u8 reset_status = 0; + int t; + struct pt_hid_desc hid_desc; + + memset(&hid_desc, 0, sizeof(hid_desc)); + + /* Only allow DUT reset if no active BL in progress */ + mutex_lock(&cd->firmware_class_lock); + + mutex_lock(&cd->system_lock); + cd->startup_state = STARTUP_NONE; + mutex_unlock(&(cd->system_lock)); + + pt_stop_wd_timer(cd); + + /* ensure no left over exclusive access is still locked */ + release_exclusive(cd, cd->dev); + + rc = pt_dut_reset(cd, PT_CORE_CMD_PROTECTED); + if (rc) { + mutex_unlock(&cd->firmware_class_lock); + pt_debug(cd->dev, DL_ERROR, + "%s: HW reset failed rc = %d\n", __func__, rc); + goto exit_hw_reset; + } + reset_status |= 0x01 << 0; + + if (cd->flashless_dut) { + mutex_unlock(&cd->firmware_class_lock); + + t = wait_event_timeout(cd->wait_q, (cd->fw_updating == true), + msecs_to_jiffies(200)); + if (IS_TMO(t)) { + pt_debug(dev, DL_ERROR, + "%s: Timeout waiting for FW update", + __func__); + rc = -ETIME; + goto exit_hw_reset; + } else { + pt_debug(dev, DL_INFO, + "%s: ----- Wait FW Loading ----", + __func__); + rc = _pt_request_wait_for_enum_state( + dev, 4000, STARTUP_STATUS_FW_RESET_SENTINEL); + if (rc) { + pt_debug(cd->dev, DL_ERROR, + "%s: No FW Sentinel detected rc = %d\n", + __func__, rc); + goto exit_hw_reset; + } + reset_status |= 0x01 << 1; + } + } else { + /* Wait for any sentinel */ + rc = _pt_request_wait_for_enum_state(dev, 150, + STARTUP_STATUS_BL_RESET_SENTINEL | + STARTUP_STATUS_FW_RESET_SENTINEL); + if (rc) { + mutex_unlock(&cd->firmware_class_lock); + pt_debug(cd->dev, DL_ERROR, + "%s: No Sentinel detected rc = %d\n", + __func__, rc); + goto exit_hw_reset; + } + + /* sleep needed to ensure no cmd is sent while DUT will NAK */ + msleep(30); + + if (cd->active_dut_generation == DUT_PIP1_ONLY) { + rc = pt_get_hid_descriptor_(cd, &hid_desc); + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, + "%s: Error on getting HID descriptor r=%d\n", + __func__, rc); + goto exit_hw_reset; + } + + cd->mode = pt_get_mode(cd, &hid_desc); + if (cd->mode == PT_MODE_BOOTLOADER) + rc = pt_hid_output_bl_launch_app_(cd); + } else { + if (cd->mode == PT_MODE_BOOTLOADER) + rc = pt_pip2_launch_app(dev, + PT_CORE_CMD_UNPROTECTED); + } + + if (rc) { + mutex_unlock(&cd->firmware_class_lock); + pt_debug(cd->dev, DL_ERROR, + "%s: PIP launch app failed rc = %d\n", + __func__, rc); + goto exit_hw_reset; + } + mutex_unlock(&cd->firmware_class_lock); + + reset_status |= 0x01 << 1; + msleep(20); + if ((cd->active_dut_generation == DUT_UNKNOWN) || + (cd->mode != PT_MODE_OPERATIONAL)) + pt_queue_restart(cd); + else + pt_queue_enum(cd); + } + + while (!(cd->startup_status & STARTUP_STATUS_COMPLETE) && time < 2000) { + msleep(50); + pt_debug(cd->dev, DL_INFO, + "%s: wait %dms for 0x%04X, current enum=0x%04X\n", + __func__, time, STARTUP_STATUS_COMPLETE, + cd->startup_status); + time += 50; + } + if (!(cd->startup_status & STARTUP_STATUS_COMPLETE)) { + rc = -ETIME; + goto exit_hw_reset; + } + + pt_debug(cd->dev, DL_INFO, "%s: HW Reset complete. enum=0x%04X\n", + __func__, cd->startup_status); + reset_status |= 0x01 << 2; + pt_start_wd_timer(cd); + +exit_hw_reset: + return scnprintf(buf, strlen(buf), + "Status: %d\n" + "Reset Status: 0x%02X\n", rc, reset_status); +} +static DEVICE_ATTR(hw_reset, 0444, pt_hw_reset_show, NULL); + +/******************************************************************************* + * FUNCTION: pt_pip2_cmd_rsp_store + * + * SUMMARY: This is the store method for the raw PIP2 cmd/rsp sysfs node. Any + * raw PIP2 command echo'd to this node will be sent directly to the DUT. + * Command byte order: + * Byte [0] - PIP2 command ID + * Byte [1-n] - PIP2 command payload + * + * RETURN: Size of passed in buffer + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to buffer that hold the command parameters + * size - size of buf + ******************************************************************************/ +static ssize_t pt_pip2_cmd_rsp_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + u16 actual_read_len; + u8 input_data[PT_MAX_PIP2_MSG_SIZE + 1]; + u8 read_buf[PT_MAX_PIP2_MSG_SIZE + 1]; + u8 pip2_cmd_id = 0x00; + u8 *pip2_cmd_data = NULL; + int data_len = 0; + int length; + int rc = 0; + + /* clear shared data */ + mutex_lock(&cd->sysfs_lock); + cd->raw_cmd_status = 0; + cd->cmd_rsp_buf_len = 0; + memset(cd->cmd_rsp_buf, 0, sizeof(cd->cmd_rsp_buf)); + mutex_unlock(&cd->sysfs_lock); + + length = _pt_ic_parse_input_hex(dev, buf, size, + input_data, PT_MAX_PIP2_MSG_SIZE); + if (length <= 0 || length > (PT_MAX_PIP2_MSG_SIZE - 2)) { + pt_debug(dev, DL_ERROR, "%s: Invalid number of arguments\n", + __func__); + rc = -EINVAL; + goto exit; + } + + /* Send PIP2 command if enough data was provided */ + if (length >= 1) { + pip2_cmd_id = input_data[0]; + pip2_cmd_data = &input_data[1]; + data_len = length - 1; + rc = _pt_request_pip2_send_cmd(dev, PT_CORE_CMD_PROTECTED, + pip2_cmd_id, pip2_cmd_data, data_len, + read_buf, &actual_read_len); + cd->raw_cmd_status = rc; + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: PIP2 cmd 0x%02x failed rc = %d\n", + __func__, pip2_cmd_id, rc); + goto exit; + } else { + cd->cmd_rsp_buf_len = actual_read_len; + memcpy(cd->cmd_rsp_buf, read_buf, actual_read_len); + pt_debug(dev, DL_ERROR, + "%s: PIP2 actual_read_len = %d\n", + __func__, actual_read_len); + } + } else { + rc = -EINVAL; + pt_debug(dev, DL_ERROR, + "%s: Insufficient data provided for PIP2 cmd\n", + __func__); + } + +exit: + if (rc) + return rc; + return size; +} +/******************************************************************************* + * FUNCTION: pt_pip2_cmd_rsp_show + * + * SUMMARY: The show method for the raw pip2_cmd_rsp sysfs node. Any PIP2 + * response generated after using the store method of the pip2_cmd_rsp + * sysfs node, are available to be read here. + * + * PARAMETERS: + * *dev - pointer to Device structure + * *attr - pointer to the device attribute structure + * *buf - pointer to buffer to print + ******************************************************************************/ +static ssize_t pt_pip2_cmd_rsp_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + int i; + ssize_t data_len; + int index; + + mutex_lock(&cd->sysfs_lock); + index = scnprintf(buf, PT_MAX_PRBUF_SIZE, + "Status: %d\n", cd->raw_cmd_status); + if (cd->raw_cmd_status) + goto error; + + /* Remove the CRC from the length of the response */ + data_len = cd->cmd_rsp_buf_len - 2; + + /* Start printing from the data payload */ + for (i = PIP1_RESP_COMMAND_ID_OFFSET; i < data_len; i++) + index += scnprintf(buf + index, PT_MAX_PRBUF_SIZE - index, + "%02X ", cd->cmd_rsp_buf[i]); + + if (data_len >= PIP1_RESP_COMMAND_ID_OFFSET) { + index += scnprintf(buf + index, PT_MAX_PRBUF_SIZE - index, + "\n(%zd bytes)\n", + data_len - PIP1_RESP_COMMAND_ID_OFFSET); + } else { + index += scnprintf(buf + index, PT_MAX_PRBUF_SIZE - index, + "\n(%zd bytes)\n", 0); + } + +error: + mutex_unlock(&cd->sysfs_lock); + return index; +} +static DEVICE_ATTR(pip2_cmd_rsp, 0644, pt_pip2_cmd_rsp_show, + pt_pip2_cmd_rsp_store); + +/******************************************************************************* + * FUNCTION: pt_command_store + * + * SUMMARY: This is the store method for the raw PIP command sysfs node. Any + * raw PIP command echo'd to this node will be sent directly to the DUT. + * TTDL will not parse the command. + * + * RETURN: Size of passed in buffer + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to buffer that hold the command parameters + * size - size of buf + ******************************************************************************/ +static ssize_t pt_command_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + unsigned short crc; + u16 actual_read_len; + u8 input_data[PT_MAX_PIP2_MSG_SIZE + 1]; + int length; + int len_field; + int rc = 0; + + mutex_lock(&cd->sysfs_lock); + cd->cmd_rsp_buf_len = 0; + memset(cd->cmd_rsp_buf, 0, sizeof(cd->cmd_rsp_buf)); + mutex_unlock(&cd->sysfs_lock); + + length = _pt_ic_parse_input_hex(dev, buf, size, + input_data, PT_MAX_PIP2_MSG_SIZE); + if (length <= 0 || length > PT_MAX_PIP2_MSG_SIZE) { + pt_debug(dev, DL_ERROR, "%s: Invalid number of arguments\n", + __func__); + rc = -EINVAL; + goto pt_command_store_exit; + } + + /* PIP2 messages begin with 01 01 */ + if (length >= 2 && input_data[0] == 0x01 && input_data[1] == 0x01) { + cd->pip2_prot_active = 1; + /* Override next seq tag with what was sent */ + cd->pip2_cmd_tag_seq = input_data[4] & 0x0F; + /* For PIP2 cmd if length does not include crc, add it */ + len_field = (input_data[3] << 8) | input_data[2]; + if (len_field == length && length <= 254) { + crc = crc_ccitt_calculate(&input_data[2], + length - 2); + pt_debug(dev, DL_ERROR, "%s: len=%d crc=0x%02X\n", + __func__, length, crc); + input_data[length] = (crc & 0xFF00) >> 8; + input_data[length + 1] = crc & 0x00FF; + length = length + 2; + } + } + + /* write PIP command to log */ + pt_pr_buf(dev, DL_INFO, input_data, length, "command_buf"); + + pm_runtime_get_sync(dev); + + rc = pt_hid_output_user_cmd(cd, PT_MAX_INPUT, cd->cmd_rsp_buf, + length, input_data, &actual_read_len); + pm_runtime_put(dev); + + mutex_lock(&cd->sysfs_lock); + cd->raw_cmd_status = rc; + if (rc) { + cd->cmd_rsp_buf_len = 0; + pt_debug(dev, DL_ERROR, "%s: Failed to send command: %s\n", + __func__, buf); + } else { + cd->cmd_rsp_buf_len = actual_read_len; + } + cd->pip2_prot_active = 0; + mutex_unlock(&cd->sysfs_lock); + +pt_command_store_exit: + if (rc) + return rc; + return size; +} +static DEVICE_ATTR(command, 0220, NULL, pt_command_store); + +/******************************************************************************* + * FUNCTION: pt_response_show + * + * SUMMARY: The show method for the raw PIP response sysfs node. Any PIP + * response generated after using the pt_command_store sysfs node, are + * available to be read here. + * + * PARAMETERS: + * *dev - pointer to Device structure + * *attr - pointer to the device attribute structure + * *buf - pointer to buffer to print + ******************************************************************************/ +static ssize_t pt_response_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + int i; + ssize_t num_read; + int index; + + mutex_lock(&cd->sysfs_lock); + index = scnprintf(buf, PT_MAX_PRBUF_SIZE, + "Status: %d\n", cd->raw_cmd_status); + if (cd->raw_cmd_status) + goto error; + + num_read = cd->cmd_rsp_buf_len; + for (i = 0; i < num_read; i++) + index += scnprintf(buf + index, PT_MAX_PRBUF_SIZE - index, + "0x%02X\n", cd->cmd_rsp_buf[i]); + + index += scnprintf(buf + index, PT_MAX_PRBUF_SIZE - index, + "(%zd bytes)\n", num_read); + +error: + mutex_unlock(&cd->sysfs_lock); + return index; +} +static DEVICE_ATTR(response, 0444, pt_response_show, NULL); + +/******************************************************************************* + * FUNCTION: pt_dut_debug_show + * + * SUMMARY: Show method for the dut_debug sysfs node. Shows what parameters + * are available for the store method. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to output buffer + ******************************************************************************/ +static ssize_t pt_dut_debug_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t ret; + + ret = scnprintf(buf, strlen(buf), + "Status: 0\n" + "dut_debug sends the following commands to the DUT:\n" + "%d %s \t- %s\n" + "%d %s \t- %s\n" + "%d %s \t- %s\n" + "%d %s \t- %s\n" + "%d %s \t- %s\n" + "%d %s \t- %s\n" + "%d %s \t- %s\n" + "%d %s \t- %s\n" + "%d %s \t- %s\n" + "%d %s \t- %s\n" + "%d %s \t- %s\n" + "%d %s \t- %s\n" + "%d %s \t- %s\n" + "%d %s \t- %s\n" + "%d %s \t- %s\n" + "%d %s \t- %s\n" + "%d %s \t- %s\n" + , + PIP1_BL_CMD_ID_VERIFY_APP_INTEGRITY, "", "BL Verify APP", + PT_DUT_DBG_HID_RESET, "", "HID Reset", + PT_DUT_DBG_HID_SET_POWER_ON, "", "HID SET_POWER ON", + PT_DUT_DBG_HID_SET_POWER_SLEEP, "", "HID SET_POWER SLEEP", + PT_DUT_DBG_HID_SET_POWER_STANDBY, "", "HID SET_POWER STANDBY", + PIP1_BL_CMD_ID_GET_INFO, "", "BL Get Info", + PIP1_BL_CMD_ID_PROGRAM_AND_VERIFY, "", "BL Program & Verify", + PIP1_BL_CMD_ID_LAUNCH_APP, "", "BL Launch APP", + PIP1_BL_CMD_ID_INITIATE_BL, "", "BL Initiate BL", + PT_DUT_DBG_PIP_SOFT_RESET, "", "PIP Soft Reset", + PT_DUT_DBG_RESET, "", "Toggle the TP_XRES GPIO", + PT_DUT_DBG_PIP_NULL, "", "PIP NULL (PING)", + PT_DUT_DBG_PIP_ENTER_BL, "", "PIP enter BL", + PT_DUT_DBG_HID_SYSINFO, "", "HID system info", + PT_DUT_DBG_PIP_SUSPEND_SCAN, "", "Suspend Scan", + PT_DUT_DBG_PIP_RESUME_SCAN, "", "Resume Scan", + PT_DUT_DBG_HID_DESC, "", "Get HID Desc" + ); + + return ret; +} +/******************************************************************************* + * FUNCTION: pt_drv_debug_show + * + * SUMMARY: Show method for the drv_debug sysfs node. Shows what parameters + * are available for the store method. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to output buffer + ******************************************************************************/ +static ssize_t pt_drv_debug_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t ret; + + ret = scnprintf(buf, strlen(buf), + "Status: 0\n" + "drv_debug supports the following values:\n" + "%d %s \t- %s\n" + "%d %s \t- %s\n" + "%d %s \t- %s\n" + "%d %s \t- %s\n" + "%d %s \t- %s\n" + "%d %s \t- %s\n" + "%d %s \t- %s\n" + "%d %s \t- %s\n" + "%d %s \t- %s\n" + "%d %s - %s\n" + "%d %s \t- %s\n" + "%d %s \t- %s\n" +#ifdef TTDL_DIAGNOSTICS + "%d %s \t- %s\n" + "%d %s \t- %s\n" + "%d %s \t- %s\n" +#endif /* TTDL_DIAGNOSTICS */ + "%d %s \t- %s\n" +#ifdef TTDL_DIAGNOSTICS + "%d %s \t- %s\n" + "%d %s \t- %s\n" + "%d %s \t- %s\n" + "%d %s \t- %s\n" + "%d %s \t- %s\n" + "%d %s \t- %s\n" + "%d %s \t- %s\n" + "%d %s \t- %s\n" + "%d %s \t- %s\n" +#endif /* TTDL_DIAGNOSTICS */ + , + PT_DRV_DBG_SUSPEND, " ", "Suspend TTDL responding to INT", + PT_DRV_DBG_RESUME, " ", "Resume TTDL responding to INT", + PT_DRV_DBG_STOP_WD, " ", "Stop TTDL WD", + PT_DRV_DBG_START_WD, " ", "Start TTDL WD", + PT_DRV_DBG_TTHE_TUNER_EXIT, " ", "Exit TTHE Tuner Logging", + PT_DRV_DBG_TTHE_BUF_CLEAN, " ", "Clear TTHE Tuner buffer", + PT_DRV_DBG_CLEAR_PARM_LIST, " ", "Clear RAM Param list", + PT_DRV_DBG_FORCE_BUS_READ, " ", "Force bus read", + PT_DRV_DBG_CLEAR_CAL_DATA, " ", "Clear CAL Cache", + PT_DRV_DBG_REPORT_LEVEL, "[0|1|2|3|4]", "Set TTDL Debug Level", + PT_DRV_DBG_WATCHDOG_INTERVAL, "[n] ", "TTDL WD Interval in ms", + PT_DRV_DBG_SHOW_TIMESTAMP, "[0|1]", "Show Timestamps" +#ifdef TTDL_DIAGNOSTICS + , PT_DRV_DBG_SETUP_PWR, "[0|1]", "Power DUT up/down", + PT_DRV_DBG_GET_PUT_SYNC, "[0|1]", "Get/Put Linux Sleep", + PT_DRV_DBG_SET_TT_DATA, "[0|1]", "Display TT_DATA" +#endif /* TTDL_DIAGNOSTICS */ + , PT_DRV_DBG_SET_GENERATION, "[0|1|2]", "Set DUT generation" +#ifdef TTDL_DIAGNOSTICS + , PT_DRV_DBG_SET_BRIDGE_MODE, "[0|1]", "On/Off Bridge Mode", + PT_DRV_DBG_SET_I2C_ADDRESS, "[0-127]", "I2C DUT Address", + PT_DRV_DBG_SET_FLASHLESS_DUT, "[0|1]", "Flashless DUT yes/no", + PT_DRV_DBG_SET_FORCE_SEQ, "[8-15]", "Force PIP2 Sequence #", + PT_DRV_DBG_BL_WITH_NO_INT, "[0|1]", "BL with no INT", + PT_DRV_DBG_CAL_CACHE_IN_HOST, "[0|1]", "CAL Cache in host", + PT_DRV_DBG_MULTI_CHIP, "[0|1]", "Multi Chip Support", + PT_DRV_DBG_PIP_TIMEOUT, "[100-7000]", "PIP Resp Timeout (ms)", + PT_DRV_DBG_TTHE_HID_USB_FORMAT, "[0|1]", + "TTHE_TUNER HID USB Format" +#endif /* TTDL_DIAGNOSTICS */ + ); + + return ret; +} + +/******************************************************************************* + * FUNCTION: pt_drv_debug_store + * + * SUMMARY: Currently the store method for both sysfs nodes: drv_debug and + * dut_debug. Drv_debug will contain all functionality that can be run + * without a DUT preset and is available anytime TTDL is running. + * Dut_debug requires a DUT to be available and will only be created after + * a DUT has been detected. + * This function will eventually be split into two but until the overlap + * has been depricated this function contains all commands that can be + * used for TTDL/DUT debugging status and control. + * All commands require at least one value to be passed in *buf with some + * requiring two. + * + * RETURN: Size of passed in buffer + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to buffer that hold the command parameters + * size - size of buf + ******************************************************************************/ +static ssize_t pt_drv_debug_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + unsigned long value; + int rc = 0; + u8 return_data[8]; + static u8 wd_disabled; + u32 input_data[3]; + int length; +#ifdef TTDL_DIAGNOSTICS + struct i2c_client *client = to_i2c_client(dev); + unsigned short crc = 0; + u16 cal_size; +#endif + input_data[0] = 0; + input_data[1] = 0; + + /* Maximmum input is two elements */ + length = _pt_ic_parse_input(dev, buf, size, + input_data, ARRAY_SIZE(input_data)); + if (length < 1 || length > 2) { + pt_debug(dev, DL_ERROR, "%s: Invalid number of arguments\n", + __func__); + rc = -EINVAL; + goto pt_drv_debug_store_exit; + } + value = input_data[0]; + + if (length == 1) { + pt_debug(dev, DL_DEBUG, + "%s: Debug Cmd Received (id=%d)\n", + __func__, input_data[0]); + } else if (length == 2) { + pt_debug(dev, DL_DEBUG, + "%s: Debug Cmd Received (id=%d, data=%d)\n", + __func__, input_data[0], input_data[1]); + } else { + pt_debug(dev, DL_DEBUG, + "%s: Invalid arguments received\n", __func__); + rc = -EINVAL; + goto pt_drv_debug_store_exit; + } + + /* Start watchdog timer command */ + if (value == PT_DRV_DBG_START_WD) { + pt_debug(dev, DL_INFO, "%s: Cmd: Start Watchdog\n", __func__); + wd_disabled = 0; + cd->watchdog_force_stop = false; + pt_start_wd_timer(cd); + goto pt_drv_debug_store_exit; + } + + /* Stop watchdog timer temporarily */ + pt_stop_wd_timer(cd); + + if (value == PT_DRV_DBG_STOP_WD) { + pt_debug(dev, DL_INFO, "%s: Cmd: Stop Watchdog\n", __func__); + wd_disabled = 1; + cd->watchdog_force_stop = true; + goto pt_drv_debug_store_exit; + } + + switch (value) { + case PT_DRV_DBG_SUSPEND: /* 4 */ + pt_debug(dev, DL_INFO, "%s: TTDL: Core Sleep\n", __func__); + wd_disabled = 1; + rc = pt_core_suspend_(cd->dev); + if (rc) + pt_debug(dev, DL_ERROR, "%s: Suspend failed rc=%d\n", + __func__, rc); + else { + pt_debug(dev, DL_INFO, "%s: Suspend succeeded\n", + __func__); + cd->drv_debug_suspend = true; + pt_debug(dev, DL_INFO, "%s: Debugfs flag set:\n", __func__); + } + break; + + case PT_DRV_DBG_RESUME: /* 5 */ + pt_debug(dev, DL_INFO, "%s: TTDL: Wake\n", __func__); + rc = pt_core_resume_(cd->dev); + if (rc) + pt_debug(dev, DL_ERROR, "%s: Resume failed rc=%d\n", + __func__, rc); + else { + pt_debug(dev, DL_INFO, "%s: Resume succeeded\n", + __func__); + cd->drv_debug_suspend = false; + pt_debug(dev, DL_INFO, "%s: Debugfs flag reset:\n", __func__); + } + break; + case PIP1_BL_CMD_ID_VERIFY_APP_INTEGRITY: /* BL - 49 */ + pt_debug(dev, DL_INFO, "%s: Cmd: verify app integ\n", __func__); + pt_hid_output_bl_verify_app_integrity(cd, &return_data[0]); + break; + case PT_DUT_DBG_HID_RESET: /* 50 */ + pt_debug(dev, DL_INFO, "%s: Cmd: hid_reset\n", __func__); + pt_hid_cmd_reset(cd); + break; + case PT_DUT_DBG_HID_SET_POWER_ON: /* 53 */ + pt_debug(dev, DL_INFO, "%s: Cmd: hid_set_power_on\n", __func__); + pt_hid_cmd_set_power(cd, HID_POWER_ON); + wd_disabled = 0; + break; + case PT_DUT_DBG_HID_SET_POWER_SLEEP: /* 54 */ + pt_debug(dev, DL_INFO, "%s: Cmd: hid_set_power_off\n", + __func__); + wd_disabled = 1; + pt_hid_cmd_set_power(cd, HID_POWER_SLEEP); + break; + case PT_DUT_DBG_HID_SET_POWER_STANDBY: /* 55 */ + pt_debug(dev, DL_INFO, "%s: Cmd: hid_set_power_standby\n", + __func__); + wd_disabled = 1; + pt_hid_cmd_set_power(cd, HID_POWER_STANDBY); + break; + case PIP1_BL_CMD_ID_GET_INFO: /* BL - 56 */ + pt_debug(dev, DL_INFO, "%s: Cmd: bl get info\n", __func__); + pt_hid_output_bl_get_information(cd, return_data); + break; + case PIP1_BL_CMD_ID_PROGRAM_AND_VERIFY: /* BL - 57 */ + pt_debug(dev, DL_INFO, "%s: Cmd: program and verify\n", + __func__); + pt_hid_output_bl_program_and_verify(cd, 0, NULL); + break; + case PIP1_BL_CMD_ID_LAUNCH_APP: /* BL - 59 */ + pt_debug(dev, DL_INFO, "%s: Cmd: launch app\n", __func__); + pt_hid_output_bl_launch_app(cd); + break; + case PIP1_BL_CMD_ID_INITIATE_BL: /* BL - 72 */ + pt_debug(dev, DL_INFO, "%s: Cmd: initiate bl\n", __func__); + pt_hid_output_bl_initiate_bl(cd, 0, NULL, 0, NULL); + break; + case PT_DUT_DBG_PIP_SOFT_RESET: /* 97 */ + pt_debug(dev, DL_INFO, "%s: Cmd: Soft Reset\n", __func__); + rc = pt_hw_soft_reset(cd, PT_CORE_CMD_PROTECTED); + break; + case PT_DUT_DBG_RESET: /* 98 */ + pt_debug(dev, DL_INFO, "%s: Cmd: Hard Reset\n", __func__); + rc = pt_hw_hard_reset(cd); + break; + case PT_DUT_DBG_PIP_NULL: /* 100 */ + pt_debug(dev, DL_INFO, "%s: Cmd: Ping (null)\n", __func__); + pt_pip_null(cd); + break; + case PT_DUT_DBG_PIP_ENTER_BL: /* 101 */ + pt_debug(dev, DL_INFO, "%s: Cmd: start_bootloader\n", __func__); + rc = pt_pip_start_bootloader(cd); + if (!rc) { + cd->startup_status = STARTUP_STATUS_BL_RESET_SENTINEL; + cd->mode = PT_MODE_BOOTLOADER; + } + break; + case PT_DUT_DBG_HID_SYSINFO: /* 102 */ + pt_debug(dev, DL_INFO, "%s: Cmd: get_sysinfo\n", __func__); + pt_hid_output_get_sysinfo(cd); + break; + case PT_DUT_DBG_PIP_SUSPEND_SCAN: /* 103 */ + pt_debug(dev, DL_INFO, "%s: Cmd: suspend_scanning\n", __func__); + pt_pip_suspend_scanning(cd); + break; + case PT_DUT_DBG_PIP_RESUME_SCAN: /* 104 */ + pt_debug(dev, DL_INFO, "%s: Cmd: resume_scanning\n", __func__); + pt_pip_resume_scanning(cd); + break; +#ifdef TTHE_TUNER_SUPPORT + case PT_DRV_DBG_TTHE_TUNER_EXIT: /* 107 */ + cd->tthe_exit = 1; + wake_up(&cd->wait_q); + kfree(cd->tthe_buf); + cd->tthe_buf = NULL; + cd->tthe_exit = 0; + break; + case PT_DRV_DBG_TTHE_BUF_CLEAN: /* 108 */ + if (cd->tthe_buf) + memset(cd->tthe_buf, 0, PT_MAX_PRBUF_SIZE); + else + pt_debug(dev, DL_INFO, "%s : tthe_buf not existed\n", + __func__); + break; +#endif +#ifdef TTDL_DIAGNOSTICS + case PT_DUT_DBG_HID_DESC: /* 109 */ + pt_debug(dev, DL_INFO, "%s: Cmd: get_hid_desc\n", __func__); + pt_get_hid_descriptor(cd, &cd->hid_desc); + break; + case PT_DRV_DBG_CLEAR_PARM_LIST: /* 110 */ + pt_debug(dev, DL_INFO, "%s: TTDL: Clear Parameter List\n", + __func__); + pt_erase_parameter_list(cd); + break; + case PT_DRV_DBG_FORCE_BUS_READ: /* 111 */ + rc = pt_read_input(cd); + if (!rc) + pt_parse_input(cd); + break; + case PT_DRV_DBG_CLEAR_CAL_DATA: /* 112 */ + rc = _pt_manage_local_cal_data(dev, PT_CAL_DATA_CLEAR, + &cal_size, &crc); + if (rc) + pt_debug(dev, DL_ERROR, + "%s: CAL Data clear failed rc=%d\n", + __func__, rc); + else + pt_debug(dev, DL_INFO, + "%s: CAL Cleared, Chip ID=0x%04X size=%d\n", + __func__, crc, size); + break; + + case PT_DRV_DBG_REPORT_LEVEL: /* 200 */ + mutex_lock(&cd->system_lock); + if (input_data[1] >= 0 && input_data[1] < DL_MAX) { + cd->debug_level = input_data[1]; + pt_debug(dev, DL_INFO, "%s: Set debug_level: %d\n", + __func__, cd->debug_level); + } else { + rc = -EINVAL; + pt_debug(dev, DL_ERROR, "%s: Invalid debug_level: %d\n", + __func__, input_data[1]); + } + mutex_unlock(&(cd->system_lock)); + break; +#endif + case PT_DRV_DBG_WATCHDOG_INTERVAL: /* 201 */ + mutex_lock(&cd->system_lock); + if (input_data[1] > 100) { + cd->watchdog_interval = input_data[1]; + pt_debug(dev, DL_INFO, + "%s: Set watchdog_ interval to: %d\n", + __func__, cd->watchdog_interval); + pt_start_wd_timer(cd); + } else { + rc = -EINVAL; + pt_debug(dev, DL_ERROR, + "%s: Invalid watchdog interval: %d\n", + __func__, input_data[1]); + } + mutex_unlock(&(cd->system_lock)); + break; +#ifdef TTDL_DIAGNOSTICS + case PT_DRV_DBG_SHOW_TIMESTAMP: /* 202 */ + mutex_lock(&cd->system_lock); + if (input_data[1] == 0) { + cd->show_timestamp = 0; + pt_debug(dev, DL_INFO, "%s: Disable show_timestamp\n", + __func__); + } else if (input_data[1] == 1) { + cd->show_timestamp = 1; + pt_debug(dev, DL_INFO, "%s: Enable show_timestamp\n", + __func__); + } else { + rc = -EINVAL; + pt_debug(dev, DL_ERROR, + "%s: Invalid parameter: %d\n", + __func__, input_data[1]); + } + mutex_unlock(&(cd->system_lock)); + break; + case PT_DRV_DBG_SETUP_PWR: /* 205 */ + if (input_data[1] == 0) { + cd->cpdata->setup_power(cd->cpdata, + PT_MT_POWER_OFF, cd->dev); + pt_debug(dev, DL_INFO, + "%s: Initiate Power Off\n", __func__); + } else if (input_data[1] == 1) { + cd->cpdata->setup_power(cd->cpdata, + PT_MT_POWER_ON, cd->dev); + pt_debug(dev, DL_INFO, + "%s: Initiate Power On\n", __func__); + } else { + rc = -EINVAL; + pt_debug(dev, DL_ERROR, + "%s: Invalid parameter: %d\n", + __func__, input_data[1]); + } + break; + case PT_DRV_DBG_GET_PUT_SYNC: /* 206 */ + if (input_data[1] == 0) { + pm_runtime_put(dev); + pt_debug(dev, DL_ERROR, + "%s: Force call pm_runtime_put()\n", __func__); + } else if (input_data[1] == 1) { + pm_runtime_get_sync(dev); + pt_debug(dev, DL_ERROR, + "%s: Force call pm_runtime_get_sync()\n", + __func__); + } else { + rc = -EINVAL; + pt_debug(dev, DL_ERROR, + "%s: WARNING: Invalid parameter: %d\n", + __func__, input_data[1]); + } + break; + case PT_DRV_DBG_SET_TT_DATA: /* 208 */ + mutex_lock(&cd->system_lock); + if (input_data[1] == 0) { + cd->show_tt_data = false; + pt_debug(dev, DL_INFO, + "%s: Disable TT_DATA\n", __func__); + } else if (input_data[1] == 1) { + cd->show_tt_data = true; + pt_debug(dev, DL_INFO, + "%s: Enable TT_DATA\n", __func__); + } else { + rc = -EINVAL; + pt_debug(dev, DL_ERROR, + "%s: Invalid parameter: %d\n", + __func__, input_data[1]); + } + mutex_unlock(&(cd->system_lock)); + break; + case PT_DRV_DBG_SET_GENERATION: /* 210 */ + if (input_data[1] == cd->active_dut_generation) { + mutex_lock(&cd->system_lock); + cd->set_dut_generation = true; + mutex_unlock(&(cd->system_lock)); + } else { + mutex_lock(&cd->system_lock); + if (input_data[1] == 0) { + cd->active_dut_generation = DUT_UNKNOWN; + cd->set_dut_generation = false; + } else if (input_data[1] == 1) { + cd->active_dut_generation = DUT_PIP1_ONLY; + cd->set_dut_generation = true; + } else if (input_data[1] == 2) { + cd->active_dut_generation = DUT_PIP2_CAPABLE; + cd->set_dut_generation = true; + } else { + rc = -EINVAL; + pt_debug(dev, DL_ERROR, + "%s: Invalid parameter: %d\n", + __func__, input_data[1]); + break; + } + cd->startup_status = STARTUP_STATUS_START; + pt_debug(cd->dev, DL_DEBUG, + "%s: Startup Status Reset\n", __func__); + mutex_unlock(&(cd->system_lock)); + + pt_debug(dev, DL_INFO, + "%s: Active DUT Generation Set to: %d\n", + __func__, cd->active_dut_generation); + + /* Changing DUT generations full restart needed */ + pt_queue_restart(cd); + } + break; + case PT_DRV_DBG_SET_BRIDGE_MODE: /* 211 */ + mutex_lock(&cd->system_lock); + if (input_data[1] == 0) { + cd->bridge_mode = false; + pt_debug(dev, DL_INFO, + "%s: Disable Bridge Mode\n", __func__); + } else if (input_data[1] == 1) { + cd->bridge_mode = true; + pt_debug(dev, DL_INFO, + "%s: Enable Bridge Mode\n", __func__); + } else { + rc = -EINVAL; + pt_debug(dev, DL_ERROR, "%s: Invalid parameter: %d\n", + __func__, input_data[1]); + } + mutex_unlock(&(cd->system_lock)); + break; + case PT_DRV_DBG_SET_I2C_ADDRESS: /* 212 */ + mutex_lock(&cd->system_lock); + /* Only a 7bit address is valid */ + if (input_data[1] >= 0 && input_data[1] <= 0x7F) { + client->addr = input_data[1]; + pt_debug(dev, DL_INFO, + "%s: Set I2C Address: 0x%2X\n", + __func__, client->addr); + } else { + rc = -EINVAL; + pt_debug(dev, DL_ERROR, "%s: Invalid I2C Address %d\n", + __func__, input_data[1]); + client->addr = 0x24; + } + mutex_unlock(&(cd->system_lock)); + break; + case PT_DRV_DBG_SET_FLASHLESS_DUT: /* 213 */ + mutex_lock(&cd->system_lock); + if (input_data[1] == 0) { + cd->flashless_dut = 0; + cd->flashless_auto_bl = PT_SUPPRESS_AUTO_BL; + pt_debug(dev, DL_INFO, "%s: Disable FLAHLESS DUT\n", + __func__); + } else if (input_data[1] == 1) { + cd->flashless_dut = 1; + cd->flashless_auto_bl = PT_ALLOW_AUTO_BL; + pt_debug(dev, DL_INFO, "%s: Enable FLAHLESS DUT\n", + __func__); + } + mutex_unlock(&(cd->system_lock)); + break; + case PT_DRV_DBG_SET_FORCE_SEQ: /* 214 */ + mutex_lock(&cd->system_lock); + if (input_data[1] >= 0x8 && input_data[1] <= 0xF) { + cd->force_pip2_seq = input_data[1]; + cd->pip2_cmd_tag_seq = input_data[1]; + pt_debug(dev, DL_INFO, + "%s: Force PIP2 Seq to: 0x%02X\n", + __func__, input_data[1]); + } else { + cd->force_pip2_seq = 0; + pt_debug(dev, DL_INFO, + "%s: Clear Forced PIP2 Seq\n", __func__); + } + mutex_unlock(&(cd->system_lock)); + break; + case PT_DRV_DBG_BL_WITH_NO_INT: /* 215 */ + mutex_lock(&cd->system_lock); + if (input_data[1] == 0) { + cd->bl_with_no_int = 0; + pt_debug(dev, DL_INFO, "%s: BL using IRQ\n", __func__); + } else if (input_data[1] == 1) { + cd->bl_with_no_int = 1; + pt_debug(dev, DL_INFO, "%s: BL using Polling\n", + __func__); + } else { + rc = -EINVAL; + pt_debug(dev, DL_ERROR, "%s: Invalid parameter: %d\n", + __func__, input_data[1]); + } + mutex_unlock(&(cd->system_lock)); + break; + case PT_DRV_DBG_CAL_CACHE_IN_HOST: /* 216 */ + mutex_lock(&cd->system_lock); + if (input_data[1] == 0) { + cd->cal_cache_in_host = PT_FEATURE_DISABLE; + pt_debug(dev, DL_INFO, + "%s: Disable Calibration cache in host\n", + __func__); + } else if (input_data[1] == 1) { + cd->cal_cache_in_host = PT_FEATURE_ENABLE; + pt_debug(dev, DL_INFO, + "%s: Enable Calibration cache in host\n", + __func__); + } else { + rc = -EINVAL; + pt_debug(dev, DL_ERROR, "%s: Invalid parameter: %d\n", + __func__, input_data[1]); + } + mutex_unlock(&(cd->system_lock)); + break; + case PT_DRV_DBG_MULTI_CHIP: /* 217 */ + mutex_lock(&cd->system_lock); + if (input_data[1] == 0) { + cd->multi_chip = PT_FEATURE_DISABLE; + cd->ttdl_bist_select = 0x07; + pt_debug(dev, DL_INFO, + "%s: Disable Multi-chip support\n", __func__); + } else if (input_data[1] == 1) { + cd->multi_chip = PT_FEATURE_ENABLE; + cd->ttdl_bist_select = 0x3F; + pt_debug(dev, DL_INFO, + "%s: Enable Multi-chip support\n", __func__); + } else { + rc = -EINVAL; + pt_debug(dev, DL_ERROR, "%s: Invalid parameter: %d\n", + __func__, input_data[1]); + } + mutex_unlock(&(cd->system_lock)); + break; + case PT_DRV_DBG_SET_PANEL_ID_TYPE: /* 218 */ + mutex_lock(&cd->system_lock); + if (input_data[1] <= 0x07) { + cd->panel_id_support = input_data[1]; + pt_debug(dev, DL_INFO, + "%s: Set panel_id_support to %d\n", + __func__, cd->panel_id_support); + } else { + rc = -EINVAL; + pt_debug(dev, DL_ERROR, "%s: Invalid parameter: %d\n", + __func__, input_data[1]); + } + mutex_unlock(&(cd->system_lock)); + break; + case PT_DRV_DBG_PIP_TIMEOUT: /* 219 */ + mutex_lock(&cd->system_lock); + if (input_data[1] >= 100 && input_data[1] <= 7000) { + /* + * The timeout is changed for some cases so the + * pip_cmd_timeout_default is used to retore back to + * what the user requested as the new timeout. + */ + cd->pip_cmd_timeout_default = input_data[1]; + cd->pip_cmd_timeout = input_data[1]; + pt_debug(dev, DL_INFO, + "%s: PIP Timeout = %d\n", __func__, + cd->pip_cmd_timeout_default); + } else { + rc = -EINVAL; + pt_debug(dev, DL_ERROR, "%s: Invalid parameter: %d\n", + __func__, input_data[1]); + } + mutex_unlock(&(cd->system_lock)); + break; + case PT_DRV_DBG_TTHE_HID_USB_FORMAT: /* 220 */ + mutex_lock(&cd->system_lock); + if (input_data[1] == 0) { + cd->tthe_hid_usb_format = PT_FEATURE_DISABLE; + pt_debug(dev, DL_INFO, + "%s: Disable tthe_tuner HID-USB format\n", + __func__); + } else if (input_data[1] == 1) { + cd->tthe_hid_usb_format = PT_FEATURE_ENABLE; + pt_debug(dev, DL_INFO, + "%s: Enable tthe_tuner HID-USB format\n", + __func__); + } else { + rc = -EINVAL; + pt_debug(dev, DL_ERROR, "%s: Invalid parameter: %d\n", + __func__, input_data[1]); + } + mutex_unlock(&(cd->system_lock)); + break; +#endif /* TTDL_DIAGNOSTICS */ + default: + rc = -EINVAL; + pt_debug(dev, DL_ERROR, "%s: Invalid value\n", __func__); + } + + /* Enable watchdog timer */ + if (!wd_disabled) + pt_start_wd_timer(cd); + +pt_drv_debug_store_exit: + if (rc) + return rc; + return size; +} +static DEVICE_ATTR(drv_debug, 0644, pt_drv_debug_show, + pt_drv_debug_store); + +/******************************************************************************* + * FUNCTION: pt_sleep_status_show + * + * SUMMARY: Show method for the sleep_status sysfs node that will show the + * sleep status as either ON or OFF + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to output buffer + ******************************************************************************/ +static ssize_t pt_sleep_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + ssize_t ret; + + mutex_lock(&cd->system_lock); + if (cd->sleep_state == SS_SLEEP_ON) + ret = scnprintf(buf, PT_MAX_PRBUF_SIZE, "off\n"); + else + ret = scnprintf(buf, PT_MAX_PRBUF_SIZE, "on\n"); + mutex_unlock(&cd->system_lock); + + return ret; +} + +/******************************************************************************* + * FUNCTION: pt_panel_id_show + * + * SUMMARY: Show method for the panel_id sysfs node that will show the + * detected panel ID from the DUT + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to output buffer + ******************************************************************************/ +static ssize_t pt_panel_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + ssize_t ret; + u8 pid = PANEL_ID_NOT_ENABLED; + int rc = 0; + + if (cd->active_dut_generation == DUT_PIP1_ONLY) { + /* + * The DUT should report the same panel ID from both the BL and + * the FW unless the panel_id feature is set to only + * PT_PANEL_ID_BY_SYS_INFO, in which case the BL is not able + * to retrieve the panel_id. + */ + if (cd->mode == PT_MODE_BOOTLOADER) { + /* + * Return the stored value if PT_PANEL_ID_BY_BL + * is not supported while other feature exits. + */ + if (cd->panel_id_support & PT_PANEL_ID_BY_BL) { + rc = pt_hid_output_bl_get_panel_id_(cd, &pid); + if (rc) { + pt_debug(dev, DL_ERROR, "%s: %s %s\n", + "Failed to retrieve Panel ID. ", + "Using cached value\n", + __func__); + } + } + } else if (cd->mode == PT_MODE_OPERATIONAL) { + if (cd->panel_id_support & + (PT_PANEL_ID_BY_BL | PT_PANEL_ID_BY_SYS_INFO)) { + /* For all systems sysinfo has the panel_id */ + rc = pt_hid_output_get_sysinfo(cd); + if (!rc) + pid = + cd->sysinfo.sensing_conf_data.panel_id; + pt_debug(dev, DL_ERROR, + "%s: Gen6 FW mode rc=%d PID=0x%02X\n", + __func__, rc, pid); + } + } else { + pt_debug(dev, DL_ERROR, "%s: Active mode unknown\n", + __func__); + rc = -EPERM; + } + } else if (cd->active_dut_generation == DUT_PIP2_CAPABLE) { + if (cd->mode == PT_MODE_BOOTLOADER) { + if (cd->panel_id_support & PT_PANEL_ID_BY_BL) { + rc = _pt_pip2_get_panel_id_by_gpio(cd, &pid); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: BL get panel ID failed rc=%d\n", + __func__, rc); + } + } + } else if (cd->mode == PT_MODE_OPERATIONAL) { + if (cd->panel_id_support & + (PT_PANEL_ID_BY_BL | PT_PANEL_ID_BY_SYS_INFO)) { + rc = pt_hid_output_get_sysinfo(cd); + if (!rc) + pid = + cd->sysinfo.sensing_conf_data.panel_id; + pt_debug(dev, DL_ERROR, + "%s: TT/TC FW mode rc=%d PID=0x%02X\n", + __func__, rc, pid); + } + } else { + pt_debug(dev, DL_ERROR, "%s: Active mode unknown\n", + __func__); + rc = -EPERM; + } + } else { + pt_debug(dev, DL_ERROR, "%s: Dut generation is unknown\n", + __func__); + rc = -EPERM; + } + + ret = scnprintf(buf, PT_MAX_PRBUF_SIZE, "Status: %d\n0x%02X\n", + rc, pid); + return ret; +} + +/******************************************************************************* + * FUNCTION: pt_get_param_store + * + * SUMMARY: Store method for the get_param sysfs node. Stores what parameter + * ID to retrieve with the show method. + * + * NOTE: This sysfs node is only available after a DUT has been enumerated + * + * RETURN: Size of passed in buffer if successful + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to output buffer + * size - size of buf + ******************************************************************************/ +static ssize_t pt_get_param_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + u32 input_data[2]; + int length; + int rc = 0; + + /* Maximum input of one value */ + length = _pt_ic_parse_input(dev, buf, size, input_data, + ARRAY_SIZE(input_data)); + if (length != 1) { + pt_debug(dev, DL_ERROR, "%s: Invalid number of arguments\n", + __func__); + rc = -EINVAL; + goto exit; + } + + mutex_lock(&cd->system_lock); + cd->get_param_id = input_data[0]; + mutex_unlock(&(cd->system_lock)); + +exit: + if (rc) + return rc; + return size; +} + +/******************************************************************************* + * FUNCTION: pt_get_param_show + * + * SUMMARY: Show method for the get_param sysfs node. Retrieves the + * parameter data from the DUT based on the ID stored in the core + * data variable "get_param_id". If the ID is invalid, the DUT cannot + * communicate or some other error occures, an error status is returned + * with no value following. + * Output is in the form: + * Status: x + * 0xyyyyyyyy + * The 32bit data will only follow the status code if the status == 0 + * + * NOTE: This sysfs node is only available after a DUT has been enumerated + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to output buffer + ******************************************************************************/ +static ssize_t pt_get_param_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + ssize_t ret = 0; + int status; + u32 value = 0; + + status = pt_pip_get_param(cd, cd->get_param_id, &value); + + if (status) { + pt_debug(dev, DL_ERROR, "%s: %s Failed, status = %d\n", + __func__, "pt_get_param", status); + ret = scnprintf(buf, strlen(buf), + "%s %d\n", + "Status:", status); + } else { + pt_debug(dev, DL_DEBUG, "%s: Param [%d] = 0x%04X\n", + __func__, cd->get_param_id, value); + ret = scnprintf(buf, strlen(buf), + "Status: %d\n" + "0x%04X\n", + status, value); + } + return ret; +} + +/******************************************************************************* + * FUNCTION: pt_ttdl_restart_show + * + * SUMMARY: Show method for ttdl_restart sysfs node. This node releases all + * probed modules, calls startup() and then re-probes modules. + * + * RETURN: size of data written to sysfs node + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes structure + * *buf - pointer to print output buffer + ******************************************************************************/ +static ssize_t pt_ttdl_restart_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + int t; + int rc = 0; + + mutex_lock(&cd->system_lock); + cd->startup_state = STARTUP_NONE; + mutex_unlock(&(cd->system_lock)); + /* ensure no left over exclusive access is still locked */ + release_exclusive(cd, cd->dev); + + pt_queue_restart(cd); + + t = wait_event_timeout(cd->wait_q, + (cd->startup_status >= STARTUP_STATUS_COMPLETE), + msecs_to_jiffies(PT_REQUEST_ENUM_TIMEOUT)); + if (IS_TMO(t)) { + pt_debug(cd->dev, DL_ERROR, + "%s: TMO waiting for FW sentinel\n", __func__); + rc = -ETIME; + } + + return scnprintf(buf, strlen(buf), + "Status: %d\n" + "Enum Status: 0x%04X\n", rc, cd->startup_status); +} +static DEVICE_ATTR(ttdl_restart, 0444, pt_ttdl_restart_show, NULL); + +/******************************************************************************* + * FUNCTION: pt_pip2_gpio_read_show + * + * SUMMARY: Sends a PIP2 READ_GPIO command to the DUT and prints the + * contents of the response to the passed in output buffer. + * + * RETURN: size of data written to sysfs node + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes structure + * *buf - pointer to print output buffer + ******************************************************************************/ +static ssize_t pt_pip2_gpio_read_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + u8 status = 0; + u32 gpio_value = 0; + int rc = 0; + + /* This functionality is only available in the BL */ + if (cd->mode == PT_MODE_BOOTLOADER) + rc = pt_pip2_read_gpio(dev, &status, &gpio_value); + else + rc = -EPERM; + + if (!rc) { + if (status == 0) + return scnprintf(buf, PT_MAX_PRBUF_SIZE, + "Status: %d\n" + "DUT GPIO Reg: 0x%08X\n", + rc, gpio_value); + else + return scnprintf(buf, PT_MAX_PRBUF_SIZE, + "Status: %d\n" + "DUT GPIO Reg: n/a\n", + status); + } else + return scnprintf(buf, PT_MAX_PRBUF_SIZE, + "Status: %d\n" + "DUT GPIO Reg: n/a\n", + rc); +} + +/******************************************************************************* + * FUNCTION: pt_device_exit + * + * SUMMARY: Remove functon for the I2C module + * + * PARAMETERS: + * *client - pointer to i2c client structure + ******************************************************************************/ +#ifdef PT_AMBIENT_MODE +static int pt_device_exit(struct i2c_client *client) +{ + + struct pt_core_data *cd = i2c_get_clientdata(client); + struct device *dev = cd->dev; + + void *glink_pt_send_msg; + int glink_touch_enter = TOUCH_ENTER; + + pt_debug(dev, DL_INFO,"%s: Start pt_device_exit\n", __func__); + + glink_pt_send_msg = &glink_touch_enter; + pt_debug(dev, DL_INFO, "[touch]glink_pt_send_msg = %0x\n", glink_pt_send_msg); + glink_touch_tx_msg(glink_pt_send_msg, TOUCH_MSG_SIZE); + + if (active_panel) + panel_event_notifier_unregister(cd->entry); + pt_core_state = STATE_SUSPEND; + + pm_runtime_suspend(dev); + pm_runtime_disable(dev); + + pt_stop_wd_timer(cd); + call_atten_cb(cd, PT_ATTEN_CANCEL_LOADER, 0); + cancel_work_sync(&cd->ttdl_restart_work); + cancel_work_sync(&cd->enum_work); + cancel_work_sync(&cd->resume_offload_work); + cancel_work_sync(&cd->suspend_offload_work); + cancel_work_sync(&cd->resume_work); + cancel_work_sync(&cd->suspend_work); + + pt_stop_wd_timer(cd); + device_init_wakeup(dev, 0); + disable_irq_nosync(cd->irq); + + if (cd->cpdata->setup_irq) + cd->cpdata->setup_irq(cd->cpdata, PT_MT_IRQ_FREE, dev); + + if (cd->cpdata->init) + cd->cpdata->init(cd->cpdata, PT_MT_POWER_OFF, dev); + + if (cd->cpdata->setup_power) + cd->cpdata->setup_power(cd->cpdata, PT_MT_POWER_OFF, dev); + + pt_debug(dev, DL_INFO,"%s: End pt_device_exit \n", __func__); + return 0; +} +#endif + +/******************************************************************************* + * FUNCTION: pt_touch_offload_store + * + * SUMMARY: The store method for the touch_offload sysfs node that allows the TTDL + * to be enabled/disabled. + * + * RETURN: Size of passed in buffer + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to buffer that hold the command parameters + * size - size of buf + ******************************************************************************/ +static ssize_t pt_touch_offload_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct i2c_client *client = to_i2c_client(dev); + u32 input_data[2]; + int length; + int rc = 0; + + /* Maximum input of one value */ + length = _pt_ic_parse_input(dev, buf, size, input_data, ARRAY_SIZE(input_data)); + if (length != 1) { + pt_debug(dev, DL_ERROR, "%s: Invalid number of arguments\n", __func__); + rc = -EINVAL; + goto exit; + } + + switch (input_data[0]) { + case 0: + pt_debug(dev, DL_ERROR, "%s: TTDL: Core Touch Offload OFF\n", __func__); + cd->touch_offload = true; + rc = pt_device_exit(client); + if (rc) + pt_debug(dev, DL_ERROR, "%s: Power off error detected rc=%d\n", + __func__, rc); + else { + cd->touch_offload = true; + pt_debug(dev, DL_ERROR, "%s: Debugfs PT DEVICE EXIT flag set:\n", + __func__); + } + break; + + case 1: + pt_debug(dev, DL_ERROR, "%s: TTDL: Core Touch Offload ON\n", __func__); + rc = pt_device_entry(&client->dev, client->irq, PT_DATA_SIZE); + if (rc) + pt_debug(dev, DL_ERROR, "%s: Power on error detected rc=%d\n", + __func__, rc); + else { + cd->touch_offload = false; + pt_debug(dev, DL_ERROR, "%s: Debugfs PT DEVICE ENTRY flag set:\n", + __func__); + } + break; + + default: + rc = -EINVAL; + pt_debug(dev, DL_ERROR, "%s: Invalid value\n", __func__); + } + +exit: + if (rc) + return rc; + return size; +} + +/******************************************************************************* + * FUNCTION: pt_touch_offload_show + * + * SUMMARY: The show method for the touch_offload sysfs node that allows the TTDL + * to verify touch offload enable or disabled. + * + * RETURN: size of data written to sysfs node + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to print output buffer + ******************************************************************************/ +static ssize_t pt_touch_offload_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + return scnprintf(buf, PT_MAX_PRBUF_SIZE, + "Touch offload : %s\n", + (cd->touch_offload)? "Enabled" : "Disabled"); +} + +/******************************************************************************* + * FUNCTION: pt_pip2_version_show + * + * SUMMARY: Sends a PIP2 VERSION command to the DUT and prints the + * contents of the response to the passed in output buffer. + * + * RETURN: size of data written to sysfs node + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes structure + * *buf - pointer to print output buffer + ******************************************************************************/ +static ssize_t pt_pip2_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int rc = 0; + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_ttdata *ttdata = &cd->sysinfo.ttdata; + + rc = pt_pip2_get_version(cd); + if (!rc) { + return scnprintf(buf, PT_MAX_PRBUF_SIZE, + "PIP VERSION : %02X.%02X\n" + "BL VERSION : %02X.%02X\n" + "FW VERSION : %02X.%02X\n" + "SILICON ID : %04X.%04X\n" + "UID : 0x%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X\n", + ttdata->pip_ver_major, ttdata->pip_ver_minor, + ttdata->bl_ver_major, ttdata->bl_ver_minor, + ttdata->fw_ver_major, ttdata->fw_ver_minor, + ttdata->chip_id, ttdata->chip_rev, + ttdata->uid[0], ttdata->uid[1], + ttdata->uid[2], ttdata->uid[3], + ttdata->uid[4], ttdata->uid[5], + ttdata->uid[6], ttdata->uid[7], + ttdata->uid[8], ttdata->uid[9], + ttdata->uid[10], ttdata->uid[11]); + } else { + pt_debug(dev, DL_ERROR, + "%s: Failed to retriev PIP2 VERSION data\n", __func__); + + return scnprintf(buf, PT_MAX_PRBUF_SIZE, + "PIP VERSION : -\n" + "BL VERSION : -\n" + "FW VERSION : -\n" + "SILICON ID : -\n" + "UID : -\n"); + } +} + +#ifdef TTDL_DIAGNOSTICS +/******************************************************************************* + * FUNCTION: pt_ttdl_status_show + * + * SUMMARY: Show method for the ttdl_status sysfs node. Displays TTDL internal + * variable states and GPIO levels. Additional information printed when + * TTDL_DIAGNOSTICS is enabled. + * + * NOTE: All counters will be reset to 0 when this function is called. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to output buffer + ******************************************************************************/ +static ssize_t pt_ttdl_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_platform_data *pdata = dev_get_platdata(dev); + struct i2c_client *client = to_i2c_client(dev); + ssize_t ret; + u16 cal_size = 0; + unsigned short crc = 0; + + if (cd->cal_cache_in_host) + _pt_manage_local_cal_data(dev, + PT_CAL_DATA_INFO, &cal_size, &crc); + ret = scnprintf(buf, strlen(buf), + "%s: 0x%04X\n" + "%s: %d\n" + "%s: %s\n" + "%s: %s %s\n" + "%s: %s\n" + "%s: 0x%02X\n" + "%s: %s\n" + "%s: %s\n" + "%s: %s\n" + "%s: %s\n" + "%s: %d\n" + "%s: %d\n" + "%s: %s\n" + "%s: %s\n" + "%s: %d\n" +#ifdef TTDL_DIAGNOSTICS + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %s\n" + "%s: %s\n" + "%s: %s\n" + "%s: %d\n" + "%s: 0x%04X\n" + "%s: %s\n" +#endif /* TTDL_DIAGNOSTICS */ + , + "Startup Status ", cd->startup_status, + "TTDL Debug Level ", cd->debug_level, + "Mode ", + cd->mode ? (cd->mode == PT_MODE_OPERATIONAL ? + "Operational" : "BL") : "Unknown", + "DUT Generation ", + cd->active_dut_generation ? + (cd->active_dut_generation == DUT_PIP2_CAPABLE ? + "PT TC/TT" : "Gen5/6") : "Unknown", + cd->active_dut_generation ? + (cd->set_dut_generation == true ? + "(Set)" : "(Detected)") : "", + "HW Detected ", + cd->hw_detected ? "True" : "False", + "I2C Address ", + cd->bus_ops->bustype == BUS_I2C ? client->addr : 0, + "Active Bus Module ", + cd->bus_ops->bustype == BUS_I2C ? "I2C" : "SPI", + "Flashless Mode ", + cd->flashless_dut == 1 ? "Yes" : "No", + "GPIO state - IRQ ", + cd->cpdata->irq_stat ? + (cd->cpdata->irq_stat(cd->cpdata, dev) ? + "High" : "Low") : "not defined", + "GPIO state - TP_XRES ", + pdata->core_pdata->rst_gpio ? + (gpio_get_value(pdata->core_pdata->rst_gpio) ? + "High" : "Low") : "not defined", + "RAM Parm restore list ", pt_count_parameter_list(cd), + "Startup Retry Count ", cd->startup_retry_count, + "WD - Manual Force Stop ", + cd->watchdog_force_stop ? "True" : "False", + "WD - Enabled ", + cd->watchdog_enabled ? "True" : "False", + "WD - Interval (ms) ", cd->watchdog_interval +#ifdef TTDL_DIAGNOSTICS + , "WD - Triggered Count ", cd->watchdog_count, + "WD - IRQ Stuck low count ", cd->watchdog_irq_stuck_count, + "WD - Device Access Errors ", cd->watchdog_failed_access_count, + "WD - XRES Count ", cd->wd_xres_count, + "IRQ Triggered Count ", cd->irq_count, + "BL Packet Retry Count ", cd->bl_retry_packet_count, + "PIP2 CRC Error Count ", cd->pip2_crc_error_count, + "Bus Transmit Error Count ", cd->bus_transmit_error_count, + "File Erase Timeout Count ", cd->file_erase_timeout_count, + "Error GPIO trigger type ", cd->err_gpio_type, + "Exclusive Access Lock ", cd->exclusive_dev ? "Set":"Free", + "Suppress No-Flash Auto BL ", + cd->flashless_auto_bl == PT_SUPPRESS_AUTO_BL ? + "Yes" : "No", + "Calibration Cache on host ", + cd->cal_cache_in_host == PT_FEATURE_ENABLE ? + "Yes" : "No", + "Calibration Cache size ", cal_size, + "Calibration Cache chip ID ", crc, + "Multi-Chip Support ", + cd->multi_chip == PT_FEATURE_ENABLE ? "Yes" : "No" +#endif /* TTDL_DIAGNOSTICS */ + ); + +#ifdef TTDL_DIAGNOSTICS + /* Reset all diagnostic counters */ + cd->watchdog_count = 0; + cd->watchdog_irq_stuck_count = 0; + cd->watchdog_failed_access_count = 0; + cd->wd_xres_count = 0; + cd->irq_count = 0; + cd->bl_retry_packet_count = 0; + cd->pip2_crc_error_count = 0; + cd->bus_transmit_error_count = 0; +#endif + + return ret; +} +static DEVICE_ATTR(ttdl_status, 0444, pt_ttdl_status_show, NULL); + +/******************************************************************************* + * FUNCTION: pt_pip2_enter_bl_show + * + * SUMMARY: Show method for the pip2_enter_bl sysfs node that will force + * the DUT into the BL and show the success or failure of entering the BL + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to output buffer + ******************************************************************************/ +static ssize_t pt_pip2_enter_bl_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t ret = 0; + int rc = 0; + int result = 0; + u8 mode = PT_MODE_UNKNOWN; + struct pt_core_data *cd = dev_get_drvdata(dev); + + /* Turn off the TTDL WD before enter bootloader */ + pt_stop_wd_timer(cd); + + /* Ensure NO enumeration work is queued or will be queued */ + cancel_work_sync(&cd->enum_work); + mutex_lock(&cd->system_lock); + cd->bridge_mode = true; + mutex_unlock(&cd->system_lock); + + /* set mode to operational to avoid any extra PIP traffic */ + rc = _pt_request_pip2_enter_bl(dev, &mode, &result); + switch (result) { + case PT_ENTER_BL_PASS: + ret = scnprintf(buf, PT_MAX_PRBUF_SIZE, + "Status: %d\nEntered BL\n", PT_ENTER_BL_PASS); + break; + case PT_ENTER_BL_ERROR: + ret = scnprintf(buf, PT_MAX_PRBUF_SIZE, "Status: %d\n%s\n", + rc, + " Unknown Error"); + break; + case PT_ENTER_BL_RESET_FAIL: + ret = scnprintf(buf, PT_MAX_PRBUF_SIZE, "Status: %d\n%s\n", + rc, + " Soft Reset Failed"); + break; + case PT_ENTER_BL_HID_START_BL_FAIL: + ret = scnprintf(buf, PT_MAX_PRBUF_SIZE, "Status: %d\n%s\n", + rc, + " PIP Start BL Cmd Failed"); + break; + case PT_ENTER_BL_CONFIRM_FAIL: + ret = scnprintf(buf, PT_MAX_PRBUF_SIZE, "Status: %d\n%s\n", + rc, + " Error confirming DUT entered BL"); + break; + default: + ret = scnprintf(buf, PT_MAX_PRBUF_SIZE, "Status: %d\n%s\n", + rc, " Unknown Error"); + break; + }; + + /* Allow enumeration work to be queued again */ + cd->bridge_mode = false; + + return ret; +} +static DEVICE_ATTR(pip2_enter_bl, 0444, pt_pip2_enter_bl_show, NULL); + +/******************************************************************************* + * FUNCTION: pt_pip2_exit_bl_show + * + * SUMMARY: Show method for the pip2_exit_bl sysfs node that will attempt to + * launch the APP and put the DUT Application mode + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to output buffer + ******************************************************************************/ +static ssize_t pt_pip2_exit_bl_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + ssize_t ret = 0; + int rc = 0; + u8 status_str[PT_STATUS_STR_LEN]; + + rc = pt_pip2_exit_bl_(cd, status_str, PT_STATUS_STR_LEN); + /* + * Perform enum if startup_status doesn't reach to + * STARTUP_STATUS_FW_OUT_OF_BOOT. + */ + if (!rc && (!(cd->startup_status & STARTUP_STATUS_FW_OUT_OF_BOOT))) { + rc = pt_enum_with_dut(cd, false, &cd->startup_status); + if (!(cd->startup_status & STARTUP_STATUS_FW_OUT_OF_BOOT)) { + strlcpy(status_str, + "Already in APP mode - FW stuck in Boot mode", sizeof(status_str)); + } + } + ret = scnprintf(buf, PT_MAX_PRBUF_SIZE, "Status: %d\n%s\n", + rc, status_str); + return ret; +} +static DEVICE_ATTR(pip2_exit_bl, 0444, pt_pip2_exit_bl_show, NULL); +#endif + +#ifdef EASYWAKE_TSG6 +/******************************************************************************* + * FUNCTION: pt_easy_wakeup_gesture_show + * + * SUMMARY: Show method for the easy_wakeup_gesture sysfs node that will show + * current easy wakeup gesture + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to output buffer + ******************************************************************************/ +static ssize_t pt_easy_wakeup_gesture_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + ssize_t ret; + + mutex_lock(&cd->system_lock); + ret = scnprintf(buf, PT_MAX_PRBUF_SIZE, "0x%02X\n", + cd->easy_wakeup_gesture); + mutex_unlock(&cd->system_lock); + return ret; +} + +/******************************************************************************* + * FUNCTION: pt_easy_wakeup_gesture_store + * + * SUMMARY: The store method for the easy_wakeup_gesture sysfs node that + * allows the wake gesture to be set to a custom value. + * + * RETURN: Size of passed in buffer is success + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to buffer that hold the command parameters + * size - size of buf + ******************************************************************************/ +static ssize_t pt_easy_wakeup_gesture_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + u32 input_data[2]; + int length; + int rc = 0; + + /* Maximum input of one value */ + length = _pt_ic_parse_input(dev, buf, size, input_data, + ARRAY_SIZE(input_data)); + if (length != 1) { + pt_debug(dev, DL_ERROR, "%s: Invalid number of arguments\n", + __func__); + rc = -EINVAL; + goto exit; + } + + pt_debug(dev, DL_INFO, "%s: features.easywake = 0x%02X\n", + __func__, cd->features.easywake); + + if (!cd->features.easywake || input_data[0] > 0xFF) { + rc = -EINVAL; + goto exit; + } + + + pm_runtime_get_sync(dev); + mutex_lock(&cd->system_lock); + + if (cd->sysinfo.ready && IS_PIP_VER_GE(&cd->sysinfo, 1, 2)) { + cd->easy_wakeup_gesture = (u8)input_data[0]; + pt_debug(dev, DL_INFO, + "%s: Updated easy_wakeup_gesture = 0x%02X\n", + __func__, cd->easy_wakeup_gesture); + } else + rc = -ENODEV; + + mutex_unlock(&cd->system_lock); + pm_runtime_put(dev); + +exit: + if (rc) + return rc; + return size; +} + +/******************************************************************************* + * FUNCTION: pt_easy_wakeup_gesture_id_show + * + * SUMMARY: Show method for the easy_wakeup_gesture_id sysfs node that will + * show the TSG6 easywake gesture ID + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to output buffer + ******************************************************************************/ +static ssize_t pt_easy_wakeup_gesture_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + ssize_t ret; + + mutex_lock(&cd->system_lock); + ret = scnprintf(buf, PT_MAX_PRBUF_SIZE, "Status: 0\n0x%02X\n", + cd->gesture_id); + mutex_unlock(&cd->system_lock); + return ret; +} + +/******************************************************************************* + * FUNCTION: pt_easy_wakeup_gesture_data_show + * + * SUMMARY: Show method for the easy_wakeup_gesture_data sysfs node that will + * show the TSG6 easywake gesture data in the following format: + * x1(LSB),x1(MSB), y1(LSB),y1(MSB), x2(LSB),x2(MSB), y2(LSB),y2(MSB),... + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to output buffer + ******************************************************************************/ +static ssize_t pt_easy_wakeup_gesture_data_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + ssize_t ret = 0; + int i; + + mutex_lock(&cd->system_lock); + ret += scnprintf(buf + ret, PT_MAX_PRBUF_SIZE - ret, "Status: %d\n", 0); + for (i = 0; i < cd->gesture_data_length; i++) + ret += scnprintf(buf + ret, PT_MAX_PRBUF_SIZE - ret, + "0x%02X\n", cd->gesture_data[i]); + + ret += scnprintf(buf + ret, PT_MAX_PRBUF_SIZE - ret, + "(%d bytes)\n", cd->gesture_data_length); + + mutex_unlock(&cd->system_lock); + return ret; +} +#endif /* EASYWAKE_TSG6 */ + +#ifdef TTDL_DIAGNOSTICS +/******************************************************************************* + * FUNCTION: pt_err_gpio_show + * + * SUMMARY: Show method for the err_gpio sysfs node that will show if + * setting up the gpio was successful + * + * RETURN: Char buffer with printed GPIO creation state + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to output buffer + ******************************************************************************/ +static ssize_t pt_err_gpio_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + return scnprintf(buf, strlen(buf), "Status: 0\n" + "Err GPIO (%d) : %s\n" + "Err GPIO trig type: %d\n", + cd->err_gpio, + (cd->err_gpio ? (gpio_get_value(cd->err_gpio) ? + "HIGH" : "low") : "not defined"), + cd->err_gpio_type); +} + +/******************************************************************************* + * FUNCTION: pt_err_gpio_store + * + * SUMMARY: The store method for the err_gpio sysfs node that allows any + * available host GPIO to be used to trigger when TTDL detects a PIP + * command/response timeout. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to buffer that hold the command parameters + * size - size of buf + ******************************************************************************/ +static ssize_t pt_err_gpio_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + unsigned long gpio; + int rc = 0; + u32 input_data[3]; + int length; + u8 err_type; + + input_data[0] = 0; + input_data[1] = 0; + /* Maximmum input is two elements */ + length = _pt_ic_parse_input(dev, buf, size, + input_data, ARRAY_SIZE(input_data)); + if (length < 1 || length > 2) { + pt_debug(dev, DL_ERROR, "%s: Invalid number of arguments\n", + __func__); + rc = -EINVAL; + goto exit; + } + + gpio = input_data[0]; + err_type = (u8)input_data[1]; + if (err_type < 0 || err_type > PT_ERR_GPIO_MAX_TYPE) { + rc = -EINVAL; + goto exit; + } + + mutex_lock(&cd->system_lock); + gpio_free(gpio); + rc = gpio_request(gpio, NULL); + if (rc) { + pt_debug(dev, DL_ERROR, "error requesting gpio %lu\n", gpio); + rc = -ENODEV; + } else { + cd->err_gpio = gpio; + cd->err_gpio_type = err_type; + gpio_direction_output(gpio, 0); + } + mutex_unlock(&cd->system_lock); + +exit: + if (rc) + return rc; + return size; +} +static DEVICE_ATTR(err_gpio, 0644, pt_err_gpio_show, + pt_err_gpio_store); + +/******************************************************************************* + * FUNCTION: pt_drv_irq_show + * + * SUMMARY: Show method for the drv_irq sysfs node that will show if the + * TTDL interrupt is enabled/disabled + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to output buffer + ******************************************************************************/ +static ssize_t pt_drv_irq_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + ssize_t ret = 0; + + mutex_lock(&cd->system_lock); + ret += scnprintf(buf, PT_MAX_PRBUF_SIZE, "Status: %d\n", 0); + if (cd->irq_enabled) + ret += scnprintf(buf + ret, PT_MAX_PRBUF_SIZE - ret, + "Driver interrupt: ENABLED\n"); + else + ret += scnprintf(buf + ret, PT_MAX_PRBUF_SIZE - ret, + "Driver interrupt: DISABLED\n"); + mutex_unlock(&cd->system_lock); + + return ret; +} + +/******************************************************************************* + * FUNCTION: pt_drv_irq_store + * + * SUMMARY: The store method for the drv_irq sysfs node that allows the TTDL + * IRQ to be enabled/disabled. + * + * RETURN: Size of passed in buffer + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to buffer that hold the command parameters + * size - size of buf + ******************************************************************************/ +static ssize_t pt_drv_irq_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + u32 input_data[2]; + int length; + int rc = 0; + + /* Maximum input of one value */ + length = _pt_ic_parse_input(dev, buf, size, input_data, + ARRAY_SIZE(input_data)); + if (length != 1) { + pt_debug(dev, DL_ERROR, "%s: Invalid number of arguments\n", + __func__); + rc = -EINVAL; + goto exit; + } + + mutex_lock(&cd->system_lock); + switch (input_data[0]) { + case 0: + if (cd->irq_enabled) { + cd->irq_enabled = false; + /* Disable IRQ has no return value to check */ + disable_irq_nosync(cd->irq); + pt_debug(dev, DL_INFO, + "%s: Driver IRQ now disabled\n", + __func__); + } else + pt_debug(dev, DL_INFO, + "%s: Driver IRQ already disabled\n", + __func__); + break; + + case 1: + if (cd->irq_enabled == false) { + cd->irq_enabled = true; + enable_irq(cd->irq); + pt_debug(dev, DL_INFO, + "%s: Driver IRQ now enabled\n", + __func__); + } else + pt_debug(dev, DL_INFO, + "%s: Driver IRQ already enabled\n", + __func__); + break; + + default: + rc = -EINVAL; + pt_debug(dev, DL_ERROR, "%s: Invalid value\n", __func__); + } + mutex_unlock(&(cd->system_lock)); + +exit: + if (rc) + return rc; + return size; +} + +/******************************************************************************* + * FUNCTION: pt_pip2_bin_hdr_show + * + * SUMMARY: Show method for the pip2_bin_hdr sysfs node that will read + * the bin file header from flash and show each field + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to output buffer + ******************************************************************************/ +static ssize_t pt_pip2_bin_hdr_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t ret = 0; + struct pt_bin_file_hdr hdr = {0}; + int rc; + + rc = _pt_request_pip2_bin_hdr(dev, &hdr); + + ret = scnprintf(buf, strlen(buf), + "%s: %d\n" + "%s: %d\n" + "%s: 0x%04X\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: 0x%08X\n" + "%s: 0x%04X\n" + "%s: 0x%04X\n" + "%s: %d\n" + "%s: %d\n", + "Status", rc, + "Header Length ", hdr.length, + "TTPID ", hdr.ttpid, + "FW Major Ver ", hdr.fw_major, + "FW Minor Ver ", hdr.fw_minor, + "FW Rev Control ", hdr.fw_rev_ctrl, + "FW CRC ", hdr.fw_crc, + "Silicon Rev ", hdr.si_rev, + "Silicon ID ", hdr.si_id, + "Config Ver ", hdr.config_ver, + "HEX File Size ", hdr.hex_file_size + ); + return ret; +} + +/******************************************************************************* + * FUNCTION: pt_platform_data_show + * + * SUMMARY: Show method for the platform_data sysfs node that will show the + * active platform data including: GPIOs, Vendor and Product IDs, + * Virtual Key coordinates, Core/MT/Loader flags, Level trigger delay, + * HID registers, and Easy wake gesture + * + * RETURN: Size of printed data + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to output buffer + ******************************************************************************/ +static ssize_t pt_platform_data_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pt_platform_data *pdata = dev_get_platdata(dev); + struct pt_core_data *cd = dev_get_drvdata(dev); + ssize_t ret; + + ret = scnprintf(buf, strlen(buf), + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %s\n" + "%s: %s\n" + "%s: %d\n", + "Status", 0, + "Interrupt GPIO ", pdata->core_pdata->irq_gpio, + "Interrupt GPIO Value ", + pdata->core_pdata->irq_gpio ? + gpio_get_value(pdata->core_pdata->irq_gpio) : 0, + "Reset GPIO ", pdata->core_pdata->rst_gpio, + "Reset GPIO Value ", + pdata->core_pdata->rst_gpio ? + gpio_get_value(pdata->core_pdata->rst_gpio) : 0, + "DDI Reset GPIO ", pdata->core_pdata->ddi_rst_gpio, + "DDI Reset GPIO Value ", + pdata->core_pdata->ddi_rst_gpio ? + gpio_get_value(pdata->core_pdata->ddi_rst_gpio) : 0, + "VDDI GPIO ", pdata->core_pdata->vddi_gpio, + "VDDI GPIO Value ", + pdata->core_pdata->vddi_gpio ? + gpio_get_value(pdata->core_pdata->vddi_gpio) : 0, + "VCC GPIO ", pdata->core_pdata->vcc_gpio, + "VCC GPIO Value ", + pdata->core_pdata->vcc_gpio ? + gpio_get_value(pdata->core_pdata->vcc_gpio) : 0, + "AVDD GPIO ", pdata->core_pdata->avdd_gpio, + "AVDD GPIO Value ", + pdata->core_pdata->avdd_gpio ? + gpio_get_value(pdata->core_pdata->avdd_gpio) : 0, + "AVEE GPIO ", pdata->core_pdata->avee_gpio, + "AVEE GPIO Value ", + pdata->core_pdata->avee_gpio ? + gpio_get_value(pdata->core_pdata->avee_gpio) : 0, + "Vendor ID ", pdata->core_pdata->vendor_id, + "Product ID ", pdata->core_pdata->product_id, + "Vkeys x ", pdata->mt_pdata->vkeys_x, + "Vkeys y ", pdata->mt_pdata->vkeys_y, + "Core data flags ", pdata->core_pdata->flags, + "MT data flags ", pdata->mt_pdata->flags, + "Loader data flags ", pdata->loader_pdata->flags, + "Level trigger delay (us) ", + pdata->core_pdata->level_irq_udelay, + "HID Descriptor Register ", + pdata->core_pdata->hid_desc_register, + "HID Output Register ", + cd->hid_desc.output_register, + "HID Command Register ", + cd->hid_desc.command_register, + "Easy wakeup gesture ", + pdata->core_pdata->easy_wakeup_gesture, + "Config DUT generation ", + pdata->core_pdata->config_dut_generation ? + (pdata->core_pdata->config_dut_generation == + CONFIG_DUT_PIP2_CAPABLE ? + "PT TC/TT" : "Gen5/6") : "Auto", + "Watchdog Force Stop ", + pdata->core_pdata->watchdog_force_stop ? + "True" : "False", + "Panel ID Support ", + pdata->core_pdata->panel_id_support); + return ret; +} + +#define PT_ERR_STR_SIZE 64 +/******************************************************************************* + * FUNCTION: pt_bist_bus_test + * + * SUMMARY: Tests the connectivity of the active bus pins: + * I2C - SDA and SCL + * SPI - MOSI, MISO, CLK + * + * Disable TTDL interrupts, send a PIP cmd and then manually read the + * bus. If any data is read we know the I2C/SPI pins are connected + * + * RETURN: + * 0 = Success + * !0 = Failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *net_toggled - pointer to where to store if bus toggled + * *err_str - pointer to error string buffer + ******************************************************************************/ +static int pt_bist_bus_test(struct device *dev, u8 *net_toggled, u8 *err_str) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + u8 ver_cmd[8] = {0x01, 0x01, 0x06, 0x00, 0x0E, 0x07, 0xF0, 0xB1}; + u8 *read_buf = NULL; + int bytes_read = 0; + int rc = 0; + + read_buf = kzalloc(PT_MAX_PIP1_MSG_SIZE, GFP_KERNEL); + if (read_buf == NULL) { + rc = -ENOMEM; + goto exit; + } + + bytes_read = pt_flush_bus(cd, PT_FLUSH_BUS_BASED_ON_LEN, NULL); + + pt_debug(dev, DL_INFO, "%s: TTDL Core Suspend\n", __func__); + disable_irq(cd->irq); + mutex_lock(&cd->system_lock); + cd->irq_disabled = true; + mutex_unlock(&cd->system_lock); + + /* + * Sleep >4ms to allow any pending TTDL IRQ to finish. Without this + * the disable_irq_nosync() could cause the IRQ to get stuck asserted + */ + usleep_range(5000, 6000); + + pt_debug(cd->dev, DL_INFO, ">>> %s: Write Buffer Size[%d] VERSION\n", + __func__, (int)sizeof(ver_cmd)); + + pt_pr_buf(cd->dev, DL_DEBUG, ver_cmd, (int)sizeof(ver_cmd), + ">>> User CMD"); + rc = pt_adap_write_read_specific(cd, sizeof(ver_cmd), ver_cmd, NULL); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: BUS Test - Failed to send VER cmd\n", __func__); + *net_toggled = 0; + strlcpy(err_str, + "- Write failed, bus open or shorted or DUT in reset", PT_ERR_STR_SIZE); + goto exit_enable_irq; + } + usleep_range(4000, 5000); + bytes_read = pt_flush_bus(cd, PT_FLUSH_BUS_BASED_ON_LEN, read_buf); + pt_debug(dev, DL_INFO, "%s: BUS Test - %d bytes forced read\n", + __func__, bytes_read); + if (bytes_read == 0) { + *net_toggled = 0; + pt_debug(dev, DL_INFO, "%s: BUS Read Failed, 0 bytes read\n", + __func__); + strlcpy(err_str, + "- Bus open, shorted or DUT in reset", PT_ERR_STR_SIZE); + rc = -EIO; + goto exit_enable_irq; + } else { + if (cd->bus_ops->bustype == BUS_I2C) + *net_toggled = 1; + else { + if ((bytes_read > 3) && + (read_buf[3] & PIP2_CMD_COMMAND_ID_MASK) == + PIP2_CMD_ID_VERSION) + *net_toggled = 1; + else { + *net_toggled = 0; + pt_debug(dev, DL_INFO, + "%s: BUS Read Failed, %d bytes read\n", + __func__, bytes_read); + strlcpy(err_str, + "- Bus open, shorted or DUT in reset", PT_ERR_STR_SIZE); + } + } + } + +exit_enable_irq: + enable_irq(cd->irq); + usleep_range(5000, 6000); + mutex_lock(&cd->system_lock); + cd->irq_disabled = false; + mutex_unlock(&cd->system_lock); + pt_debug(dev, DL_INFO, "%s: TTDL Core Resumed\n", __func__); + +exit: + kfree(read_buf); + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_bist_irq_test + * + * SUMMARY: Tests the connectivity of the IRQ net + * + * This test will ensure there is a good connection between the host + * and the DUT on the irq pin. First determine if the IRQ is stuck + * asserted and if so keep reading messages off of the bus until + * it de-asserts. Possible outcomes: + * - IRQ was already de-asserted: Send a PIP command and if an + * interrupt is generated the test passes. + * - IRQ was asserted: Reading off the bus de-assertes the IRQ, + * test passes. + * - IRQ stays asserted: After reading the bus multiple times + * the IRQ stays asserted. Likely open or shorted to GND + * + * RETURN: + * 0 = Success + * !0 = Failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *bus_toggled - pointer to where to store if bus toggled + * *irq_toggled - pointer to where to store if IRQ toggled + * *xres_toggled - pointer to where to store if XRES toggled + * *err_str - pointer to error string buffer + ******************************************************************************/ +static int pt_bist_irq_test(struct device *dev, + u8 *bus_toggled, u8 *irq_toggled, u8 *xres_toggled, u8 *err_str) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + u8 *read_buf = NULL; + u8 mode = PT_MODE_UNKNOWN; + u16 actual_read_len; + int bytes_read = 0; + int count = 0; + int rc = 0; + + read_buf = kzalloc(PT_MAX_PIP1_MSG_SIZE, GFP_KERNEL); + if (read_buf == NULL) { + rc = -ENOMEM; + goto exit; + } + + /* Clear IRQ triggered count, and re-evaluate at the end of test */ + cd->irq_count = 0; + + /* + * Check if IRQ is stuck asserted, if so try a non forced flush of + * the bus based on the 2 byte initial length read. Try up to 5x. + */ + while (pt_check_irq_asserted(cd) && count < 5) { + count++; + bytes_read += pt_flush_bus(cd, PT_FLUSH_BUS_BASED_ON_LEN, NULL); + } + + if (count > 1 && count < 5 && bytes_read > 0) { + /* + * IRQ was stuck but data was successfully read from the + * bus releasing the IRQ line. + */ + pt_debug(dev, DL_INFO, "%s: count=%d bytes_read=%d\n", + __func__, count, bytes_read); + *bus_toggled = 1; + *irq_toggled = 1; + goto exit; + } + + if (count == 5 && bytes_read == 0) { + /* + * Looped 5x and read nothing off the bus yet the IRQ is still + * asserted. Possible conditions: + * - IRQ open circuit + * - IRQ shorted to GND + * - I2C/SPI bus is disconnected + * - FW holding the pin low + * Try entering the BL to see if communication works there. + */ + mode = PT_MODE_IGNORE; + rc = _pt_request_pip2_enter_bl(dev, &mode, NULL); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s Failed to enter BL\n", __func__); + strlcpy(err_str, + "- likely shorted to GND or FW holding it.", PT_ERR_STR_SIZE); + *irq_toggled = 0; + goto exit; + } + /* + * If original mode was operational and we successfully + * entered the BL, then the XRES net must have toggled + */ + if (mode == PT_MODE_OPERATIONAL) + *xres_toggled = 1; + + rc = _pt_request_pip2_send_cmd(dev, PT_CORE_CMD_PROTECTED, + PIP2_CMD_ID_VERSION, NULL, 0, read_buf, + &actual_read_len); + if (rc) { + /* + * Could not communicate to DUT in BL mode. Save the + * error string, slim chance but the XRES test below may + * show the IRQ is actually working. + */ + strlcpy(err_str, "- likely shorted to GND.", PT_ERR_STR_SIZE); + pt_debug(dev, DL_ERROR, + "%s: %s, count=%d bytes_read=%d\n", + __func__, err_str, count, bytes_read); + *irq_toggled = 0; + rc = pt_pip2_exit_bl_(cd, NULL, 0); + goto exit; + } else { + *bus_toggled = 1; + *irq_toggled = 1; + goto exit; + } + } + if (pt_check_irq_asserted(cd)) { + strlcpy(err_str, "- likely shorted to GND", PT_ERR_STR_SIZE); + rc = -EIO; + *irq_toggled = 0; + goto exit; + } + + /* Try sending a PIP command to see if we get a response */ + rc = _pt_request_pip2_send_cmd(dev, PT_CORE_CMD_PROTECTED, + PIP2_CMD_ID_VERSION, NULL, 0, read_buf, &actual_read_len); + if (rc) { + /* + * Potential IRQ issue, no communication in App mode, attempt + * the same command in the BL + */ + mode = PT_MODE_IGNORE; + rc = _pt_request_pip2_enter_bl(dev, &mode, NULL); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s Failed to enter BL\n", __func__); + *irq_toggled = 0; + strlcpy(err_str, "- likely open or shorted to VDDI.", PT_ERR_STR_SIZE); + goto exit; + } + /* + * If original mode was operational and we successfully + * entered the BL, this will be useful info for the tp_xres + * test below. + */ + if (mode == PT_MODE_OPERATIONAL) + *xres_toggled = 1; + + rc = _pt_request_pip2_send_cmd(dev, PT_CORE_CMD_PROTECTED, + PIP2_CMD_ID_VERSION, NULL, 0, read_buf, + &actual_read_len); + if (rc) { + /* + * Could not communicate in FW mode or BL mode. Save the + * error string, slim chance but the XRES test below may + * show the IRQ is actually working. + */ + strlcpy(err_str, "- likely open or shorted to VDDI.", PT_ERR_STR_SIZE); + pt_debug(dev, DL_ERROR, + "%s: request_active_pip_prot failed\n", + __func__); + *irq_toggled = 0; + goto exit; + } + } + + if (cd->irq_count > 0) { + pt_debug(dev, DL_INFO, "%s: irq_count=%d\n", __func__, + cd->irq_count); + *bus_toggled = 1; + *irq_toggled = 1; + goto exit; + } + +exit: + kfree(read_buf); + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_bist_xres_test + * + * SUMMARY: Tests the connectivity of the TP_XRES net + * + * This test will ensure there is a good connection between the host + * and the DUT on the tp_xres pin. The pin will be toggled to + * generate a TP reset which will cause the DUT to output a reset + * sentinel. If the reset sentinel is seen the test passes. If it is + * not seen the test will attempt to send a soft reset to simply gain + * some additional information on the failure: + * - soft reset fails to send: XRES and IRQ likely open + * - soft reset passes: XRES likely open or stuck de-asserted + * - soft reset fails: XRES likely stuck asserted + * + * RETURN: + * 0 = Success + * !0 = Failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *bus_toggled - pointer to where to store if bus toggled + * *irq_toggled - pointer to where to store if IRQ toggled + * *xres_toggled - pointer to where to store if XRES toggled + * *err_str - pointer to error string buffer + ******************************************************************************/ +static int pt_bist_xres_test(struct device *dev, + u8 *bus_toggled, u8 *irq_toggled, u8 *xres_toggled, u8 *err_str) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_platform_data *pdata = dev_get_platdata(dev); + u8 *read_buf = NULL; + u8 mode = PT_MODE_UNKNOWN; + int rc = 0; + int t = 0; + int timeout = 300; + + read_buf = kzalloc(PT_MAX_PIP1_MSG_SIZE, GFP_KERNEL); + if (read_buf == NULL) { + rc = -ENOMEM; + goto exit; + } + + /* Clear the startup bit mask, reset and enum will re-populate it */ + cd->startup_status = STARTUP_STATUS_START; + pt_debug(dev, DL_DEBUG, "%s: Startup Status Reset\n", __func__); + + if ((!pdata->core_pdata->rst_gpio) || (!pdata->core_pdata->xres)) { + strlcpy(err_str, "- Net not configured or available", PT_ERR_STR_SIZE); + rc = -ENODEV; + goto exit; + } + + + /* Ensure we have nothing pending on active bus */ + pt_flush_bus_if_irq_asserted(cd, PT_FLUSH_BUS_BASED_ON_LEN); + + /* Perform a hard XRES toggle and wait for reset sentinel */ + mutex_lock(&cd->system_lock); + cd->hid_reset_cmd_state = 1; + mutex_unlock(&cd->system_lock); + pt_debug(dev, DL_INFO, "%s: Perform a hard reset\n", __func__); + rc = pt_hw_hard_reset(cd); + + /* Set timeout to 1s for the flashless case where a BL could be done */ + if (cd->flashless_dut) + timeout = 1000; + + /* + * To avoid the case that next PIP command can be confused by BL/FW + * sentinel's "wakeup" event, chekcing hid_reset_cmd_state which is + * followed by "wakeup event" function can lower the failure rate. + */ + t = wait_event_timeout(cd->wait_q, + ((cd->startup_status != STARTUP_STATUS_START) + && (cd->hid_reset_cmd_state == 0)), + msecs_to_jiffies(timeout)); + if (IS_TMO(t)) { + pt_debug(cd->dev, DL_ERROR, + "%s: TMO waiting for sentinel\n", __func__); + *xres_toggled = 0; + strlcpy(err_str, "- likely open. (No Reset Sentinel)", PT_ERR_STR_SIZE); + + /* + * Possibly bad FW, Try entering BL and wait for reset sentinel. + * To enter the BL we need to generate an XRES so first try to + * launch the applicaiton + */ + if (cd->mode == PT_MODE_BOOTLOADER) + pt_pip2_launch_app(dev, PT_CORE_CMD_PROTECTED); + pt_debug(dev, DL_INFO, "%s: Enter BL with a hard reset\n", + __func__); + mode = PT_MODE_IGNORE; + rc = _pt_request_pip2_enter_bl(dev, &mode, NULL); + if (rc) { + pt_debug(dev, DL_ERROR, "%s Failed to enter BL\n", + __func__); + *xres_toggled = 0; + strlcpy(err_str, "- likely open or shorted to VDDI.", PT_ERR_STR_SIZE); + goto exit; + } else { + /* Wait for the BL sentinel */ + t = wait_event_timeout(cd->wait_q, + (cd->startup_status != STARTUP_STATUS_START), + msecs_to_jiffies(500)); + if (IS_TMO(t)) { + pt_debug(cd->dev, DL_ERROR, + "%s: TMO waiting for BL sentinel\n", + __func__); + *xres_toggled = 0; + strlcpy(err_str, + "- likely open or shorted to VDDI.", PT_ERR_STR_SIZE); + rc = -ETIME; + goto exit; + } + } + } + mutex_lock(&cd->system_lock); + cd->hid_reset_cmd_state = 0; + mutex_unlock(&cd->system_lock); + + /* Look for BL or FW reset sentinels */ + if (!rc && ((cd->startup_status & STARTUP_STATUS_BL_RESET_SENTINEL) || + (cd->startup_status & STARTUP_STATUS_FW_RESET_SENTINEL))) { + pt_debug(dev, DL_INFO, "%s: hard XRES pass\n", __func__); + /* If a sentinel was seen, all nets are working */ + *xres_toggled = 1; + *irq_toggled = 1; + /* + * For SPI test, bus read result can be confused as FW sentinel + * if MISO(slave) is connected to MISO(host). + */ + if (cd->bus_ops->bustype == BUS_I2C) + *bus_toggled = 1; + } else { + /* + * Hard reset failed, however some additional information + * could be determined. Try a soft reset to see if DUT resets + * with the possible outcomes: + * - if it resets the line is not stuck asserted + * - if it does not reset the line could be stuck asserted + */ + *xres_toggled = 0; + rc = pt_hw_soft_reset(cd, PT_CORE_CMD_PROTECTED); + msleep(30); + pt_debug(dev, DL_INFO, "%s: TP_XRES BIST soft reset rc=%d", + __func__, rc); + if (rc) { + strlcpy(err_str, "- likely open.", PT_ERR_STR_SIZE); + pt_debug(dev, DL_ERROR, + "%s: Hard reset failed, soft reset failed %s\n", + __func__, err_str); + goto exit; + } + if (cd->startup_status & STARTUP_STATUS_BL_RESET_SENTINEL || + cd->startup_status & STARTUP_STATUS_FW_RESET_SENTINEL) { + strlcpy(err_str, + "- likely open or stuck high, soft reset OK", PT_ERR_STR_SIZE); + pt_debug(dev, DL_ERROR, + "%s: Hard reset failed, soft reset passed-%s\n", + __func__, err_str); + } else if (cd->startup_status == 0) { + strlcpy(err_str, "- likely stuck high.", PT_ERR_STR_SIZE); + pt_debug(dev, DL_ERROR, + "%s: Hard reset failed, soft reset failed-%s\n", + __func__, err_str); + } else { + strlcpy(err_str, "- open or stuck.", PT_ERR_STR_SIZE); + pt_debug(dev, DL_ERROR, + "%s: Hard and Soft reset failed - %s\n", + __func__, err_str); + } + } + +exit: + kfree(read_buf); + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_bist_slave_irq_test + * + * SUMMARY: Tests the connectivity of the Master/Slave IRQ net + * + * This test will ensure there is a good IRQ connection between the master + * DUT and the slave DUT(s). After power up the STATUS command is sent + * and the 'Slave Detect' bits are verified to ensure the master DUT + * saw each slave trigger the IRQ with it's reset sentinel. + * + * RETURN: + * 0 = Success + * !0 = Failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *slave_irq_toggled - pointer to where to store if slave IRQ toggled + * *slave_bus_toggled - pointer to where to store if slave Bus toggled + * *err_str - pointer to error string buffer + * *slave_detect - pointer to slave detect buffer + * *boot_err - pointer to boot_err buffer + ******************************************************************************/ +static int pt_bist_slave_irq_test(struct device *dev, + u8 *slave_irq_toggled, u8 *slave_bus_toggled, u8 *err_str, + u8 *slave_detect, u8 *boot_err) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + u8 mode = PT_MODE_UNKNOWN; + u8 status; + u8 boot; + u8 read_buf[12]; + u8 detected = 0; + u8 last_err = -1; + u16 actual_read_len; + int result = 0; + int rc = 0; + + /* + * Ensure DUT is in the BL where the STATUS cmd will report the slave + * detect bits. If the DUT was in FW, entering the BL will cause an + * XRES signal which will inadvertently test the XRES net as well + */ + rc = _pt_request_pip2_enter_bl(dev, &mode, &result); + if (rc) { + pt_debug(cd->dev, DL_ERROR, "%s: Error entering BL rc=%d\n", + __func__, rc); + strlcpy(err_str, "- State could not be determined.", PT_ERR_STR_SIZE); + goto exit; + } + + /* Use the STATUS command to retrieve the slave detect bit(s) */ + rc = _pt_request_pip2_send_cmd(cd->dev, PT_CORE_CMD_UNPROTECTED, + PIP2_CMD_ID_STATUS, NULL, 0, read_buf, + &actual_read_len); + if (!rc) { + pt_pr_buf(cd->dev, DL_INFO, read_buf, actual_read_len, + "PIP2 STATUS"); + status = read_buf[PIP2_RESP_STATUS_OFFSET]; + boot = read_buf[PIP2_RESP_BODY_OFFSET] & 0x01; + + /* Slave detect is only valid if status ok and in boot exec */ + if (status == PIP2_RSP_ERR_NONE && + boot == PIP2_STATUS_BOOT_EXEC) { + detected = read_buf[PIP2_RESP_BODY_OFFSET + 2] & + SLAVE_DETECT_MASK; + } else { + strlcpy(err_str, "- State could not be determined", PT_ERR_STR_SIZE); + rc = -EPERM; + } + } else { + pt_debug(cd->dev, DL_ERROR, "%s: STATUS cmd failure\n", + __func__); + strlcpy(err_str, "- State could not be determined.", PT_ERR_STR_SIZE); + goto exit; + } + + /* + * Retrieve boot error regardless of the state of the slave detect + * bit because the IRQ could have been stuck high or low. + */ + rc = _pt_request_pip2_send_cmd(cd->dev, PT_CORE_CMD_UNPROTECTED, + PIP2_CMD_ID_GET_LAST_ERRNO, NULL, 0, + read_buf, &actual_read_len); + if (!rc) { + pt_pr_buf(cd->dev, DL_INFO, read_buf, actual_read_len, + "PIP2 GET_LAST_ERRNO"); + status = read_buf[PIP2_RESP_STATUS_OFFSET]; + last_err = read_buf[PIP2_RESP_BODY_OFFSET]; + if (last_err) { + pt_debug(cd->dev, DL_ERROR, + "%s: Master Boot Last Err = 0x%02X\n", + __func__, last_err); + } + } else { + pt_debug(cd->dev, DL_ERROR, + "%s: GET_LAST_ERRNO cmd failure\n", __func__); + strlcpy(err_str, "- stuck, likely shorted to GND.", PT_ERR_STR_SIZE); + } + +exit: + pt_debug(cd->dev, DL_INFO, + "%s: rc=%d detected=0x%02X boot_err=0x%02X\n", + __func__, rc, detected, last_err); + if (err_str && last_err) { + if (detected) + scnprintf(err_str, PT_ERR_STR_SIZE, "%s 0x%02X", + "- Likely stuck low. Boot Error:", + last_err); + else + scnprintf(err_str, PT_ERR_STR_SIZE, "%s 0x%02X", + "- Likely stuck high. Boot Error:", + last_err); + } + + /* Ignore an invalid image error as BIST doesn't need valid FW */ + if (last_err == PIP2_RSP_ERR_INVALID_IMAGE) + last_err = PIP2_RSP_ERR_NONE; + + if (slave_irq_toggled) + *slave_irq_toggled = (detected && !last_err) ? true : false; + if (slave_bus_toggled) { + /* Leave as UNTEST if slave not detected */ + if (detected) + *slave_bus_toggled = !last_err ? true : false; + } + if (slave_detect) + *slave_detect = detected; + if (boot_err) + *boot_err = last_err; + + if (slave_irq_toggled && slave_bus_toggled) + pt_debug(cd->dev, DL_INFO, "%s: %s=0x%02X, %s=0x%02X, %s=0x%02X\n", + __func__, + "Detected", detected, + "slave_irq_toggled", *slave_irq_toggled, + "slave_bus_toggled", *slave_bus_toggled); + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_bist_slave_xres_test + * + * SUMMARY: Tests the connectivity of the Master/Slave TP_XRES net + * + * This test will ensure there is a good TP_XRES connection between the + * master DUT and the slave DUT(s). After toggling the XRES pin to the + * master, the STATUS command is sent and the 'Slave Detect' bits are + * verified to ensure the master DUT saw each slave trigger the IRQ with + * it's reset sentinel. + * + * RETURN: + * 0 = Success + * !0 = Failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *slave_irq_toggled - pointer to where to store if slave IRQ toggled + * *slave_bus_toggled - pointer to where to store if slave Bus toggled + * *slave_xres_toggled - pointer to where to store if slave XRES toggled + * *err_str - pointer to error string buffer + ******************************************************************************/ +static int pt_bist_slave_xres_test(struct device *dev, + u8 *slave_irq_toggled, u8 *slave_bus_toggled, u8 *slave_xres_toggled, + u8 *err_str) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + u8 slave_detect = 0; + u8 boot_err = 0; + int rc = 0; + + /* Force a reset to force the 'slave detect' bits to be re-acquired */ + mutex_lock(&cd->system_lock); + cd->hid_reset_cmd_state = 1; + mutex_unlock(&cd->system_lock); + pt_debug(dev, DL_INFO, "%s: Perform a hard reset\n", __func__); + pt_hw_hard_reset(cd); + msleep(100); + + rc = pt_bist_slave_irq_test(dev, slave_irq_toggled, + slave_bus_toggled, err_str, &slave_detect, &boot_err); + pt_debug(dev, DL_INFO, "%s: IRQ test rc = %d\n", __func__, rc); + + if (!rc && *slave_irq_toggled == false) { + /* + * If the slave IRQ did not toggle, either the slave_detect + * bit was not set or we had a boot error. If the slave + * detect was not set the slave did not reset causing a boot + * error. + */ + if (!slave_detect) + strlcpy(err_str, "- likely open.", PT_ERR_STR_SIZE); + else + scnprintf(err_str, PT_ERR_STR_SIZE, "%s 0x%02X", + "- likely open or an IRQ issue. Boot Error:", + boot_err); + } + if (slave_xres_toggled) { + if (!rc) + *slave_xres_toggled = *slave_irq_toggled ? true : false; + else + *slave_xres_toggled = false; + + } + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_bist_slave_bus_test + * + * SUMMARY: Tests the connectivity of the Master/Slave SPI bus net + * + * This test will ensure a good SPI bus connection between the + * master DUT and the slave DUT(s). This bus connection is ensured by + * opening file 0 (SRAM loader). If there is a slave and the open fails + * then there is a master/slave communication issue. Opening file 0 on + * the master will open it on the slave as well if the slave was detected. + * + * RETURN: + * 0 = Success + * !0 = Failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *slave_irq_toggled - pointer to where to store if slave IRQ toggled + * *slave_bus_toggled - pointer to where to store if slave Bus toggled + * *err_str - pointer to error string buffer + ******************************************************************************/ +static int pt_bist_slave_bus_test(struct device *dev, + u8 *slave_irq_toggled, u8 *slave_bus_toggled, u8 *err_str) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + u8 mode = PT_MODE_UNKNOWN; + u8 bus_toggled = false; + u8 file_handle; + int result = 0; + int rc = 0; + + rc = _pt_request_pip2_enter_bl(dev, &mode, &result); + if (rc) { + pt_debug(cd->dev, DL_ERROR, "%s: Error entering BL rc=%d\n", + __func__, rc); + strlcpy(err_str, "- State could not be determined.", PT_ERR_STR_SIZE); + goto exit; + } + + pt_debug(dev, DL_INFO, "%s Attempt open file 0\n", __func__); + file_handle = _pt_pip2_file_open(dev, PIP2_RAM_FILE); + if (file_handle != PIP2_RAM_FILE) { + rc = -ENOENT; + bus_toggled = false; + pt_debug(dev, DL_ERROR, + "%s Failed to open bin file\n", __func__); + strlcpy(err_str, "- Bus open, shorted or DUT in reset", PT_ERR_STR_SIZE); + goto exit; + } else { + bus_toggled = true; + if (file_handle != _pt_pip2_file_close(dev, file_handle)) { + pt_debug(dev, DL_ERROR, + "%s: File Close failed, file_handle=%d\n", + __func__, file_handle); + } + } + +exit: + /* If the master was able to send/recv a PIP msg, the IRQ must be ok */ + if (slave_irq_toggled) + *slave_irq_toggled = bus_toggled; + if (slave_bus_toggled) + *slave_bus_toggled = bus_toggled; + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_ttdl_bist_show + * + * SUMMARY: Show method for the ttdl_bist sysfs node. This built in self test + * will test that I2C/SPI, IRQ and TP_XRES pins are operational. + * + * NOTE: This function will reset the DUT and the startup_status bit + * mask. A pt_enum will be queued after completion. + * + * NOTE: The order of the net tests is done to optimize the time it takes + * to run. The first test is capable of verifying all nets, each subsequent + * test is only run if the previous was not able to see all nets toggle. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to output buffer + ******************************************************************************/ +static ssize_t pt_ttdl_bist_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + ssize_t ret; + char *bus_err_str = NULL; + char *irq_err_str = NULL; + char *xres_err_str = NULL; + char *slave_bus_err_str = NULL; + char *slave_irq_err_str = NULL; + char *slave_xres_err_str = NULL; + u8 tests; + int rc = 0; + int num_tests = 0; + int status = 1; /* 0 = Pass, !0 = fail */ + u8 bus_toggled = 0x0F; /* default to untested */ + u8 i2c_toggled = 0x0F; /* default to untested */ + u8 spi_toggled = 0x0F; /* default to untested */ + u8 irq_toggled = 0x0F; /* default to untested */ + u8 xres_toggled = 0x0F; /* default to untested */ + u8 slave_bus_toggled = 0x0F; /* default to untested */ + u8 slave_irq_toggled = 0x0F; /* default to untested */ + u8 slave_xres_toggled = 0x0F; /* default to untested */ + + bus_err_str = kzalloc(PT_ERR_STR_SIZE, GFP_KERNEL); + irq_err_str = kzalloc(PT_ERR_STR_SIZE, GFP_KERNEL); + xres_err_str = kzalloc(PT_ERR_STR_SIZE, GFP_KERNEL); + if (!bus_err_str || !irq_err_str || !xres_err_str) + goto print_results; + + memset(xres_err_str, 0, PT_ERR_STR_SIZE); + memset(irq_err_str, 0, PT_ERR_STR_SIZE); + memset(bus_err_str, 0, PT_ERR_STR_SIZE); + + if (cd->multi_chip) { + slave_bus_err_str = kzalloc(PT_ERR_STR_SIZE, GFP_KERNEL); + slave_irq_err_str = kzalloc(PT_ERR_STR_SIZE, GFP_KERNEL); + slave_xres_err_str = kzalloc(PT_ERR_STR_SIZE, GFP_KERNEL); + if (!slave_bus_err_str || + !slave_irq_err_str || + !slave_xres_err_str) + goto print_results; + memset(slave_bus_err_str, 0, PT_ERR_STR_SIZE); + memset(slave_irq_err_str, 0, PT_ERR_STR_SIZE); + memset(slave_xres_err_str, 0, PT_ERR_STR_SIZE); + } + + /* Turn off the TTDL WD during the test */ + pt_stop_wd_timer(cd); + + /* Shorten default PIP cmd timeout while running BIST */ + cd->pip_cmd_timeout = 200; + + /* Count the number of tests to run */ + tests = cd->ttdl_bist_select; + while (tests) { + num_tests += tests & 1; + tests >>= 1; + } + pt_debug(dev, DL_ERROR, "%s: BIST select = 0x%02X, run %d tests\n", + __func__, cd->ttdl_bist_select, num_tests); + + /* Suppress auto BL to avoid loader thread sending PIP during xres */ + if (cd->flashless_dut) { + pt_debug(dev, DL_INFO, "%s Flashless Auto_BL - SUPPRESS\n", + __func__); + mutex_lock(&cd->system_lock); + cd->flashless_auto_bl = PT_SUPPRESS_AUTO_BL; + mutex_unlock(&cd->system_lock); + } + + /* --------------- TP_XRES BIST TEST --------------- */ + if ((cd->ttdl_bist_select & PT_TTDL_BIST_TP_XRES_TEST) != 0) { + pt_debug(dev, DL_INFO, + "%s: ----- Start TP_XRES BIST -----", __func__); + rc = pt_bist_xres_test(dev, &bus_toggled, &irq_toggled, + &xres_toggled, xres_err_str); + /* Done if the rest of all nets toggled */ + if (bus_toggled == 1 && irq_toggled == 1 && xres_toggled == 1) + goto host_nets_complete; + } + + /* Flush bus in case a PIP response is waiting from previous test */ + pt_flush_bus(cd, PT_FLUSH_BUS_BASED_ON_LEN, NULL); + + /* --------------- IRQ BIST TEST --------------- */ + if ((cd->ttdl_bist_select & PT_TTDL_BIST_IRQ_TEST) != 0) { + pt_debug(dev, DL_INFO, + "%s: ----- Start IRQ BIST -----", __func__); + bus_toggled = 0xFF; + irq_toggled = 0xFF; + rc = pt_bist_irq_test(dev, &bus_toggled, &irq_toggled, + &xres_toggled, irq_err_str); + /* If this net failed clear results from previous net */ + if (irq_toggled != 1) { + xres_toggled = 0x0F; + memset(xres_err_str, 0, PT_ERR_STR_SIZE); + } + if (bus_toggled == 1 && irq_toggled == 1) + goto host_nets_complete; + } + + /* Flush bus in case a PIP response is waiting from previous test */ + pt_flush_bus(cd, PT_FLUSH_BUS_BASED_ON_LEN, NULL); + + /* --------------- BUS BIST TEST --------------- */ + if ((cd->ttdl_bist_select & PT_TTDL_BIST_BUS_TEST) != 0) { + pt_debug(dev, DL_INFO, + "%s: ----- Start BUS BIST -----", __func__); + bus_toggled = 0xFF; + rc = pt_bist_bus_test(dev, &bus_toggled, bus_err_str); + /* If this net failed clear results from previous net */ + if (bus_toggled == 0) { + irq_toggled = 0x0F; + memset(irq_err_str, 0, PT_ERR_STR_SIZE); + } + } + +host_nets_complete: + /* --------------- SLAVE XRES BIST TEST --------------- */ + if (cd->multi_chip && + (cd->ttdl_bist_select & PT_TTDL_BIST_SLAVE_XRES_TEST) != 0) { + pt_debug(dev, DL_INFO, + "%s: ----- Start Slave XRES BIST -----", __func__); + slave_xres_toggled = 0xFF; + rc = pt_bist_slave_xres_test(dev, &slave_irq_toggled, + &slave_bus_toggled, &slave_xres_toggled, + slave_xres_err_str); + if ((slave_bus_toggled == 1 && slave_irq_toggled == 1 && + slave_xres_toggled == 1) || slave_xres_toggled == 0) + goto print_results; + } + + /* --------------- SLAVE IRQ BIST TEST --------------- */ + if (cd->multi_chip && + (cd->ttdl_bist_select & PT_TTDL_BIST_SLAVE_IRQ_TEST) != 0) { + pt_debug(dev, DL_INFO, + "%s: ----- Start Slave IRQ BIST -----", __func__); + slave_irq_toggled = 0xFF; + rc = pt_bist_slave_irq_test(dev, &slave_irq_toggled, + &slave_bus_toggled, slave_irq_err_str, NULL, NULL); + pt_debug(dev, DL_INFO, "%s: slave_irq_toggled = 0x%02X\n", + __func__, slave_irq_toggled); + if (slave_irq_toggled == 1) { + slave_bus_toggled = 1; + goto print_results; + } + } + + /* --------------- SLAVE BUS BIST TEST --------------- */ + if (cd->multi_chip && + (cd->ttdl_bist_select & PT_TTDL_BIST_SLAVE_BUS_TEST) != 0) { + pt_debug(dev, DL_INFO, + "%s: ----- Start Slave BUS BIST -----", __func__); + slave_bus_toggled = 0xFF; + rc = pt_bist_slave_bus_test(dev, &slave_irq_toggled, + &slave_bus_toggled, slave_bus_err_str); + } + +print_results: + /* Restore PIP command timeout */ + cd->pip_cmd_timeout = cd->pip_cmd_timeout_default; + + /* + * We're done! - Perform a hard XRES toggle, allowing BL + * to load FW if there is any in Flash + */ + mutex_lock(&cd->system_lock); + cd->hid_reset_cmd_state = 0; + mutex_unlock(&cd->system_lock); + + pt_debug(dev, DL_INFO, + "%s: TTDL BIST Complete - Final reset\n", __func__); + + if (cd->flashless_dut) { + /* + * For Flashless solution, FW update is triggered after BL is + * seen that several miliseconds delay is needed. + */ + pt_debug(dev, DL_INFO, "%s Flashless Auto_BL - ALLOW\n", + __func__); + mutex_lock(&cd->system_lock); + cd->flashless_auto_bl = PT_ALLOW_AUTO_BL; + mutex_unlock(&cd->system_lock); + + /* Reset DUT and wait 100ms to see if loader started */ + pt_hw_hard_reset(cd); + msleep(100); + if (cd->fw_updating) { + pt_debug(dev, DL_INFO, + "%s: ----- BIST Wait FW Loading ----", + __func__); + rc = _pt_request_wait_for_enum_state( + dev, 4000, STARTUP_STATUS_COMPLETE); + } + } else { + if (cd->mode == PT_MODE_BOOTLOADER) { + rc = pt_pip2_launch_app(dev, PT_CORE_CMD_PROTECTED); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s Failed to launch app\n", __func__); + rc = pt_hw_hard_reset(cd); + } + } + + /* + * If FW exists the BL may have just started or will start soon, + * so the FW sentinel may be on it's way but with no FW it will + * not arrive, wait for it before deciding if we need to queue + * an enum. + */ + rc = _pt_request_wait_for_enum_state( + dev, 400, STARTUP_STATUS_FW_RESET_SENTINEL); + if ((cd->startup_status & STARTUP_STATUS_FW_RESET_SENTINEL) && + (cd->startup_status & STARTUP_STATUS_COMPLETE) == 0) { + pt_debug(dev, DL_INFO, "%s: ----- BIST Enum ----", + __func__); + pt_queue_enum(cd); + rc = _pt_request_wait_for_enum_state( + dev, 2000, STARTUP_STATUS_COMPLETE); + } + } + msleep(20); + + /* --------------- PRINT OUT BIST RESULTS ---------------*/ + pt_debug(dev, DL_INFO, "%s: ----- BIST Print Results ----", __func__); + pt_start_wd_timer(cd); + + /* Canned print if any memory allocation issues */ + if (!bus_err_str || !irq_err_str || !xres_err_str) { + ret = scnprintf(buf, strlen(buf), + "Status: %d\n" + "I2C (SDA,SCL): [UNTEST]\n" + "SPI (MISO,MOSI,CS,CLK): [UNTEST]\n" + "IRQ connection: [UNTEST]\n" + "TP_XRES connection: [UNTEST]\n", -ENOMEM); + if (cd->multi_chip) { + ret += scnprintf(buf + ret, strlen(buf), + "I/P SPI (MISO,MOSI,CS,CLK): [UNTEST]\n" + "I/P IRQ connection: [UNTEST]\n" + "I/P TP_XRES connection: [UNTEST]\n"); + } + } else { + status = 0; + if (bus_toggled == 1) + memset(bus_err_str, 0, PT_ERR_STR_SIZE); + if (irq_toggled == 1) + memset(irq_err_str, 0, PT_ERR_STR_SIZE); + if (xres_toggled == 1) + memset(xres_err_str, 0, PT_ERR_STR_SIZE); + + if (cd->ttdl_bist_select & PT_TTDL_BIST_BUS_TEST) + status += bus_toggled; + if (cd->ttdl_bist_select & PT_TTDL_BIST_IRQ_TEST) + status += irq_toggled; + if (cd->ttdl_bist_select & PT_TTDL_BIST_TP_XRES_TEST) + status += xres_toggled; + pt_debug(dev, DL_ERROR, "%s: status = %d (%d,%d,%d)\n", + __func__, status, bus_toggled, irq_toggled, + xres_toggled); + + if (cd->multi_chip) { + if (slave_irq_toggled == 1) + memset(slave_irq_err_str, 0, PT_ERR_STR_SIZE); + if (slave_xres_toggled == 1) + memset(slave_xres_err_str, 0, PT_ERR_STR_SIZE); + if (slave_bus_toggled == 1) + memset(slave_bus_err_str, 0, PT_ERR_STR_SIZE); + + if (cd->ttdl_bist_select & PT_TTDL_BIST_SLAVE_BUS_TEST) + status += slave_bus_toggled; + if (cd->ttdl_bist_select & PT_TTDL_BIST_SLAVE_IRQ_TEST) + status += slave_irq_toggled; + if (cd->ttdl_bist_select & PT_TTDL_BIST_SLAVE_XRES_TEST) + status += slave_xres_toggled; + pt_debug(dev, DL_ERROR, + "%s: status = %d (%d,%d,%d,%d,%d,%d)\n", + __func__, status, bus_toggled, irq_toggled, + xres_toggled, slave_bus_toggled, + slave_irq_toggled, slave_xres_toggled); + } + + if (cd->bus_ops->bustype == BUS_I2C) + i2c_toggled = bus_toggled; + else + spi_toggled = bus_toggled; + + ret = scnprintf(buf, strlen(buf), + "Status: %d\n" + "I2C (SDA,SCL): %s %s\n" + "SPI (MISO,MOSI,CS,CLK): %s %s\n" + "IRQ connection: %s %s\n" + "TP_XRES connection: %s %s\n", + status == num_tests ? 0 : 1, + i2c_toggled == 0x0F ? "[UNTEST]" : + i2c_toggled == 1 ? "[ OK ]" : "[FAILED]", + i2c_toggled == 0x0F ? "" : bus_err_str, + spi_toggled == 0x0F ? "[UNTEST]" : + spi_toggled == 1 ? "[ OK ]" : "[FAILED]", + spi_toggled == 0x0F ? "" : bus_err_str, + irq_toggled == 0x0F ? "[UNTEST]" : + irq_toggled == 1 ? "[ OK ]" : "[FAILED]", + irq_err_str, + xres_toggled == 0x0F ? "[UNTEST]" : + xres_toggled == 1 ? "[ OK ]" : "[FAILED]", + xres_err_str); + + if (cd->multi_chip) { + ret += scnprintf(buf + ret, strlen(buf), + "I/P SPI (MISO,MOSI,CS,CLK): %s %s\n" + "I/P IRQ connection: %s %s\n" + "I/P TP_XRES connection: %s %s\n", + slave_bus_toggled == 0x0F ? "[UNTEST]" : + slave_bus_toggled == 1 ? "[ OK ]" : + "[FAILED]", slave_bus_err_str, + slave_irq_toggled == 0x0F ? "[UNTEST]" : + slave_irq_toggled == 1 ? "[ OK ]" : + "[FAILED]", slave_irq_err_str, + slave_xres_toggled == 0x0F ? "[UNTEST]" : + slave_xres_toggled == 1 ? "[ OK ]" : + "[FAILED]", slave_xres_err_str); + } + } + + /* Put TTDL back into a known state, issue a ttdl enum if needed */ + pt_debug(dev, DL_INFO, "%s: Startup_status = 0x%04X\n", + __func__, cd->startup_status); + kfree(bus_err_str); + kfree(irq_err_str); + kfree(xres_err_str); + return ret; +} + +/******************************************************************************* + * FUNCTION: pt_ttdl_bist_store + * + * SUMMARY: Store method for the ttdl_bist sysfs node. + * + * RETURN: Size of passed in buffer if successful + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to command buffer + * size - size of buf + ******************************************************************************/ +static ssize_t pt_ttdl_bist_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + u32 input_data[2] = {0}; + int length; + int rc = 0; + + /* Maximum input of one value */ + length = _pt_ic_parse_input(dev, buf, size, input_data, + ARRAY_SIZE(input_data)); + if (length != 1) { + pt_debug(dev, DL_ERROR, "%s: Invalid number of arguments\n", + __func__); + rc = -EINVAL; + goto exit; + } else { + mutex_lock(&cd->system_lock); + cd->ttdl_bist_select = input_data[0]; + mutex_unlock(&cd->system_lock); + } + +exit: + if (rc) + return rc; + return size; +} +static DEVICE_ATTR(ttdl_bist, 0644, pt_ttdl_bist_show, + pt_ttdl_bist_store); + +/******************************************************************************* + * FUNCTION: pt_flush_bus_store + * + * SUMMARY: Store method for the flush_bus sysfs node. + * + * RETURN: Size of passed in buffer if successful + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to command buffer + * size - size of buf + ******************************************************************************/ +static ssize_t pt_flush_bus_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + u32 input_data[2] = {0}; + int length; + int rc = 0; + + /* Maximum input of one value */ + length = _pt_ic_parse_input(dev, buf, size, input_data, + ARRAY_SIZE(input_data)); + if (length != 1) { + pt_debug(dev, DL_ERROR, "%s: Invalid number of arguments\n", + __func__); + rc = -EINVAL; + goto exit; + } + + mutex_lock(&cd->system_lock); + if (input_data[0] == 0) + cd->flush_bus_type = PT_FLUSH_BUS_BASED_ON_LEN; + else if (input_data[0] == 1) + cd->flush_bus_type = PT_FLUSH_BUS_FULL_256_READ; + else + rc = -EINVAL; + mutex_unlock(&cd->system_lock); + +exit: + if (rc) + return rc; + return size; +} + +/******************************************************************************* + * FUNCTION: pt_flush_bus_show + * + * SUMMARY: Show method for the flush_bus sysfs node that flushes the active bus + * based on either the size of the first two bytes or a blind 256 bytes. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to output buffer + ******************************************************************************/ +static ssize_t pt_flush_bus_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t ret = 0; + ssize_t bytes = 0; + struct pt_core_data *cd = dev_get_drvdata(dev); + + mutex_lock(&cd->system_lock); + bytes = pt_flush_bus(cd, cd->flush_bus_type, NULL); + + ret = scnprintf(buf, strlen(buf), + "Status: 0\n" + "%s: %zd\n", + "Bytes flushed", bytes); + + mutex_unlock(&cd->system_lock); + return ret; +} +static DEVICE_ATTR(flush_bus, 0644, pt_flush_bus_show, + pt_flush_bus_store); + +/******************************************************************************* + * FUNCTION: pt_pip2_ping_test_store + * + * SUMMARY: Store method for the pip2_ping_test sysfs node. + * + * NOTE: The max PIP2 packet size is 255 (payload for PING 247) however + * someone may want to test sending invalid packet lengths so any values + * up to 255 are allowed. + * + * RETURN: Size of passed in buffer if successful + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to command buffer + * size - size of buf + ******************************************************************************/ +static ssize_t pt_pip2_ping_test_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + u32 input_data[2]; + int length; + int rc = 0; + + /* Maximum input of one value */ + length = _pt_ic_parse_input(dev, buf, size, input_data, + ARRAY_SIZE(input_data)); + if (length != 1) { + pt_debug(dev, DL_ERROR, "%s: Invalid number of arguments\n", + __func__); + rc = -EINVAL; + goto exit; + } + + mutex_lock(&cd->system_lock); + if (input_data[1] >= 0 && input_data[0] <= PT_MAX_PIP2_MSG_SIZE) + cd->ping_test_size = input_data[0]; + else + rc = -EINVAL; + mutex_unlock(&cd->system_lock); + +exit: + if (rc) + return rc; + return size; +} + +/******************************************************************************* + * FUNCTION: pt_ping_test_show + * + * SUMMARY: Show method for the ping_test_show sysfs node that sends the PIP2 + * PING command and ramps up the optional payload from 0 to + * ping_test_size. + * The max payload size is 247: + * (255 - 2 byte reg address - 4 byte header - 2 byte CRC) + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to output buffer + ******************************************************************************/ +static ssize_t pt_pip2_ping_test_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + ssize_t ret; + int last_packet_size; + int rc = 0; + + rc = pt_pip2_ping_test(dev, cd->ping_test_size, &last_packet_size); + + if (rc) { + ret = scnprintf(buf, strlen(buf), "Status: %d\n", rc); + return ret; + } + ret = scnprintf(buf, strlen(buf), + "Status: %d\n" + "PING payload test passed with packet sizes 0 - %d\n", + (last_packet_size == cd->ping_test_size ? 0 : 1), + last_packet_size); + + return ret; +} + +/******************************************************************************* + * FUNCTION: pt_t_refresh_store + * + * SUMMARY: Store method for the t-refresh sysfs node that will takes a passed + * in integer as the number of interrupts to count. A timer is started to + * calculate the total time it takes to see that number of interrupts. + * + * RETURN: Size of passed in buffer if successful + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to buffer that hold the command parameters + * size - size of buf + ******************************************************************************/ +static ssize_t pt_t_refresh_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + u32 input_data[2]; + int length; + int rc = 0; + + /* Maximum input of one value */ + length = _pt_ic_parse_input(dev, buf, size, input_data, + ARRAY_SIZE(input_data)); + if (length != 1) { + pt_debug(dev, DL_ERROR, "%s: Invalid number of arguments\n", + __func__); + rc = -EINVAL; + goto exit; + } + + mutex_lock(&cd->system_lock); + pt_debug(dev, DL_INFO, "%s: Input value: %d\n", __func__, + input_data[0]); + if (input_data[0] >= 0 && input_data[0] <= 1000) { + cd->t_refresh_total = input_data[0]; + cd->t_refresh_count = 0; + cd->t_refresh_active = 1; + } else { + pt_debug(dev, DL_ERROR, "%s: Invalid value\n", __func__); + rc = -EINVAL; + } + mutex_unlock(&cd->system_lock); + +exit: + pt_debug(dev, DL_ERROR, "%s: rc = %d\n", __func__, rc); + if (rc) + return rc; + return size; +} + +/******************************************************************************* + * FUNCTION: pt_t_refresh_show + * + * SUMMARY: Show method for the t-refresh sysfs node that will show the results + * of the T-Refresh timer counting the time it takes to see a user defined + * number of interrupts. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to output buffer + ******************************************************************************/ +static ssize_t pt_t_refresh_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t ret = 0; + struct pt_core_data *cd = dev_get_drvdata(dev); + u32 whole; + u16 fraction; + + mutex_lock(&cd->system_lock); + + /* Check if we have counted the number requested */ + if (cd->t_refresh_count != cd->t_refresh_total) { + ret = scnprintf(buf, strlen(buf), + "Status: 0\n" + "%s: %d\n", + "Still counting... current IRQ count", + cd->t_refresh_count); + } else { + /* Ensure T-Refresh is de-activated */ + cd->t_refresh_active = 0; + + whole = cd->t_refresh_time / cd->t_refresh_count; + fraction = cd->t_refresh_time % cd->t_refresh_count; + fraction = fraction * 1000 / cd->t_refresh_count; + + ret = scnprintf(buf, strlen(buf), + "Status: 0\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d.%02d\n", + "Requested IRQ Count ", cd->t_refresh_total, + "IRQ Counted ", cd->t_refresh_count, + "Total Time Elapsed (ms) ", (int)cd->t_refresh_time, + "Average T-Refresh (ms) ", whole, fraction); + } + mutex_unlock(&cd->system_lock); + return ret; +} + +/******************************************************************************* + * FUNCTION: pt_dut_status_show + * + * SUMMARY: Show method for DUT status sysfs node. Display DUT's scan state, and + * more items such as operation mode,easywake state are added in the future. + * + * RETURN: Char buffer with printed scan status information + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to output buffer + ******************************************************************************/ +static ssize_t pt_dut_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + u8 sys_mode = FW_SYS_MODE_UNDEFINED; + u8 mode = PT_MODE_UNKNOWN; + char *outputstring[7] = {"BOOT", "SCANNING", "DEEP_SLEEP", + "TEST", "DEEP_STANDBY", "UNDEFINED", "n/a"}; + struct pt_core_data *cd = dev_get_drvdata(dev); + ssize_t ret; + u16 calculated_crc = 0; + u16 stored_crc = 0; + u8 status; + int rc = 0; + + /* In STANDBY the DUT will not repond to any PIP cmd */ + if (cd->fw_sys_mode_in_standby_state) { + mode = PT_MODE_OPERATIONAL; + sys_mode = FW_SYS_MODE_DEEP_STANDBY; + goto print_limited_results; + } + + /* Retrieve mode and FW system mode which can only be 0-4 */ + rc = pt_get_fw_sys_mode(cd, &sys_mode, &mode); + if (rc || mode == PT_MODE_UNKNOWN) { + ret = scnprintf(buf, PT_MAX_PRBUF_SIZE, + "%s: %d\n" + "%s: n/a\n" + "%s: n/a\n" + "%s: n/a\n" + "%s: n/a\n", + "Status", rc, + "Active Exec ", + "FW System Mode ", + "Stored CRC ", + "Calculated CRC "); + return ret; + } else { + if (mode == PT_MODE_OPERATIONAL) { + if (sys_mode > FW_SYS_MODE_MAX) + sys_mode = FW_SYS_MODE_UNDEFINED; + if (sys_mode != FW_SYS_MODE_TEST) + goto print_limited_results; + + rc = pt_pip_verify_config_block_crc_(cd, + PT_TCH_PARM_EBID, &status, + &calculated_crc, &stored_crc); + if (rc) + goto print_limited_results; + + ret = scnprintf(buf, PT_MAX_PRBUF_SIZE, + "%s: %d\n" + "%s: %s\n" + "%s: %s\n" + "%s: 0x%04X\n" + "%s: 0x%04X\n", + "Status", rc, + "Active Exec ", "FW", + "FW System Mode ", outputstring[sys_mode], + "Stored CRC ", stored_crc, + "Calculated CRC ", calculated_crc); + return ret; + } else { + /* When in BL or unknon mode Active Exec is "n/a" */ + sys_mode = FW_SYS_MODE_UNDEFINED + 1; + } + } + +print_limited_results: + ret = scnprintf(buf, PT_MAX_PRBUF_SIZE, + "%s: %d\n" + "%s: %s\n" + "%s: %s\n" + "%s: n/a\n" + "%s: n/a\n", + "Status", rc, + "Active Exec ", + mode == PT_MODE_OPERATIONAL ? "FW" : "BL", + "FW System Mode ", outputstring[sys_mode], + "Stored CRC ", + "Calculated CRC "); + + return ret; +} +#endif /* TTDL_DIAGNOSTICS */ + + +/******************************************************************************* + * Structures of sysfs attributes for all DUT dependent sysfs node + ******************************************************************************/ +static struct attribute *early_attrs[] = { + &dev_attr_hw_version.attr, + &dev_attr_drv_version.attr, + &dev_attr_drv_ver.attr, + &dev_attr_fw_version.attr, + &dev_attr_sysinfo.attr, + &dev_attr_pip2_cmd_rsp.attr, + &dev_attr_command.attr, + &dev_attr_drv_debug.attr, + &dev_attr_hw_reset.attr, + &dev_attr_response.attr, + &dev_attr_ttdl_restart.attr, +#ifdef TTDL_DIAGNOSTICS + &dev_attr_ttdl_status.attr, + &dev_attr_pip2_enter_bl.attr, + &dev_attr_pip2_exit_bl.attr, + &dev_attr_err_gpio.attr, + &dev_attr_flush_bus.attr, + &dev_attr_ttdl_bist.attr, +#endif /* TTDL_DIAGNOSTICS */ + NULL, +}; + +static struct attribute_group early_attr_group = { + .attrs = early_attrs, +}; + +static struct device_attribute pip2_attributes[] = { + __ATTR(pip2_version, 0444, pt_pip2_version_show, NULL), + __ATTR(pip2_gpio_read, 0444, pt_pip2_gpio_read_show, NULL), +#ifdef TTDL_DIAGNOSTICS + __ATTR(pip2_bin_hdr, 0444, pt_pip2_bin_hdr_show, NULL), + __ATTR(pip2_ping_test, 0644, pt_pip2_ping_test_show, + pt_pip2_ping_test_store), +#endif +}; + +static struct device_attribute attributes[] = { + __ATTR(dut_debug, 0644, + pt_dut_debug_show, pt_drv_debug_store), + __ATTR(sleep_status, 0444, pt_sleep_status_show, NULL), + __ATTR(panel_id, 0444, pt_panel_id_show, NULL), + __ATTR(get_param, 0644, + pt_get_param_show, pt_get_param_store), + __ATTR(pt_touch_offload, 0644, + pt_touch_offload_show, pt_touch_offload_store), +#ifdef EASYWAKE_TSG6 + __ATTR(easy_wakeup_gesture, 0644, pt_easy_wakeup_gesture_show, + pt_easy_wakeup_gesture_store), + __ATTR(easy_wakeup_gesture_id, 0444, + pt_easy_wakeup_gesture_id_show, NULL), + __ATTR(easy_wakeup_gesture_data, 0444, + pt_easy_wakeup_gesture_data_show, NULL), +#endif +#ifdef TTDL_DIAGNOSTICS + __ATTR(platform_data, 0444, pt_platform_data_show, NULL), + __ATTR(drv_irq, 0644, pt_drv_irq_show, pt_drv_irq_store), + __ATTR(dut_status, 0444, pt_dut_status_show, NULL), + __ATTR(t_refresh, 0644, pt_t_refresh_show, pt_t_refresh_store), +#endif /* TTDL_DIAGNOSTICS */ +}; + +/******************************************************************************* + * FUNCTION: add_sysfs_interfaces + * + * SUMMARY: Creates all DUT dependent sysfs nodes owned by the core + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int add_sysfs_interfaces(struct device *dev) +{ + int i; + int j = 0; + struct pt_core_data *cd = dev_get_drvdata(dev); + + for (i = 0; i < ARRAY_SIZE(attributes); i++) { + if (device_create_file(dev, attributes + i)) + goto undo; + } + + pt_debug(dev, DL_INFO, "%s: Active DUT Generation: %d", + __func__, cd->active_dut_generation); + if (cd->active_dut_generation == DUT_PIP2_CAPABLE) { + for (j = 0; j < ARRAY_SIZE(pip2_attributes); j++) { + if (device_create_file(dev, pip2_attributes + j)) + goto undo; + } + } + return 0; +undo: + for (i--; i >= 0; i--) + device_remove_file(dev, attributes + i); + for (j--; j >= 0; j--) + device_remove_file(dev, pip2_attributes + j); + pt_debug(dev, DL_ERROR, "%s: failed to create sysfs interface\n", + __func__); + return -ENODEV; +} + +/******************************************************************************* + * FUNCTION: remove_sysfs_interfaces + * + * SUMMARY: Removes all DUT dependent sysfs nodes owned by the core + * + * RETURN: void + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static void remove_sysfs_interfaces(struct device *dev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(attributes); i++) + device_remove_file(dev, attributes + i); + for (i = 0; i < ARRAY_SIZE(pip2_attributes); i++) + device_remove_file(dev, pip2_attributes + i); +} + +/******************************************************************************* + * FUNCTION: remove_sysfs_and_modules + * + * SUMMARY: Removes all DUT dependent sysfs nodes and modules + * + * RETURN: void + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static void remove_sysfs_and_modules(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + /* Queued work should be removed before to release loader module */ + call_atten_cb(cd, PT_ATTEN_CANCEL_LOADER, 0); + pt_release_modules(cd); + pt_btn_release(dev); + pt_mt_release(dev); + remove_sysfs_interfaces(dev); +} + +static int pt_ts_pinctrl_init(struct pt_core_data *cd) +{ + int retval; + + /* Get pinctrl if target uses pinctrl */ + cd->ts_pinctrl = devm_pinctrl_get(cd->dev); + if (IS_ERR_OR_NULL(cd->ts_pinctrl)) { + retval = PTR_ERR(cd->ts_pinctrl); + dev_dbg(cd->dev, + "Target does not use pinctrl %d\n", retval); + goto err_pinctrl_get; + } + + cd->pinctrl_state_active + = pinctrl_lookup_state(cd->ts_pinctrl, + PINCTRL_STATE_ACTIVE); + if (IS_ERR_OR_NULL(cd->pinctrl_state_active)) { + retval = PTR_ERR(cd->pinctrl_state_active); + dev_err(cd->dev, + "Can not lookup %s pinstate %d\n", + PINCTRL_STATE_ACTIVE, retval); + goto err_pinctrl_lookup; + } + + cd->pinctrl_state_suspend + = pinctrl_lookup_state(cd->ts_pinctrl, + PINCTRL_STATE_SUSPEND); + if (IS_ERR_OR_NULL(cd->pinctrl_state_suspend)) { + retval = PTR_ERR(cd->pinctrl_state_suspend); + dev_err(cd->dev, + "Can not lookup %s pinstate %d\n", + PINCTRL_STATE_SUSPEND, retval); + goto err_pinctrl_lookup; + } + + cd->pinctrl_state_release + = pinctrl_lookup_state(cd->ts_pinctrl, + PINCTRL_STATE_RELEASE); + if (IS_ERR_OR_NULL(cd->pinctrl_state_release)) { + retval = PTR_ERR(cd->pinctrl_state_release); + dev_dbg(cd->dev, + "Can not lookup %s pinstate %d\n", + PINCTRL_STATE_RELEASE, retval); + } + + return 0; + +err_pinctrl_lookup: + devm_pinctrl_put(cd->ts_pinctrl); +err_pinctrl_get: + cd->ts_pinctrl = NULL; + return retval; +} + +void touch_notify_glink_pt_channel_state(bool state) +{ + pr_info("%s:[touch] touch_notify_glink\n", __func__); +} + +void glink_touch_pt_rx_msg(void *data, int len) +{ + int rc = 0; + pr_info("%s: TOUCH_RX_MSG Start:\n", __func__); + + if (len > TOUCH_GLINK_INTENT_SIZE) { + pr_err("Invalid TOUCH glink intent size\n"); + return; + } + /* check SLATE response */ + pt_slate_resp_ack = *(uint32_t *)&data[8]; + if (pt_slate_resp_ack == 0x01) { + pr_err("Bad SLATE response\n"); + rc = -EINVAL; + goto err_ret; + } + pr_info("%s: TOUCH_RX_MSG End:\n", __func__); +err_ret: +return; +} + +/******************************************************************************* + ******************************************************************************* + * FUNCTION: pt_probe + * + * SUMMARY: Probe of the core module. + * + * NOTE: For the Parade Technologies development platform (PtSBC) the + * probe functionality is split into two functions; pt_probe() and + * pt_probe_complete(). the initial setup is done in this function which + * then creates a WORK task which runs after the probe timer expires. This + * ensures the I2C/SPI is up on the PtSBC in time for TTDL. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *ops - pointer to the bus + * *dev - pointer to the device structure + * irq - IRQ + * xfer_buf_size - size of the buffer + ******************************************************************************/ +int pt_probe(const struct pt_bus_ops *ops, struct device *dev, + u16 irq, size_t xfer_buf_size) +{ + struct pt_core_data *cd; + struct pt_platform_data *pdata = dev_get_platdata(dev); + enum pt_atten_type type; + struct i2c_client *client = to_i2c_client(dev); + int rc = 0; + u8 pip_ver_major; + u8 pip_ver_minor; + u32 status = STARTUP_STATUS_START; + + if (!pdata || !pdata->core_pdata || !pdata->mt_pdata) { + pt_debug(dev, DL_ERROR, "%s: Missing platform data\n", + __func__); + rc = -ENODEV; + goto error_no_pdata; + } + + if (pdata->core_pdata->flags & PT_CORE_FLAG_POWEROFF_ON_SLEEP) { + if (!pdata->core_pdata->power) { + pt_debug(dev, DL_ERROR, + "%s: Missing platform data function\n", + __func__); + rc = -ENODEV; + goto error_no_pdata; + } + } + /* get context and debug print buffers */ + cd = kzalloc(sizeof(*cd), GFP_KERNEL); + if (!cd) { + rc = -ENOMEM; + goto error_no_pdata; + } + /* Initialize device info */ + cd->dev = dev; + cd->pdata = pdata; + cd->cpdata = pdata->core_pdata; + cd->bus_ops = ops; + cd->debug_level = PT_INITIAL_DEBUG_LEVEL; + cd->show_timestamp = PT_INITIAL_SHOW_TIME_STAMP; + scnprintf(cd->core_id, 20, "%s%d", PT_CORE_NAME, core_number++); + cd->hw_detected = false; + cd->pip2_prot_active = false; + cd->pip2_send_user_cmd = false; + cd->bl_pip_ver_ready = false; + cd->app_pip_ver_ready = false; + cd->pip2_cmd_tag_seq = 0x08; /* PIP2 TAG=1 and 3 bit SEQ=0 */ + cd->get_param_id = 0; + cd->watchdog_enabled = 0; + cd->startup_retry_count = 0; + cd->core_probe_complete = 0; + cd->fw_system_mode = FW_SYS_MODE_BOOT; + cd->pip_cmd_timeout = PT_PIP_CMD_DEFAULT_TIMEOUT; + cd->pip_cmd_timeout_default = PT_PIP_CMD_DEFAULT_TIMEOUT; + cd->flashless_dut = 0; + cd->flashless_auto_bl = PT_SUPPRESS_AUTO_BL; + cd->bl_with_no_int = 0; + cd->cal_cache_in_host = PT_FEATURE_DISABLE; + cd->multi_chip = PT_FEATURE_DISABLE; + cd->tthe_hid_usb_format = PT_FEATURE_DISABLE; + cd->sleep_state = SS_SLEEP_NONE; + cd->quick_boot = false; + cd->drv_debug_suspend = false; + cd->touch_offload = false; + + if (cd->cpdata->config_dut_generation == CONFIG_DUT_PIP2_CAPABLE) { + cd->set_dut_generation = true; + cd->active_dut_generation = DUT_PIP2_CAPABLE; + } else if (cd->cpdata->config_dut_generation == CONFIG_DUT_PIP1_ONLY) { + cd->set_dut_generation = true; + cd->active_dut_generation = DUT_PIP1_ONLY; + } else { + cd->set_dut_generation = false; + cd->active_dut_generation = DUT_UNKNOWN; + } + /* Initialize with platform data */ + cd->watchdog_force_stop = cd->cpdata->watchdog_force_stop; + cd->watchdog_interval = PT_WATCHDOG_TIMEOUT; + cd->hid_cmd_state = 1; + cd->fw_updating = false; + cd->multi_chip = 0; + +#ifdef TTDL_DIAGNOSTICS + cd->t_refresh_active = 0; + cd->t_refresh_count = 0; + cd->pip2_crc_error_count = 0; + cd->wd_xres_count = 0; + cd->bl_retry_packet_count = 0; + cd->file_erase_timeout_count = 0; + cd->show_tt_data = false; + cd->flush_bus_type = PT_FLUSH_BUS_BASED_ON_LEN; + cd->err_gpio = 0; + cd->err_gpio_type = PT_ERR_GPIO_NONE; + cd->ttdl_bist_select = 0x07; + cd->force_pip2_seq = 0; +#endif /* TTDL_DIAGNOSTICS */ + + memset(cd->pip2_us_file_path, 0, PT_MAX_PATH_SIZE); + memcpy(cd->pip2_us_file_path, PT_PIP2_BIN_FILE_PATH, + sizeof(PT_PIP2_BIN_FILE_PATH)); + pt_init_hid_descriptor(&cd->hid_desc); + /* Read and store the descriptor lengths */ + cd->hid_core.hid_report_desc_len = + le16_to_cpu(cd->hid_desc.report_desc_len); + cd->hid_core.hid_max_input_len = + le16_to_cpu(cd->hid_desc.max_input_len); + cd->hid_core.hid_max_output_len = + le16_to_cpu(cd->hid_desc.max_output_len); + /* Initialize mutexes and spinlocks */ + mutex_init(&cd->module_list_lock); + mutex_init(&cd->system_lock); + mutex_init(&cd->sysfs_lock); + mutex_init(&cd->ttdl_restart_lock); + mutex_init(&cd->firmware_class_lock); + spin_lock_init(&cd->spinlock); + + /* Initialize module list */ + INIT_LIST_HEAD(&cd->module_list); + + /* Initialize attention lists */ + for (type = 0; type < PT_ATTEN_NUM_ATTEN; type++) + INIT_LIST_HEAD(&cd->atten_list[type]); + + /* Initialize parameter list */ + INIT_LIST_HEAD(&cd->param_list); + + /* Initialize wait queue */ + init_waitqueue_head(&cd->wait_q); + rc = pt_ts_pinctrl_init(cd); + if (!rc && cd->ts_pinctrl) { + /* + * Pinctrl handle is optional. If pinctrl handle is found + * let pins to be configured in active state. If not + * found continue further without error. + */ + rc = pinctrl_select_state(cd->ts_pinctrl, + cd->pinctrl_state_active); + if (rc < 0) + dev_err(&client->dev, "failed to select pin to active state\n"); + } + rc = pt_get_regulator(cd, true); + if (rc) { + dev_err(&client->dev, "Failed to get voltage regulators\n"); + goto error_alloc_data; + } + + rc = pt_enable_regulator(cd, true); + if (rc) { + dev_err(dev, "Failed to enable regulators: rc=%d\n", rc); + goto error_get_regulator; + } + + /* Initialize works */ + INIT_WORK(&cd->enum_work, pt_enum_work_function); + INIT_WORK(&cd->ttdl_restart_work, pt_restart_work_function); + INIT_WORK(&cd->watchdog_work, pt_watchdog_work); + + /* Initialize HID specific data */ + cd->hid_core.hid_vendor_id = (cd->cpdata->vendor_id) ? + cd->cpdata->vendor_id : HID_VENDOR_ID; + cd->hid_core.hid_product_id = (cd->cpdata->product_id) ? + cd->cpdata->product_id : HID_APP_PRODUCT_ID; + cd->hid_core.hid_desc_register = + cpu_to_le16(cd->cpdata->hid_desc_register); + + /* Set platform easywake value */ + cd->easy_wakeup_gesture = cd->cpdata->easy_wakeup_gesture; + +#if defined(CONFIG_DRM) || defined(CONFIG_PANEL_NOTIFIER) + /* Setup active dsi panel */ + active_panel = cd->cpdata->active_panel; +#endif + + /* Set platform panel_id value */ + cd->panel_id_support = cd->cpdata->panel_id_support; + + if (cd->panel_id_support & PT_PANEL_ID_BY_SYS_INFO) + /* Set Panel ID to default to 0 */ + cd->pid_for_loader = PT_PANEL_ID_DEFAULT; + else + /* Set Panel ID to Not Enabled */ + cd->pid_for_loader = PANEL_ID_NOT_ENABLED; + /* Initialize hw_version default to FFFF.FFFF.FF */ + snprintf(cd->hw_version, HW_VERSION_LEN_MAX, "FFFF.FFFF.FF"); + + dev_set_drvdata(dev, cd); + /* PtSBC builds will call this function in pt_probe_complete() */ + pt_add_core(dev); + + rc = sysfs_create_group(&dev->kobj, &early_attr_group); + if (rc) { + pt_debug(cd->dev, DL_ERROR, "%s:create early attrs failed\n", + __func__); + goto error_enable_regulator; + } + /* + * Save the pointer to a global value, which will be used + * in ttdl_restart function + */ + cd->bus_ops = ops; + + glink_touch_channel_init(&touch_notify_glink_pt_channel_state, &glink_touch_pt_rx_msg); + + /* + * When the IRQ GPIO is not direclty accessible and no function is + * defined to get the IRQ status, the IRQ passed in must be assigned + * directly as the gpio_to_irq will not work. e.g. CHROMEOS + */ + if (!cd->cpdata->irq_stat) { + cd->irq = irq; + pt_debug(cd->dev, DL_ERROR, "%s:No irq_stat, Set cd->irq = %d\n", + __func__, cd->irq); + } + /* Call platform init function before setting up the GPIO's */ + if (cd->cpdata->init) { + pt_debug(cd->dev, DL_INFO, "%s: Init HW\n", __func__); + rc = cd->cpdata->init(cd->cpdata, PT_MT_POWER_ON, cd->dev); + } else { + pt_debug(cd->dev, DL_ERROR, "%s: No HW INIT function\n", + __func__); + rc = 0; + } + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, "%s: HW Init fail r=%d\n", + __func__, rc); + } + /* Power on any needed regulator(s) */ + if (cd->cpdata->setup_power) { + pt_debug(cd->dev, DL_INFO, "%s: Device power on!\n", __func__); + rc = cd->cpdata->setup_power(cd->cpdata, + PT_MT_POWER_ON, cd->dev); + } else { + pt_debug(cd->dev, DL_ERROR, "%s: No setup power function\n", + __func__); + rc = 0; + } + if (rc < 0) + pt_debug(cd->dev, DL_ERROR, "%s: Setup power on fail r=%d\n", + __func__, rc); + +#ifdef TTDL_DIAGNOSTICS + cd->watchdog_irq_stuck_count = 0; + cd->bus_transmit_error_count = 0; +#endif /* TTDL_DIAGNOSTICS */ + if (cd->cpdata->detect) { + pt_debug(cd->dev, DL_INFO, "%s: Detect HW\n", __func__); + rc = cd->cpdata->detect(cd->cpdata, cd->dev, + pt_platform_detect_read); + if (!rc) { + cd->hw_detected = true; + pt_debug(cd->dev, DL_INFO, + "%s: HW detected\n", __func__); + } else { + cd->hw_detected = false; + pt_debug(cd->dev, DL_INFO, + "%s: No HW detected\n", __func__); + rc = -ENODEV; + goto error_detect; + } + } else { + pt_debug(dev, DL_ERROR, + "%s: PARADE No HW detect function pointer\n", + __func__); + /* + * "hw_reset" is not needed in the "if" statement, + * because "hw_reset" is already included in "hw_detect" + * function. + */ + rc = pt_hw_hard_reset(cd); + if (rc) + pt_debug(cd->dev, DL_ERROR, + "%s: FAILED to execute HARD reset\n", + __func__); + } + + if (cd->cpdata->setup_irq) { + pt_debug(cd->dev, DL_INFO, "%s: setup IRQ\n", __func__); + rc = cd->cpdata->setup_irq(cd->cpdata, PT_MT_IRQ_REG, cd->dev); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Error, couldn't setup IRQ\n", __func__); + goto error_setup_irq; + } + } else { + pt_debug(dev, DL_ERROR, + "%s: IRQ function pointer not setup\n", + __func__); + goto error_setup_irq; + } + +#if (KERNEL_VERSION(4, 14, 0) > LINUX_VERSION_CODE) + setup_timer(&cd->watchdog_timer, pt_watchdog_timer, + (unsigned long)cd); +#else + timer_setup(&cd->watchdog_timer, pt_watchdog_timer, 0); +#endif + pt_stop_wd_timer(cd); +#ifdef TTHE_TUNER_SUPPORT + mutex_init(&cd->tthe_lock); + cd->tthe_debugfs = debugfs_create_file(PT_TTHE_TUNER_FILE_NAME, + 0644, NULL, cd, &tthe_debugfs_fops); +#endif + rc = device_init_wakeup(dev, 1); + if (rc < 0) + pt_debug(dev, DL_ERROR, "%s: Error, device_init_wakeup rc:%d\n", + __func__, rc); + + if (!enable_irq_wake(cd->irq)) { + cd->irq_wake = 1; + pt_debug(cd->dev, DL_WARN, "%s Device MAY wakeup\n", __func__); + } + pm_runtime_get_noresume(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + /* If IRQ asserted, read out all from buffer to release INT pin */ + if (cd->cpdata->irq_stat) { + pt_flush_bus_if_irq_asserted(cd, PT_FLUSH_BUS_BASED_ON_LEN); + } else { + /* Force a read in case the reset sentinel already arrived */ + rc = pt_read_input(cd); + if (!rc) + pt_parse_input(cd); + } + /* Without sleep DUT is not ready and will NAK the first write */ + msleep(150); + /* Attempt to set the DUT generation if not yet set */ + if (cd->active_dut_generation == DUT_UNKNOWN) { + if (cd->bl_pip_ver_ready || + (cd->app_pip_ver_ready && + IS_PIP_VER_GE(&cd->sysinfo, 1, 12))) { + cd->active_dut_generation = DUT_PIP2_CAPABLE; + pt_debug(dev, DL_INFO, "%s: dut generation is %d\n", + __func__, cd->active_dut_generation); + } else { + rc = _pt_detect_dut_generation(cd->dev, + &status, &cd->active_dut_generation, + &cd->mode); + if ((cd->active_dut_generation == DUT_UNKNOWN) + || rc) { + pt_debug(cd->dev, DL_ERROR, + " === DUT Gen unknown, Skip Enum ===\n"); + goto skip_enum; + } + } + } + _pt_request_active_pip_protocol(cd->dev, PT_CORE_CMD_PROTECTED, + &pip_ver_major, &pip_ver_minor); + if (pip_ver_major == 2) { + cd->bl_pip_ver_ready = true; + pt_debug(dev, DL_ERROR, + " === PIP2.%d Detected, Attempt to launch APP ===\n", + pip_ver_minor); + cd->hw_detected = true; + } else if (pip_ver_major == 1) { + cd->app_pip_ver_ready = true; + pt_debug(dev, DL_ERROR, + " === PIP1.%d Detected ===\n", pip_ver_minor); + cd->hw_detected = true; + } else { + cd->sysinfo.ttdata.pip_ver_major = 0; + cd->sysinfo.ttdata.pip_ver_minor = 0; + cd->app_pip_ver_ready = false; + cd->hw_detected = false; + pt_debug(dev, DL_ERROR, + " === PIP Version Not Detected, Skip Enum ===\n"); + /* For legacy DUTS proceed, enum will attempt to launch app */ + if (cd->active_dut_generation != DUT_PIP1_ONLY) + goto skip_enum; + } + rc = pt_enum_with_dut(cd, false, &status); + pt_debug(dev, DL_ERROR, "%s: cd->startup_status=0x%04X status=0x%04X\n", + __func__, cd->startup_status, status); + if (rc == -ENODEV) { + pt_debug(cd->dev, DL_ERROR, + "%s: Enumeration Failed r=%d\n", __func__, rc); + /* For PtSBC don't error out, allow TTDL to stay up */ + rc = -EPROBE_DEFER; + goto error_after_startup; + } + /* Suspend scanning until probe is complete to avoid asyc touches */ + pt_pip_suspend_scanning_(cd); + + if (cd->hw_detected) { + pt_debug(dev, DL_INFO, "%s: Add sysfs interfaces\n", + __func__); + rc = add_sysfs_interfaces(dev); + if (rc < 0) { + pt_debug(dev, DL_ERROR, + "%s: Error, fail sysfs init\n", __func__); + goto error_after_startup; + } + } else { + pt_debug(dev, DL_ERROR, + "%s: No HW detected, sysfs interfaces not added\n", + __func__); + } +skip_enum: + pm_runtime_put_sync(dev); + + pt_debug(dev, DL_INFO, "%s: Probe: MT, BTN\n", __func__); + rc = pt_mt_probe(dev); + if (rc < 0) { + pt_debug(dev, DL_ERROR, "%s: Error, fail mt probe\n", + __func__); + goto error_after_sysfs_create; + } + rc = pt_btn_probe(dev); + if (rc < 0) { + pt_debug(dev, DL_ERROR, "%s: Error, fail btn probe\n", + __func__); + goto error_after_startup_mt; + } + pt_probe_modules(cd); + +#ifdef CONFIG_HAS_EARLYSUSPEND + pt_setup_early_suspend(cd); +#elif defined(CONFIG_PANEL_NOTIFIER) + pt_debug(dev, DL_ERROR, "%s: Probe: Setup Panel Event notifier\n", __func__); + pt_setup_panel_event_notifier(cd); + INIT_WORK(&cd->resume_offload_work, pt_resume_offload_work); + INIT_WORK(&cd->suspend_offload_work, pt_suspend_offload_work); +#elif defined(CONFIG_DRM) + pt_debug(dev, DL_ERROR, "%s: Probe: Setup drm notifier\n", __func__); + pt_setup_drm_notifier(cd); + INIT_WORK(&cd->resume_offload_work, pt_resume_offload_work); + INIT_WORK(&cd->suspend_offload_work, pt_suspend_offload_work); +#elif defined(CONFIG_FB) + pt_setup_fb_notifier(cd); +#endif + +#ifdef NEED_SUSPEND_NOTIFIER + cd->pm_notifier.notifier_call = pt_pm_notifier; + register_pm_notifier(&cd->pm_notifier); +#endif + pt_pip_resume_scanning_(cd); + mutex_lock(&cd->system_lock); + cd->startup_status |= status; + cd->core_probe_complete = 1; + mutex_unlock(&cd->system_lock); + + pt_core_state = STATE_RESUME; + pt_debug(dev, DL_ERROR, "%s: TTDL Core Probe Completed Successfully\n", + __func__); + return 0; + + +error_after_startup_mt: + pr_err("%s PARADE error_after_startup_mt\n", __func__); + pt_mt_release(dev); +error_after_sysfs_create: + pr_err("%s PARADE error_after_sysfs_create\n", __func__); + pm_runtime_disable(dev); +#if (KERNEL_VERSION(3, 16, 0) > LINUX_VERSION_CODE) + device_wakeup_disable(dev); +#endif + device_init_wakeup(dev, 0); + cancel_work_sync(&cd->enum_work); + pt_stop_wd_timer(cd); + pt_free_si_ptrs(cd); + remove_sysfs_interfaces(dev); + +error_after_startup: + pr_err("%s PARADE error_after_startup\n", __func__); + del_timer(&cd->watchdog_timer); + if (cd->cpdata->setup_irq) + cd->cpdata->setup_irq(cd->cpdata, PT_MT_IRQ_FREE, dev); +error_setup_irq: +error_detect: + if (cd->cpdata->init) + cd->cpdata->init(cd->cpdata, PT_MT_POWER_OFF, dev); + if (cd->cpdata->setup_power) + cd->cpdata->setup_power(cd->cpdata, PT_MT_POWER_OFF, dev); + sysfs_remove_group(&dev->kobj, &early_attr_group); +error_enable_regulator: + pt_del_core(dev); + dev_set_drvdata(dev, NULL); + pt_enable_regulator(cd, false); +error_get_regulator: + pt_get_regulator(cd, false); +error_alloc_data: + kfree(cd); +error_no_pdata: + pr_err("%s failed.\n", __func__); + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_device_entry + * + * SUMMARY: Wrapper function of pt_core_suspend_() to help avoid TP from being + * woke up or put to sleep based on Kernel power state even when the display + * is off based on the check of TTDL core platform flag. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to core device + ******************************************************************************/ +#ifdef PT_AMBIENT_MODE +int pt_device_entry(struct device *dev, + u16 irq, size_t xfer_buf_size) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_platform_data *pdata = dev_get_platdata(dev); + struct i2c_client *client = to_i2c_client(dev); + int rc = 0; + void *glink_pt_send_msg; + int glink_touch_exit = TOUCH_EXIT; + + pt_debug(dev, DL_INFO, "%s: Start pt_device_entry\n", __func__); + + cd->dev = dev; + cd->pdata = pdata; + cd->cpdata = pdata->core_pdata; + + glink_pt_send_msg = &glink_touch_exit; + pt_debug(dev, DL_INFO, "[touch]glink_pt_send_msg = %d\n", glink_pt_send_msg); + glink_touch_tx_msg(glink_pt_send_msg, TOUCH_MSG_SIZE); + + msleep(150); + + if (!rc && cd->ts_pinctrl) { + /* + * Pinctrl handle is optional. If pinctrl handle is found + * let pins to be configured in active state. If not + * found continue further without error. + */ + rc = pinctrl_select_state(cd->ts_pinctrl, cd->pinctrl_state_active); + if (rc < 0) + dev_err(&client->dev, "failed to select pin to active state\n"); + } + + /* Set platform easywake value */ + cd->easy_wakeup_gesture = cd->cpdata->easy_wakeup_gesture; + + /* + * When the IRQ GPIO is not direclty accessible and no function is + * defined to get the IRQ status, the IRQ passed in must be assigned + * directly as the gpio_to_irq will not work. e.g. CHROMEOS + */ + + if (!cd->cpdata->irq_stat) { + cd->irq = irq; + pt_debug(cd->dev, DL_ERROR, "%s:No irq_stat, Set cd->irq = %d\n", __func__, cd->irq); + } + + /* Call platform init function before setting up the GPIO's */ + if (cd->cpdata->init) { + pt_debug(cd->dev, DL_INFO, "%s: Init HW\n", __func__); + rc = cd->cpdata->init(cd->cpdata, PT_MT_POWER_ON, cd->dev); + } else { + pt_debug(cd->dev, DL_ERROR, "%s: No HW INIT function\n", __func__); + rc = 0; + } + if (rc < 0) { + pt_debug(cd->dev, DL_ERROR, "%s: HW Init fail r=%d\n", __func__, rc); + } + + /* Power on any needed regulator(s) */ + if (cd->cpdata->setup_power) { + pt_debug(cd->dev, DL_INFO, "%s: Device power on!\n", __func__); + rc = cd->cpdata->setup_power(cd->cpdata, PT_MT_POWER_ON, cd->dev); + } else { + pt_debug(cd->dev, DL_ERROR, "%s: No setup power function\n", __func__); + rc = 0; + } + if (rc < 0) + pt_debug(cd->dev, DL_ERROR, "%s: Setup power on fail r=%d\n", __func__, rc); + + if (cd->cpdata->detect) { + pt_debug(cd->dev, DL_INFO, "%s: Detect HW\n", __func__); + rc = cd->cpdata->detect(cd->cpdata, cd->dev, pt_platform_detect_read); + if (!rc) { + cd->hw_detected = true; + pt_debug(cd->dev, DL_INFO, "%s: HW detected\n", __func__); + } else { + cd->hw_detected = false; + pt_debug(cd->dev, DL_INFO, "%s: No HW detected\n", __func__); + rc = -ENODEV; + goto pt_error_detect; + } + } else { + pt_debug(dev, DL_ERROR, "%s: PARADE No HW detect function pointer\n", __func__); + /* + * "hw_reset" is not needed in the "if" statement, + * because "hw_reset" is already included in "hw_detect" + * function. + */ + rc = pt_hw_hard_reset(cd); + if (rc) + pt_debug(cd->dev, DL_ERROR, "%s: FAILED to execute HARD reset\n", __func__); + } + + if (cd->cpdata->setup_irq) { + pt_debug(cd->dev, DL_INFO, "%s: setup IRQ\n", __func__); + rc = cd->cpdata->setup_irq(cd->cpdata, PT_MT_IRQ_REG, cd->dev); + if (rc) { + pt_debug(dev, DL_ERROR, "%s: Error, couldn't setup IRQ\n", __func__); + goto pt_error_setup_irq; + } + } else { + pt_debug(dev, DL_ERROR, "%s: IRQ function pointer not setup\n", __func__); + goto pt_error_setup_irq; + } + + rc = device_init_wakeup(dev, 1); + if (rc < 0) + pt_debug(dev, DL_ERROR, "%s: Error, device_init_wakeup rc:%d\n", __func__, rc); + + if (!enable_irq_wake(cd->irq)) { + cd->irq_wake = 1; + pt_debug(cd->dev, DL_WARN, "%s Device MAY wakeup\n", __func__); + } + + pm_runtime_get_noresume(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + /* Without sleep DUT is not ready and will NAK the first write */ + msleep(150); + + pm_runtime_put_sync(dev); + +#if defined(CONFIG_PANEL_NOTIFIER) + /* Setup active dsi panel */ + active_panel = cd->cpdata->active_panel; + + + pt_debug(dev, DL_ERROR, "%s: Probe: Setup Panel Event notifier\n", __func__); + pt_setup_panel_event_notifier(cd); +#endif + + mutex_lock(&cd->system_lock); + cd->core_probe_complete = 1; + mutex_unlock(&cd->system_lock); + + pt_debug(dev, DL_INFO, "%s: ####TTDL Core Device Probe Completed Successfully\n", __func__); + pt_core_state = STATE_RESUME; + return 0; + +pt_error_setup_irq: + device_init_wakeup(dev, 0); +pt_error_detect: + if (cd->cpdata->init) + cd->cpdata->init(cd->cpdata, PT_MT_POWER_OFF, dev); + if (cd->cpdata->setup_power) + cd->cpdata->setup_power(cd->cpdata, PT_MT_POWER_OFF, dev); + return rc; +} +#endif + +EXPORT_SYMBOL_GPL(pt_probe); + +/******************************************************************************* + * FUNCTION: pt_release + * + * SUMMARY: This function does the following cleanup: + * - Releases all probed modules + * - Stops the watchdog + * - Cancels all pending work tasks + * - Removes all created sysfs nodes + * - Removes all created debugfs nodes + * - Frees the IRQ + * - Deletes the core + * - Frees all pointers and HID reports + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *cd - pointer to the core data structure + ******************************************************************************/ +int pt_release(struct pt_core_data *cd) +{ + struct device *dev = cd->dev; + + /* + * Suspend the device before freeing the startup_work and stopping + * the watchdog since sleep function restarts watchdog on failure + */ + pm_runtime_suspend(dev); + pm_runtime_disable(dev); + + /* + * Any 'work' that can trigger a new thread should be canceled first. + * The watchdog is also stopped again because another thread could have + * restarted it. The irq_work is cancelled last because it is used for + * all I2C/SPI communication. + */ + pt_stop_wd_timer(cd); + call_atten_cb(cd, PT_ATTEN_CANCEL_LOADER, 0); + cancel_work_sync(&cd->ttdl_restart_work); + cancel_work_sync(&cd->enum_work); + cancel_work_sync(&cd->resume_offload_work); + cancel_work_sync(&cd->suspend_offload_work); + cancel_work_sync(&cd->resume_work); + cancel_work_sync(&cd->suspend_work); + destroy_workqueue(cd->pt_workqueue); + + pt_stop_wd_timer(cd); + + pt_release_modules(cd); + pt_proximity_release(dev); + pt_btn_release(dev); + pt_mt_release(dev); + +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&cd->es); +#elif defined(CONFIG_PANEL_NOTIFIER) + if (active_panel) + panel_event_notifier_unregister(cd->entry); +#elif defined(CONFIG_DRM) + if (active_panel) + drm_panel_notifier_unregister(active_panel, &cd->fb_notifier); +#elif defined(CONFIG_FB) + fb_unregister_client(&cd->fb_notifier); +#endif + +#ifdef NEED_SUSPEND_NOTIFIER + unregister_pm_notifier(&cd->pm_notifier); +#endif + +#if (KERNEL_VERSION(3, 16, 0) > LINUX_VERSION_CODE) + device_wakeup_disable(dev); +#endif + device_init_wakeup(dev, 0); + +#ifdef TTHE_TUNER_SUPPORT + mutex_lock(&cd->tthe_lock); + cd->tthe_exit = 1; + wake_up(&cd->wait_q); + mutex_unlock(&cd->tthe_lock); + debugfs_remove(cd->tthe_debugfs); +#endif + + sysfs_remove_group(&dev->kobj, &early_attr_group); + + remove_sysfs_interfaces(dev); + + disable_irq_nosync(cd->irq); + if (cd->cpdata->setup_irq) + cd->cpdata->setup_irq(cd->cpdata, PT_MT_IRQ_FREE, dev); + if (cd->cpdata->init) + cd->cpdata->init(cd->cpdata, PT_MT_POWER_OFF, dev); + if (cd->cpdata->setup_power) + cd->cpdata->setup_power(cd->cpdata, PT_MT_POWER_OFF, dev); + dev_set_drvdata(dev, NULL); + pt_del_core(dev); + if (cd->vcc_i2c) + regulator_set_load(cd->vcc_i2c, 0); + + if (cd->vdd) + regulator_set_load(cd->vdd, 0); + pt_enable_regulator(cd, false); + pt_get_regulator(cd, false); + pt_free_si_ptrs(cd); + kfree(cd); + return 0; +} +EXPORT_SYMBOL_GPL(pt_release); + + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Parade TrueTouch(R) Standard Product Core Driver"); +MODULE_AUTHOR("Parade Technologies "); diff --git a/qcom/opensource/touch-drivers/pt/pt_core.h b/qcom/opensource/touch-drivers/pt/pt_core.h new file mode 100644 index 0000000000..792427717a --- /dev/null +++ b/qcom/opensource/touch-drivers/pt/pt_core.h @@ -0,0 +1,230 @@ +/* + * pt_core.h + * Parade TrueTouch(TM) Standard Product Core Module. + * For use with Parade touchscreen controllers. + * Supported parts include: + * TMA5XX + * TMA448 + * TMA445A + * TT21XXX + * TT31XXX + * TT4XXXX + * TT7XXX + * TC3XXX + * + * Copyright (C) 2015-2020 Parade Technologies + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * 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. + * + * Contact Parade Technologies at www.parade.com + */ + +#ifndef _LINUX_PT_CORE_H +#define _LINUX_PT_CORE_H + +#include +#include + +#define PT_I2C_NAME "pt_i2c_adapter" +#define PT_SPI_NAME "pt_spi_adapter" + +#define PT_CORE_NAME "pt_core" +#define PT_MT_NAME "pt_mt" +#define PT_BTN_NAME "pt_btn" +#define PT_PROXIMITY_NAME "pt_proximity" + +#define PT_DRIVER_NAME TTDL +#define PT_DRIVER_MAJOR 04 +#define PT_DRIVER_MINOR 11 + +#define PT_DRIVER_REVCTRL 977092 + +#define PT_DRIVER_VERSION \ +__stringify(PT_DRIVER_NAME) \ +"." __stringify(PT_DRIVER_MAJOR) \ +"." __stringify(PT_DRIVER_MINOR) \ +"." __stringify(PT_DRIVER_REVCTRL) + +#define PT_DRIVER_DATE "20201210" + +/* abs settings */ +#define PT_IGNORE_VALUE -1 + +enum pt_core_platform_flags { + PT_CORE_FLAG_NONE, + PT_CORE_FLAG_POWEROFF_ON_SLEEP = 0x02, + PT_CORE_FLAG_RESTORE_PARAMETERS = 0x04, + PT_CORE_FLAG_DEEP_STANDBY = 0x08, + PT_CORE_FLAG_SKIP_SYS_SLEEP = 0x10, + PT_CORE_FLAG_SKIP_RUNTIME = 0x20, + PT_CORE_FLAG_SKIP_RESUME = 0x40, +}; + +enum pt_core_platform_easy_wakeup_gesture { + PT_CORE_EWG_NONE, + PT_CORE_EWG_TAP_TAP, + PT_CORE_EWG_TWO_FINGER_SLIDE, + PT_CORE_EWG_RESERVED, + PT_CORE_EWG_WAKE_ON_INT_FROM_HOST = 0xFF, +}; + +enum pt_loader_platform_flags { + PT_LOADER_FLAG_NONE, + PT_LOADER_FLAG_CALIBRATE_AFTER_FW_UPGRADE, + /* Use CONFIG_VER field in TT_CFG to decide TT_CFG update */ + PT_LOADER_FLAG_CHECK_TTCONFIG_VERSION, + PT_LOADER_FLAG_CALIBRATE_AFTER_TTCONFIG_UPGRADE, +}; + +enum CONFIG_DUT_GENERATION { + CONFIG_DUT_AUTO_DETECT = 0x00, + CONFIG_DUT_PIP1_ONLY = 0x01, + CONFIG_DUT_PIP2_CAPABLE = 0x02, +}; + +enum pt_core_platform_panel_id_flags { + PT_PANEL_ID_DISABLE = 0x00, + PT_PANEL_ID_BY_BL = 0x01, + PT_PANEL_ID_BY_SYS_INFO = 0x02, + PT_PANEL_ID_BY_MFG_DATA = 0x04, +}; + +struct touch_settings { + const uint8_t *data; + uint32_t size; + uint8_t tag; +}; + +struct pt_touch_firmware { + const uint8_t *img; + uint32_t size; + const uint8_t *ver; + uint8_t vsize; + uint8_t panel_id; +}; + +struct pt_touch_config { + struct touch_settings *param_regs; + struct touch_settings *param_size; + const uint8_t *fw_ver; + uint8_t fw_vsize; + uint8_t panel_id; +}; + +struct pt_loader_platform_data { + struct pt_touch_firmware *fw; + struct pt_touch_config *ttconfig; + struct pt_touch_firmware **fws; + struct pt_touch_config **ttconfigs; + u32 flags; +}; + +typedef int (*pt_platform_read) (struct device *dev, void *buf, int size); + +#define PT_TOUCH_SETTINGS_MAX 32 + +struct pt_core_platform_data { + int irq_gpio; + u32 irq_gpio_flags; + int rst_gpio; + u32 rst_gpio_flags; + int ddi_rst_gpio; + int vddi_gpio; + int vcc_gpio; + int avdd_gpio; + int avee_gpio; + int level_irq_udelay; + u16 hid_desc_register; + u16 vendor_id; + u16 product_id; + + int (*xres)(struct pt_core_platform_data *pdata, + struct device *dev); + int (*init)(struct pt_core_platform_data *pdata, + int on, struct device *dev); + int (*power)(struct pt_core_platform_data *pdata, + int on, struct device *dev, atomic_t *ignore_irq); + int (*detect)(struct pt_core_platform_data *pdata, + struct device *dev, pt_platform_read read); + int (*irq_stat)(struct pt_core_platform_data *pdata, + struct device *dev); + int (*setup_power)(struct pt_core_platform_data *pdata, + int on, struct device *dev); + int (*setup_irq)(struct pt_core_platform_data *pdata, + int on, struct device *dev); + struct touch_settings *sett[PT_TOUCH_SETTINGS_MAX]; + u32 flags; + u8 easy_wakeup_gesture; + u8 config_dut_generation; + u8 watchdog_force_stop; + u8 panel_id_support; + + struct device_node *node; + struct pinctrl *pinctrl; + struct pinctrl_state *pins_active; + struct pinctrl_state *pins_suspend; + struct pinctrl_state *pins_release; + + struct drm_panel *active_panel; +}; + +struct touch_framework { + const int16_t *abs; + uint8_t size; + uint8_t enable_vkeys; +} __packed; + +enum pt_mt_platform_power_state { + PT_MT_POWER_OFF = 0x00, + PT_MT_POWER_ON = 0x01 +}; + +enum pt_mt_platform_irq_state { + PT_MT_IRQ_FREE = 0x00, + PT_MT_IRQ_REG = 0x01 +}; + +enum pt_mt_platform_flags { + PT_MT_FLAG_NONE, + PT_MT_FLAG_HOVER = 0x04, + PT_MT_FLAG_FLIP = 0x08, + PT_MT_FLAG_INV_X = 0x10, + PT_MT_FLAG_INV_Y = 0x20, + PT_MT_FLAG_VKEYS = 0x40, + PT_MT_FLAG_NO_TOUCH_ON_LO = 0x80, +}; + +struct pt_mt_platform_data { + struct touch_framework *frmwrk; + unsigned short flags; + char const *inp_dev_name; + int vkeys_x; + int vkeys_y; +}; + +struct pt_btn_platform_data { + char const *inp_dev_name; +}; + +struct pt_proximity_platform_data { + struct touch_framework *frmwrk; + char const *inp_dev_name; +}; + +struct pt_platform_data { + struct pt_core_platform_data *core_pdata; + struct pt_mt_platform_data *mt_pdata; + struct pt_btn_platform_data *btn_pdata; + struct pt_proximity_platform_data *prox_pdata; + struct pt_loader_platform_data *loader_pdata; +}; + +#endif /* _LINUX_PT_CORE_H */ diff --git a/qcom/opensource/touch-drivers/pt/pt_debug.c b/qcom/opensource/touch-drivers/pt/pt_debug.c new file mode 100644 index 0000000000..a959e6e089 --- /dev/null +++ b/qcom/opensource/touch-drivers/pt/pt_debug.c @@ -0,0 +1,556 @@ +/* + * pt_debug.c + * Parade TrueTouch(TM) Standard Product Debug Module. + * For use with Parade touchscreen controllers. + * Supported parts include: + * TMA5XX + * TMA448 + * TMA445A + * TT21XXX + * TT31XXX + * TT4XXXX + * TT7XXX + * TC3XXX + * + * Copyright (C) 2015-2020 Parade Technologies + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * 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. + * + * Contact Parade Technologies at www.paradetech.com + */ + +#include "pt_regs.h" + +#define PT_DEBUG_NAME "pt_debug" + +struct pt_debug_data { + struct device *dev; + struct pt_sysinfo *si; + uint32_t interrupt_count; + uint32_t formated_output; + struct mutex sysfs_lock; + u8 pr_buf[PT_MAX_PRBUF_SIZE]; +}; + +static struct pt_core_commands *cmd; + +static struct pt_module debug_module; + +/******************************************************************************* + * FUNCTION: pt_get_debug_data + * + * SUMMARY: Inline function to get pt_debug_data pointer from debug module. + * + * RETURN: + * pointer to pt_debug_data structure + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static inline struct pt_debug_data *pt_get_debug_data( + struct device *dev) +{ + return pt_get_module_data(dev, &debug_module); +} + +/******************************************************************************* + * FUNCTION: pt_pr_buf_op_mode + * + * SUMMARY: Formats touch/button report to pr_buf that combined xy_mode and + * xy_data. The feature is required by TTHE. + * + * PARAMETERS: + * *dd - pointer to pt_debug_data structure + * *pr_buf - pointer to print buffer + * *si - pointer to sysinfo structure + * cur_touch - number of current touch + ******************************************************************************/ +static void pt_pr_buf_op_mode(struct pt_debug_data *dd, u8 *pr_buf, + struct pt_sysinfo *si, u8 cur_touch) +{ + const char fmt[] = "%02X "; + int max = (PT_MAX_PRBUF_SIZE - 1) - sizeof(PT_PR_TRUNCATED); + u8 report_id = si->xy_mode[2]; + int header_size = 0; + int report_size = 0; + int total_size = 0; + int i, k; + + if (report_id == si->desc.tch_report_id) { + header_size = si->desc.tch_header_size; + report_size = cur_touch * si->desc.tch_record_size; + } else if (report_id == si->desc.btn_report_id) { + header_size = BTN_INPUT_HEADER_SIZE; + report_size = BTN_REPORT_SIZE; + } + total_size = header_size + report_size; + + pr_buf[0] = 0; + for (i = k = 0; i < header_size && i < max; i++, k += 3) + scnprintf(pr_buf + k, PT_MAX_PRBUF_SIZE, fmt, si->xy_mode[i]); + + for (i = 0; i < report_size && i < max; i++, k += 3) + scnprintf(pr_buf + k, PT_MAX_PRBUF_SIZE, fmt, si->xy_data[i]); + + pr_info("%s=%s%s\n", "pt_OpModeData", pr_buf, + total_size <= max ? "" : PT_PR_TRUNCATED); +} + +/******************************************************************************* + * FUNCTION: pt_debug_print + * + * SUMMARY: This function prints header to show data size and data_name and + * content of "pr_buf" with hex base. + * + * PARAMETERS: + * *dev - pointer to device structure + * *pr_buf - pointer to input buffer which stores the formated data + * *sptr - pointer to the buffer to print + * size - size of data elements + * *data_name - data name to print + ******************************************************************************/ +static void pt_debug_print(struct device *dev, u8 *pr_buf, u8 *sptr, + int size, const char *data_name) +{ + int i, j; + int elem_size = sizeof("XX ") - 1; + int max = (PT_MAX_PRBUF_SIZE - 1) / elem_size; + int limit = size < max ? size : max; + + if (limit < 0) + limit = 0; + + pr_buf[0] = 0; + for (i = j = 0; i < limit; i++, j += elem_size) + scnprintf(pr_buf + j, PT_MAX_PRBUF_SIZE - j, "%02X ", sptr[i]); + + if (size) + pr_info("%s[0..%d]=%s%s\n", data_name, size - 1, pr_buf, + size <= max ? "" : PT_PR_TRUNCATED); + else + pr_info("%s[]\n", data_name); +} + +/******************************************************************************* + * FUNCTION: pt_debug_formated + * + * SUMMARY: Formats and prints touch & button report. + * + * PARAMETERS: + * *dev - pointer to device structure + * *pr_buf - pointer to print buffer + * *si - pointer to sysinfo structure + * cur_touch - number of current touch + ******************************************************************************/ +static void pt_debug_formated(struct device *dev, u8 *pr_buf, + struct pt_sysinfo *si, u8 num_cur_tch) +{ + u8 report_id = si->xy_mode[2]; + int header_size = 0; + int report_size = 0; + u8 data_name[] = "touch[99]"; + int max_print_length = 20; + int i; + + if (report_id == si->desc.tch_report_id) { + header_size = si->desc.tch_header_size; + report_size = num_cur_tch * si->desc.tch_record_size; + } else if (report_id == si->desc.btn_report_id) { + header_size = BTN_INPUT_HEADER_SIZE; + report_size = BTN_REPORT_SIZE; + } + + /* xy_mode */ + pt_debug_print(dev, pr_buf, si->xy_mode, header_size, "xy_mode"); + + /* xy_data */ + if (report_size > max_print_length) { + pr_info("xy_data[0..%d]:\n", report_size); + for (i = 0; i < report_size - max_print_length; + i += max_print_length) { + pt_debug_print(dev, pr_buf, si->xy_data + i, + max_print_length, " "); + } + if (report_size - i) + pt_debug_print(dev, pr_buf, si->xy_data + i, + report_size - i, " "); + } else { + pt_debug_print(dev, pr_buf, si->xy_data, report_size, + "xy_data"); + } + + /* touches */ + if (report_id == si->desc.tch_report_id) { + for (i = 0; i < num_cur_tch; i++) { + scnprintf(data_name, sizeof(data_name) - 1, + "touch[%u]", i); + pt_debug_print(dev, pr_buf, + si->xy_data + (i * si->desc.tch_record_size), + si->desc.tch_record_size, data_name); + } + } + + /* buttons */ + if (report_id == si->desc.btn_report_id) + pt_debug_print(dev, pr_buf, si->xy_data, report_size, + "button"); +} + +/******************************************************************************* + * FUNCTION: pt_xy_worker + * + * SUMMARY: Read xy_data for all touches for debug. + * + * RETURN: + * 0 = success + * + * PARAMETERS: + * *dd - pointer to pt_debug_data structure + ******************************************************************************/ +static int pt_xy_worker(struct pt_debug_data *dd) +{ + struct device *dev = dd->dev; + struct pt_sysinfo *si = dd->si; + u8 report_reg = si->xy_mode[TOUCH_COUNT_BYTE_OFFSET]; + u8 num_cur_tch = GET_NUM_TOUCHES(report_reg); + uint32_t formated_output; + + mutex_lock(&dd->sysfs_lock); + dd->interrupt_count++; + formated_output = dd->formated_output; + mutex_unlock(&dd->sysfs_lock); + + /* Interrupt */ + pr_info("Interrupt(%u)\n", dd->interrupt_count); + + if (formated_output) + pt_debug_formated(dev, dd->pr_buf, si, num_cur_tch); + else + /* print data for TTHE */ + pt_pr_buf_op_mode(dd, dd->pr_buf, si, num_cur_tch); + + pr_info("\n"); + + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_debug_attention + * + * SUMMARY: Wrapper function for pt_xy_worker() to subcribe into TTDL attention + * list. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int pt_debug_attention(struct device *dev) +{ + struct pt_debug_data *dd = pt_get_debug_data(dev); + struct pt_sysinfo *si = dd->si; + u8 report_id = si->xy_mode[2]; + int rc = 0; + + if (report_id != si->desc.tch_report_id + && report_id != si->desc.btn_report_id) + return 0; + + /* core handles handshake */ + rc = pt_xy_worker(dd); + if (rc < 0) + pt_debug(dev, DL_ERROR, "%s: xy_worker error r=%d\n", + __func__, rc); + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_status_show + * + * SUMMARY: The show method for the int_count sysfs node. This node displays + * the count of interrupt. + * + * PARAMETERS: + * *dev - pointer to Device structure + * *attr - pointer to the device attribute structure + * *buf - pointer to buffer to print + ******************************************************************************/ +static ssize_t pt_interrupt_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pt_debug_data *dd = pt_get_debug_data(dev); + int val; + + mutex_lock(&dd->sysfs_lock); + val = dd->interrupt_count; + mutex_unlock(&dd->sysfs_lock); + + return scnprintf(buf, PT_MAX_PRBUF_SIZE, "Interrupt Count: %d\n", val); +} + +/******************************************************************************* + * FUNCTION: pt_interrupt_count_store + * + * SUMMARY: The store method for the int_count sysfs node that allows the count + * of interrput to be cleared. + * + * RETURN: Size of passed in buffer + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to buffer that hold the command parameters + * size - size of buf + ******************************************************************************/ +static ssize_t pt_interrupt_count_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct pt_debug_data *dd = pt_get_debug_data(dev); + + mutex_lock(&dd->sysfs_lock); + dd->interrupt_count = 0; + mutex_unlock(&dd->sysfs_lock); + return size; +} + +static DEVICE_ATTR(int_count, 0600, + pt_interrupt_count_show, pt_interrupt_count_store); + +/******************************************************************************* + * FUNCTION: pt_formated_output_show + * + * SUMMARY: Show method for the formated_output sysfs node that will show + * whether to format data to buffer or print directly. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to output buffer + ******************************************************************************/ +static ssize_t pt_formated_output_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pt_debug_data *dd = pt_get_debug_data(dev); + int val; + + mutex_lock(&dd->sysfs_lock); + val = dd->formated_output; + mutex_unlock(&dd->sysfs_lock); + + return scnprintf(buf, PT_MAX_PRBUF_SIZE, + "Formated debug output: %x\n", val); +} + +/******************************************************************************* + * FUNCTION: pt_formated_output_store + * + * SUMMARY: The store method for the formated_output sysfs node. Allows the + * setting to format data to buffer or print directly. + * + * RETURN: Size of passed in buffer + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to buffer that hold the command parameters + * size - size of buf + ******************************************************************************/ +static ssize_t pt_formated_output_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct pt_debug_data *dd = pt_get_debug_data(dev); + unsigned long value; + int rc; + + rc = kstrtoul(buf, 10, &value); + if (rc < 0) { + pt_debug(dev, DL_ERROR, "%s: Invalid value\n", __func__); + return size; + } + + /* Expecting only 0 or 1 */ + if (value != 0 && value != 1) { + pt_debug(dev, DL_ERROR, "%s: Invalid value %lu\n", + __func__, value); + return size; + } + + mutex_lock(&dd->sysfs_lock); + dd->formated_output = value; + mutex_unlock(&dd->sysfs_lock); + return size; +} + +static DEVICE_ATTR(formated_output, 0600, + pt_formated_output_show, pt_formated_output_store); + +/******************************************************************************* + * FUNCTION: pt_mt_probe + * + * SUMMARY: The probe function for debug module to create sysfs nodes and + * subscribe attention list. + * + * RETURN: + * 0 = success + * + * PARAMETERS: + * *dev - pointer to device structure + * **data - double pointer tothe pt_debug_data structure to be created here + ******************************************************************************/ +static int pt_debug_probe(struct device *dev, void **data) +{ + struct pt_debug_data *dd; + int rc; + + /* get context and debug print buffers */ + dd = kzalloc(sizeof(*dd), GFP_KERNEL); + if (!dd) { + rc = -ENOMEM; + goto pt_debug_probe_alloc_failed; + } + + rc = device_create_file(dev, &dev_attr_int_count); + if (rc) { + pt_debug(dev, DL_ERROR, "%s: Error, could not create int_count\n", + __func__); + goto pt_debug_probe_create_int_count_failed; + } + + rc = device_create_file(dev, &dev_attr_formated_output); + if (rc) { + pt_debug(dev, DL_ERROR, "%s: Error, could not create formated_output\n", + __func__); + goto pt_debug_probe_create_formated_failed; + } + + mutex_init(&dd->sysfs_lock); + dd->dev = dev; + *data = dd; + + dd->si = cmd->request_sysinfo(dev); + if (!dd->si) { + pt_debug(dev, DL_ERROR, "%s: Fail get sysinfo pointer from core\n", + __func__); + rc = -ENODEV; + goto pt_debug_probe_sysinfo_failed; + } + + rc = cmd->subscribe_attention(dev, PT_ATTEN_IRQ, PT_DEBUG_NAME, + pt_debug_attention, PT_MODE_OPERATIONAL); + if (rc < 0) { + pt_debug(dev, DL_ERROR, "%s: Error, could not subscribe attention cb\n", + __func__); + goto pt_debug_probe_subscribe_failed; + } + + return 0; + +pt_debug_probe_subscribe_failed: +pt_debug_probe_sysinfo_failed: + device_remove_file(dev, &dev_attr_formated_output); +pt_debug_probe_create_formated_failed: + device_remove_file(dev, &dev_attr_int_count); +pt_debug_probe_create_int_count_failed: + kfree(dd); +pt_debug_probe_alloc_failed: + pt_debug(dev, DL_ERROR, "%s failed.\n", __func__); + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_debug_release + * + * SUMMARY: Remove function for debug module that does following cleanup: + * - Unsubscibe all registered attention tasks + * - Removes all created sysfs nodes + * + * PARAMETERS: + * *dev - pointer to device structure + * *data - pointer to the pt_debug_data structure + ******************************************************************************/ +static void pt_debug_release(struct device *dev, void *data) +{ + struct pt_debug_data *dd = data; + int rc; + + rc = cmd->unsubscribe_attention(dev, PT_ATTEN_IRQ, PT_DEBUG_NAME, + pt_debug_attention, PT_MODE_OPERATIONAL); + if (rc < 0) { + pt_debug(dev, DL_ERROR, "%s: Error, could not un-subscribe attention\n", + __func__); + goto pt_debug_release_exit; + } + +pt_debug_release_exit: + device_remove_file(dev, &dev_attr_formated_output); + device_remove_file(dev, &dev_attr_int_count); + kfree(dd); +} + +static struct pt_module debug_module = { + .name = PT_DEBUG_NAME, + .probe = pt_debug_probe, + .release = pt_debug_release, +}; + +/******************************************************************************* + * FUNCTION: pt_debug_init + * + * SUMMARY: Initialize function for debug module which to register + * debug_module into TTDL module list. + * + * RETURN: + * 0 = success + ******************************************************************************/ +static int __init pt_debug_init(void) +{ + int rc; + + cmd = pt_get_commands(); + if (!cmd) + return -EINVAL; + + rc = pt_register_module(&debug_module); + if (rc < 0) { + pr_err("%s: Error, failed registering module\n", + __func__); + return rc; + } + + pr_info("%s: Parade TTSP Debug Driver (Built %s) rc=%d\n", + __func__, PT_DRIVER_VERSION, rc); + return 0; +} +module_init(pt_debug_init); + +/******************************************************************************* + * FUNCTION: pt_debug_exit + * + * SUMMARY: Exit function for debug module which to unregister debug_module + * from TTDL module list. + * + ******************************************************************************/ +static void __exit pt_debug_exit(void) +{ + pt_unregister_module(&debug_module); +} +module_exit(pt_debug_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Parade TrueTouch(R) Standard Product Debug Driver"); +MODULE_AUTHOR("Parade Technologies "); diff --git a/qcom/opensource/touch-drivers/pt/pt_device_access.c b/qcom/opensource/touch-drivers/pt/pt_device_access.c new file mode 100644 index 0000000000..611483a06b --- /dev/null +++ b/qcom/opensource/touch-drivers/pt/pt_device_access.c @@ -0,0 +1,6746 @@ +/* + * pt_device_access.c + * Parade TrueTouch(TM) Standard Product Device Access Module. + * Configuration and Test command/status user interface. + * For use with Parade touchscreen controllers. + * Supported parts include: + * TMA5XX + * TMA448 + * TMA445A + * TT21XXX + * TT31XXX + * TT4XXXX + * TT7XXX + * TC3XXX + * + * Copyright (C) 2015-2020 Parade Technologies + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * 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. + * + * Contact Parade Technologies at www.paradetech.com + */ + +#include "pt_regs.h" +#include + +#include +#include +#include + +#define PT_CMCP_THRESHOLD_FILE_NAME "pt_thresholdfile.csv" +#define CMCP_THRESHOLD_FILE_NAME "ttdl_cmcp_thresholdfile.csv" + +/* Max test case number */ +#define MAX_CASE_NUM (22) + +/* ASCII */ +#define ASCII_LF (0x0A) +#define ASCII_CR (0x0D) +#define ASCII_COMMA (0x2C) +#define ASCII_ZERO (0x30) +#define ASCII_NINE (0x39) + +/* Max characters of test case name */ +#define NAME_SIZE_MAX (50) + +/* Max sensor and button number */ +#define MAX_BUTTONS (PIP1_SYSINFO_MAX_BTN) +#define MAX_SENSORS (5120) +#define MAX_TX_SENSORS (128) +#define MAX_RX_SENSORS (128) + +/* Multiply by 2 for double (min, max) values */ +#define TABLE_BUTTON_MAX_SIZE (MAX_BUTTONS * 2) +#define TABLE_SENSOR_MAX_SIZE (MAX_SENSORS * 2) +#define TABLE_TX_MAX_SIZE (MAX_TX_SENSORS*2) +#define TABLE_RX_MAX_SIZE (MAX_RX_SENSORS*2) + +#define CM_PANEL_DATA_OFFSET (6) +#define CM_BTN_DATA_OFFSET (6) +#define CP_PANEL_DATA_OFFSET (6) +#define CP_BTN_DATA_OFFSET (6) +#define MAX_BUF_LEN (100000) +#define RETRIEVE_PANEL_SCAN_HDR (10) + +enum print_buffer_format { + PT_PR_FORMAT_DEFAULT = 0, + PT_PR_FORMAT_U8_SPACE = 1, + PT_PR_FORMAT_U16_SPACE = 2, + PT_PR_FORMAT_U8_NO_SPACE = 3, + PT_PR_FORMAT_U32_SPACE = 4, + PT_PR_FORMAT_UNDEFINE +}; + +/* cmcp csv file information */ +struct configuration { + u32 cm_range_limit_row; + u32 cm_range_limit_col; + u32 cm_min_limit_cal; + u32 cm_max_limit_cal; + u32 cm_max_delta_sensor_percent; + u32 cm_max_delta_button_percent; + u32 min_button; + u32 max_button; + u32 cp_max_delta_sensor_rx_percent; + u32 cp_max_delta_sensor_tx_percent; + u32 cm_min_max_table_btn[TABLE_BUTTON_MAX_SIZE]; + u32 cp_min_max_table_btn[TABLE_BUTTON_MAX_SIZE]; + u32 cm_min_max_table_sensor[TABLE_SENSOR_MAX_SIZE]; + u32 cp_min_max_table_rx[TABLE_RX_MAX_SIZE]; + u32 cp_min_max_table_tx[TABLE_TX_MAX_SIZE]; + u32 cm_min_max_table_btn_size; + u32 cp_min_max_table_btn_size; + u32 cm_min_max_table_sensor_size; + u32 cp_min_max_table_rx_size; + u32 cp_min_max_table_tx_size; + u32 cp_max_delta_button_percent; + u32 cm_max_table_gradient_cols_percent[TABLE_TX_MAX_SIZE]; + u32 cm_max_table_gradient_cols_percent_size; + u32 cm_max_table_gradient_rows_percent[TABLE_RX_MAX_SIZE]; + u32 cm_max_table_gradient_rows_percent_size; + u32 cm_excluding_row_edge; + u32 cm_excluding_col_edge; + u32 rx_num; + u32 tx_num; + u32 btn_num; + u32 cm_enabled; + u32 cp_enabled; + u32 is_valid_or_not; +}; + +/* Test case search definition */ +struct test_case_search { + char name[NAME_SIZE_MAX]; /* Test case name */ + u32 name_size; /* Test case name size */ + u32 offset; /* Test case offset */ +}; + +/* Test case field definition */ +struct test_case_field { + char *name; /* Test case name */ + u32 name_size; /* Test case name size */ + u32 type; /* Test case type */ + u32 *bufptr; /* Buffer to store value information */ + u32 exist_or_not;/* Test case exist or not */ + u32 data_num; /* Buffer data number */ + u32 line_num; /* Buffer line number */ +}; + +/* Test case type */ +enum test_case_type { + TEST_CASE_TYPE_NO, + TEST_CASE_TYPE_ONE, + TEST_CASE_TYPE_MUL, + TEST_CASE_TYPE_MUL_LINES, +}; + +/* Test case order in test_case_field_array */ +enum case_order { + CM_TEST_INPUTS, + CM_EXCLUDING_COL_EDGE, + CM_EXCLUDING_ROW_EDGE, + CM_GRADIENT_CHECK_COL, + CM_GRADIENT_CHECK_ROW, + CM_RANGE_LIMIT_ROW, + CM_RANGE_LIMIT_COL, + CM_MIN_LIMIT_CAL, + CM_MAX_LIMIT_CAL, + CM_MAX_DELTA_SENSOR_PERCENT, + CM_MAX_DELTA_BUTTON_PERCENT, + PER_ELEMENT_MIN_MAX_TABLE_BUTTON, + PER_ELEMENT_MIN_MAX_TABLE_SENSOR, + CP_TEST_INPUTS, + CP_MAX_DELTA_SENSOR_RX_PERCENT, + CP_MAX_DELTA_SENSOR_TX_PERCENT, + CP_MAX_DELTA_BUTTON_PERCENT, + CP_PER_ELEMENT_MIN_MAX_BUTTON, + MIN_BUTTON, + MAX_BUTTON, + PER_ELEMENT_MIN_MAX_RX, + PER_ELEMENT_MIN_MAX_TX, + CASE_ORDER_MAX, +}; + +enum cmcp_test_item { + CMCP_FULL = 0, + CMCP_CM_PANEL, + CMCP_CP_PANEL, + CMCP_CM_BTN, + CMCP_CP_BTN, +}; + +#define CM_ENABLED 0x10 +#define CP_ENABLED 0x20 +#define CM_PANEL (0x01 | CM_ENABLED) +#define CP_PANEL (0x02 | CP_ENABLED) +#define CM_BTN (0x04 | CM_ENABLED) +#define CP_BTN (0x08 | CP_ENABLED) +#define CMCP_FULL_CASE\ + (CM_PANEL | CP_PANEL | CM_BTN | CP_BTN | CM_ENABLED | CP_ENABLED) + +#define PT_DEVICE_ACCESS_NAME "pt_device_access" +#define PT_INPUT_ELEM_SZ (sizeof("0xHH") + 1) + +#define PIP_CMD_MAX_LENGTH ((1 << 16) - 1) + +#ifdef TTHE_TUNER_SUPPORT +struct heatmap_param { + bool scan_start; + enum scan_data_type_list data_type; /* raw, base, diff */ + int num_element; +}; +#endif +#define ABS(x) (((x) < 0) ? -(x) : (x)) + +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +#define PT_MAX_CONFIG_BYTES 256 +#define PT_TTHE_TUNER_GET_PANEL_DATA_FILE_NAME "get_panel_data" +#define TTHE_TUNER_MAX_BUF (PT_MAX_PRBUF_SIZE * 8) + +struct pt_device_access_data { + struct device *dev; + struct pt_sysinfo *si; + struct mutex sysfs_lock; + bool sysfs_nodes_created; + struct kobject mfg_test; + u8 panel_scan_retrieve_id; + u8 panel_scan_type_id; + u8 get_idac_data_id; + u8 calibrate_sensing_mode; + u8 calibrate_initialize_baselines; + u8 baseline_sensing_mode; + u8 fw_self_test_id; + u8 fw_self_test_format; + u16 fw_self_test_param_len; + u8 fw_self_test_param[PT_FW_SELF_TEST_MAX_PARM]; + struct pt_cal_ext_data cal_ext_data; + struct dentry *panel_scan_debugfs; + int panel_scan_size; + u8 panel_scan_data_buf[TTHE_TUNER_MAX_BUF]; + struct mutex debugfs_lock; +#ifdef TTHE_TUNER_SUPPORT + struct heatmap_param heatmap; + struct dentry *tthe_get_panel_data_debugfs; + u8 tthe_get_panel_data_is_open; +#endif + struct dentry *cmcp_results_debugfs; + struct dentry *base_dentry; + struct dentry *mfg_test_dentry; + u8 ic_buf[PT_MAX_PRBUF_SIZE]; + u8 response_buf[PT_MAX_PRBUF_SIZE]; + struct mutex cmcp_threshold_lock; + u8 *cmcp_threshold_data; + int cmcp_threshold_size; + bool cmcp_threshold_loading; + struct work_struct cmcp_threshold_update; + int builtin_cmcp_threshold_status; + bool is_manual_upgrade_enabled; + struct configuration *configs; + struct cmcp_data *cmcp_info; + struct result *result; + struct test_case_search *test_search_array; + struct test_case_field *test_field_array; + int cmcp_test_items; + int test_executed; + int cmcp_range_check; + int cmcp_force_calibrate; + int cmcp_test_in_progress; +}; + +struct cmcp_data { + struct gd_sensor *gd_sensor_col; + struct gd_sensor *gd_sensor_row; + int32_t *cm_data_panel; + int32_t *cp_tx_data_panel; + int32_t *cp_rx_data_panel; + int32_t *cp_tx_cal_data_panel; + int32_t *cp_rx_cal_data_panel; + int32_t cp_sensor_rx_delta; + int32_t cp_sensor_tx_delta; + int32_t cp_button_delta; + int32_t *cm_btn_data; + int32_t *cp_btn_data; + int32_t *cm_sensor_column_delta; + int32_t *cm_sensor_row_delta; + int32_t cp_btn_cal; + int32_t cm_btn_cal; + int32_t cp_button_ave; + int32_t cm_ave_data_panel; + int32_t cp_tx_ave_data_panel; + int32_t cp_rx_ave_data_panel; + int32_t cm_cal_data_panel; + int32_t cm_ave_data_btn; + int32_t cm_cal_data_btn; + int32_t cm_delta_data_btn; + int32_t cm_sensor_delta; + + int32_t tx_num; + int32_t rx_num; + int32_t btn_num; +}; + +struct result { + int32_t config_ver; + int32_t revision_ctrl; + int32_t device_id_high; + int32_t device_id_low; + /* Sensor Cm validation */ + bool cm_test_pass; + bool cm_sensor_validation_pass; + bool cm_sensor_row_delta_pass; + bool cm_sensor_col_delta_pass; + bool cm_sensor_gd_row_pass; + bool cm_sensor_gd_col_pass; + bool cm_sensor_calibration_pass; + bool cm_sensor_delta_pass; + bool cm_button_validation_pass; + bool cm_button_delta_pass; + + int32_t *cm_sensor_raw_data; + int32_t cm_sensor_calibration; + int32_t cm_sensor_delta; + int32_t *cm_button_raw_data; + int32_t cm_button_delta; + + /* Sensor Cp validation */ + bool cp_test_pass; + bool cp_sensor_delta_pass; + bool cp_sensor_rx_delta_pass; + bool cp_sensor_tx_delta_pass; + bool cp_sensor_average_pass; + bool cp_button_delta_pass; + bool cp_button_average_pass; + bool cp_rx_validation_pass; + bool cp_tx_validation_pass; + bool cp_button_validation_pass; + + int32_t *cp_sensor_rx_raw_data; + int32_t *cp_sensor_tx_raw_data; + int32_t cp_sensor_rx_delta; + int32_t cp_sensor_tx_delta; + int32_t cp_sensor_rx_calibration; + int32_t cp_sensor_tx_calibration; + int32_t *cp_button_raw_data; + int32_t cp_button_delta; + + /*other validation*/ + bool short_test_pass; + bool test_summary; +}; + +static struct pt_core_commands *cmd; + +static struct pt_module device_access_module; + +static ssize_t pt_run_and_get_selftest_result(struct device *dev, + int protect, char *buf, size_t buf_len, u8 test_id, + u16 read_length, bool get_result_on_pass, + bool print_results, u8 print_format); + +static int _pt_calibrate_idacs_cmd(struct device *dev, + u8 sensing_mode, u8 *status); + +static int pt_perform_calibration(struct device *dev); + +/******************************************************************************* + * FUNCTION: pt_get_device_access_data + * + * SUMMARY: Inline function to get pt_device_access_data. + * + * RETURN: + * pointer to pt_device_access_data structure + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static inline struct pt_device_access_data *pt_get_device_access_data( + struct device *dev) +{ + return pt_get_module_data(dev, &device_access_module); +} + +/******************************************************************************* + * FUNCTION: cmcp_check_config_fw_match + * + * SUMMARY: Checks if tx,rx and btn num of firmware match with configuration. + * + * RETURN: + * 0 = match + * !0 = doesn't match + * + * PARAMETERS: + * *dev - pointer to device structure + * *configuration - pointer to configuration structure + ******************************************************************************/ +static int cmcp_check_config_fw_match(struct device *dev, + struct configuration *configuration) +{ + struct pt_device_access_data *dad + = pt_get_device_access_data(dev); + int32_t tx_num = dad->configs->tx_num; + int32_t rx_num = dad->configs->rx_num; + int32_t button_num = dad->configs->btn_num; + int ret = 0; + + if (tx_num != dad->si->sensing_conf_data.tx_num) { + pt_debug(dev, DL_ERROR, + "%s: TX number mismatch! CSV=%d DUT=%d\n", + __func__, tx_num, dad->si->sensing_conf_data.tx_num); + ret = -EINVAL; + } + + if (rx_num != dad->si->sensing_conf_data.rx_num) { + pt_debug(dev, DL_ERROR, + "%s: RX number mismatch! CSV=%d DUT=%d\n", + __func__, rx_num, dad->si->sensing_conf_data.rx_num); + ret = -EINVAL; + } + + if (button_num != dad->si->num_btns) { + pt_debug(dev, DL_ERROR, + "%s: Button number mismatch! CSV=%d DUT=%d\n", + __func__, button_num, dad->si->num_btns); + ret = -EINVAL; + } + + return ret; +} + +/******************************************************************************* + * FUNCTION: validate_cm_test_results + * + * SUMMARY: Checks cm test results and outputs each test and a summary result + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *configuration - pointer to configuration structure + * *cmcp_info - pointer to cmcp_data structure to store cmcp data from fw + * *result - pointer to result structure + * *pass - pointer to bool value + * test_item - flag to store all test item are requested + ******************************************************************************/ +static int validate_cm_test_results(struct device *dev, + struct configuration *configuration, struct cmcp_data *cmcp_info, + struct result *result, bool *pass, int test_item) +{ + int32_t tx_num = cmcp_info->tx_num; + int32_t rx_num = cmcp_info->rx_num; + int32_t button_num = cmcp_info->btn_num; + uint32_t sensor_num = tx_num * rx_num; + int32_t *cm_sensor_data = cmcp_info->cm_data_panel; + int32_t cm_button_delta; + int32_t cm_sensor_calibration; + int32_t *cm_button_data = cmcp_info->cm_btn_data; + struct gd_sensor *gd_sensor_col = cmcp_info->gd_sensor_col; + struct gd_sensor *gd_sensor_row = cmcp_info->gd_sensor_row; + int32_t *cm_sensor_column_delta = cmcp_info->cm_sensor_column_delta; + int32_t *cm_sensor_row_delta = cmcp_info->cm_sensor_row_delta; + int ret = 0; + int i, j; + + pt_debug(dev, DL_INFO, "%s: start\n", __func__); + + if ((test_item & CM_PANEL) == CM_PANEL) { + pt_debug(dev, DL_INFO, + "Check each sensor Cm data for min max value\n "); + + /* Check each sensor Cm data for min/max values */ + result->cm_sensor_validation_pass = true; + + for (i = 0; i < sensor_num; i++) { + int row = i % rx_num; + int col = i / rx_num; + int32_t cm_sensor_min = + configuration->cm_min_max_table_sensor[(row*tx_num+col)*2]; + int32_t cm_sensor_max = + configuration->cm_min_max_table_sensor[(row*tx_num+col)*2+1]; + if ((cm_sensor_data[i] < cm_sensor_min) || + (cm_sensor_data[i] > cm_sensor_max)) { + pt_debug(dev, DL_WARN, + "%s: Sensor[%d,%d]:%d (%d,%d)\n", + "Cm sensor min/max test", + row, col, + cm_sensor_data[i], + cm_sensor_min, cm_sensor_max); + result->cm_sensor_validation_pass = false; + } + } + + /*check cm gradient column data*/ + result->cm_sensor_gd_col_pass = true; + for (i = 0; i < configuration->cm_max_table_gradient_cols_percent_size; + i++) { + if ((gd_sensor_col + i)->gradient_val > + 10 * configuration->cm_max_table_gradient_cols_percent[i]) { + pt_debug(dev, DL_WARN, + "%s: cm_max_table_gradient_cols_percent[%d]:%d, gradient_val:%d\n", + __func__, i, + configuration->cm_max_table_gradient_cols_percent[i], + (gd_sensor_col + i)->gradient_val); + result->cm_sensor_gd_col_pass = false; + } + } + + /*check cm gradient row data*/ + result->cm_sensor_gd_row_pass = true; + for (j = 0; j < configuration->cm_max_table_gradient_rows_percent_size; + j++) { + if ((gd_sensor_row + j)->gradient_val > + 10 * configuration->cm_max_table_gradient_rows_percent[j]) { + pt_debug(dev, DL_WARN, + "%s: cm_max_table_gradient_rows_percent[%d]:%d, gradient_val:%d\n", + __func__, j, + configuration->cm_max_table_gradient_rows_percent[j], + (gd_sensor_row + j)->gradient_val); + result->cm_sensor_gd_row_pass = false; + } + } + + result->cm_sensor_row_delta_pass = true; + result->cm_sensor_col_delta_pass = true; + result->cm_sensor_calibration_pass = true; + result->cm_sensor_delta_pass = true; + + /* Check each row Cm data with neighbor for difference */ + for (i = 0; i < tx_num; i++) { + for (j = 1; j < rx_num; j++) { + int32_t cm_sensor_row_diff = + ABS(cm_sensor_data[i * rx_num + j] - + cm_sensor_data[i * rx_num + j - 1]); + cm_sensor_row_delta[i * rx_num + j - 1] = + cm_sensor_row_diff; + if (cm_sensor_row_diff > + configuration->cm_range_limit_row) { + pt_debug(dev, DL_DEBUG, + "%s: Sensor[%d,%d]:%d (%d)\n", + "Cm sensor row range limit test", + j, i, cm_sensor_row_diff, + configuration->cm_range_limit_row); + result->cm_sensor_row_delta_pass = false; + } + } + } + + /* Check each column Cm data with neighbor for difference */ + for (i = 1; i < tx_num; i++) { + for (j = 0; j < rx_num; j++) { + int32_t cm_sensor_col_diff = + ABS((int)cm_sensor_data[i * rx_num + j] - + (int)cm_sensor_data[(i - 1) * rx_num + j]); + cm_sensor_column_delta[(i - 1) * rx_num + j] = + cm_sensor_col_diff; + if (cm_sensor_col_diff > + configuration->cm_range_limit_col) { + pt_debug(dev, DL_DEBUG, + "%s: Sensor[%d,%d]:%d (%d)\n", + "Cm sensor column range limit test", + j, i, cm_sensor_col_diff, + configuration->cm_range_limit_col); + result->cm_sensor_col_delta_pass = false; + } + } + } + + /* Check sensor calculated Cm for min/max values */ + cm_sensor_calibration = cmcp_info->cm_cal_data_panel; + if (cm_sensor_calibration < + configuration->cm_min_limit_cal || + cm_sensor_calibration > configuration->cm_max_limit_cal) { + pt_debug(dev, DL_DEBUG, "%s: Cm_cal:%d (%d,%d)\n", + "Cm sensor Cm_cal min/max test", + cm_sensor_calibration, + configuration->cm_min_limit_cal, + configuration->cm_max_limit_cal); + result->cm_sensor_calibration_pass = false; + } + + /* Check sensor Cm delta for range limit */ + if (cmcp_info->cm_sensor_delta > + (10 * configuration->cm_max_delta_sensor_percent)) { + pt_debug(dev, DL_DEBUG, + "%s: Cm_sensor_delta:%d (%d)\n", + "Cm sensor delta range limit test", + cmcp_info->cm_sensor_delta, + configuration->cm_max_delta_sensor_percent); + result->cm_sensor_delta_pass = false; + } + + result->cm_test_pass = result->cm_sensor_gd_col_pass + && result->cm_sensor_gd_row_pass + && result->cm_sensor_validation_pass + && result->cm_sensor_row_delta_pass + && result->cm_sensor_col_delta_pass + && result->cm_sensor_calibration_pass + && result->cm_sensor_delta_pass; + } + + if (((test_item & CM_BTN) == CM_BTN) && (cmcp_info->btn_num)) { + /* Check each button Cm data for min/max values */ + result->cm_button_validation_pass = true; + for (i = 0; i < button_num; i++) { + int32_t cm_button_min = + configuration->cm_min_max_table_btn[i * 2]; + int32_t cm_button_max = + configuration->cm_min_max_table_btn[i * 2 + 1]; + if ((cm_button_data[i] <= cm_button_min) || + (cm_button_data[i] >= cm_button_max)) { + pt_debug(dev, DL_DEBUG, + "%s: Button[%d]:%d (%d,%d)\n", + "Cm button min/max test", + i, cm_button_data[i], + cm_button_min, cm_button_max); + result->cm_button_validation_pass = false; + } + } + + /* Check button Cm delta for range limit */ + result->cm_button_delta_pass = true; + + cm_button_delta = ABS((cmcp_info->cm_ave_data_btn - + cmcp_info->cm_cal_data_btn) * 100 / + cmcp_info->cm_ave_data_btn); + if (cm_button_delta > + configuration->cm_max_delta_button_percent) { + pt_debug(dev, DL_INFO, + "%s: Cm_button_delta:%d (%d)\n", + "Cm button delta range limit test", + cm_button_delta, + configuration->cm_max_delta_button_percent); + result->cm_button_delta_pass = false; + } + + result->cm_test_pass = result->cm_test_pass && + result->cm_button_validation_pass && + result->cm_button_delta_pass; + } + + if (pass) + *pass = result->cm_test_pass; + + return ret; +} + +/******************************************************************************* + * FUNCTION: validate_cp_test_results + * + * SUMMARY: Checks cp test results and outputs each test and a summary result. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *configuration - pointer to configuration structure + * *cmcp_info - pointer to cmcp_data structure to store cmcp data from fw + * *result - pointer to result structure + * *pass - pointer to bool value + * test_item - flag to store all test item are requested + ******************************************************************************/ +static int validate_cp_test_results(struct device *dev, + struct configuration *configuration, struct cmcp_data *cmcp_info, + struct result *result, bool *pass, int test_item) +{ + int i = 0; + uint32_t configuration_rx_num; + uint32_t configuration_tx_num; + int32_t *cp_sensor_tx_data = cmcp_info->cp_tx_data_panel; + int32_t *cp_sensor_rx_data = cmcp_info->cp_rx_data_panel; + int32_t cp_button_delta; + int32_t cp_button_average; + + result->cp_test_pass = true; + configuration_rx_num = configuration->cp_min_max_table_rx_size/2; + configuration_tx_num = configuration->cp_min_max_table_tx_size/2; + + pt_debug(dev, DL_INFO, "%s start\n", __func__); + + if ((test_item & CP_PANEL) == CP_PANEL) { + int32_t cp_sensor_tx_delta; + int32_t cp_sensor_rx_delta; + + /* Check Sensor Cp delta for range limit */ + result->cp_sensor_delta_pass = true; + /*check cp_sensor_tx_delta */ + for (i = 0; i < configuration_tx_num; i++) { + cp_sensor_tx_delta = + ABS((cmcp_info->cp_tx_cal_data_panel[i]- + cmcp_info->cp_tx_data_panel[i]) * 100 / + cmcp_info->cp_tx_data_panel[i]); + + if (cp_sensor_tx_delta > + configuration->cp_max_delta_sensor_tx_percent) { + pt_debug(dev, DL_DEBUG, + "%s: Cp_sensor_tx_delta:%d (%d)\n", + "Cp sensor delta range limit test", + cp_sensor_tx_delta, + configuration->cp_max_delta_sensor_tx_percent); + result->cp_sensor_delta_pass = false; + } + } + + /*check cp_sensor_rx_delta */ + for (i = 0; i < configuration_rx_num; i++) { + cp_sensor_rx_delta = + ABS((cmcp_info->cp_rx_cal_data_panel[i] - + cmcp_info->cp_rx_data_panel[i]) * 100 / + cmcp_info->cp_rx_data_panel[i]); + if (cp_sensor_rx_delta > + configuration->cp_max_delta_sensor_rx_percent) { + pt_debug(dev, DL_DEBUG, + "%s: Cp_sensor_rx_delta:%d(%d)\n", + "Cp sensor delta range limit test", + cp_sensor_rx_delta, + configuration->cp_max_delta_sensor_rx_percent); + result->cp_sensor_delta_pass = false; + } + } + + /* Check sensor Cp rx for min/max values */ + result->cp_rx_validation_pass = true; + for (i = 0; i < configuration_rx_num; i++) { + int32_t cp_rx_min = + configuration->cp_min_max_table_rx[i * 2]; + int32_t cp_rx_max = + configuration->cp_min_max_table_rx[i * 2 + 1]; + if ((cp_sensor_rx_data[i] <= cp_rx_min) || + (cp_sensor_rx_data[i] >= cp_rx_max)) { + pt_debug(dev, DL_DEBUG, + "%s: Cp Rx[%d]:%d (%d,%d)\n", + "Cp Rx min/max test", + i, (int)cp_sensor_rx_data[i], + cp_rx_min, cp_rx_max); + result->cp_rx_validation_pass = false; + } + } + + /* Check sensor Cp tx for min/max values */ + result->cp_tx_validation_pass = true; + for (i = 0; i < configuration_tx_num; i++) { + int32_t cp_tx_min = + configuration->cp_min_max_table_tx[i * 2]; + int32_t cp_tx_max = + configuration->cp_min_max_table_tx[i * 2 + 1]; + if ((cp_sensor_tx_data[i] <= cp_tx_min) || + (cp_sensor_tx_data[i] >= cp_tx_max)) { + pt_debug(dev, DL_DEBUG, + "%s: Cp Tx[%d]:%d(%d,%d)\n", + "Cp Tx min/max test", + i, cp_sensor_tx_data[i], + cp_tx_min, cp_tx_max); + result->cp_tx_validation_pass = false; + } + } + + result->cp_test_pass = result->cp_test_pass + && result->cp_sensor_delta_pass + && result->cp_rx_validation_pass + && result->cp_tx_validation_pass; + } + + if (((test_item & CP_BTN) == CP_BTN) && (cmcp_info->btn_num)) { + result->cp_button_delta_pass = true; + + /* Check button Cp delta for range limit */ + cp_button_delta = ABS((cmcp_info->cp_btn_cal + - cmcp_info->cp_button_ave) * 100 / + cmcp_info->cp_button_ave); + if (cp_button_delta > + configuration->cp_max_delta_button_percent) { + pt_debug(dev, DL_INFO, + "%s: Cp_button_delta:%d (%d)\n", + "Cp button delta range limit test", + cp_button_delta, + configuration->cp_max_delta_button_percent); + result->cp_button_delta_pass = false; + } + + /* Check button Cp average for min/max values */ + result->cp_button_average_pass = true; + cp_button_average = cmcp_info->cp_button_ave; + if (cp_button_average < configuration->min_button || + cp_button_average > configuration->max_button) { + pt_debug(dev, DL_INFO, + "%s: Button Cp average fails min/max test\n", + __func__); + pt_debug(dev, DL_INFO, + "%s: Cp_button_average:%d (%d,%d)\n", + "Cp button average min/max test", + cp_button_average, + configuration->min_button, + configuration->max_button); + result->cp_button_average_pass = false; + } + + /* Check each button Cp data for min/max values */ + result->cp_button_validation_pass = true; + for (i = 0; i < cmcp_info->btn_num; i++) { + int32_t cp_button_min = + configuration->cp_min_max_table_btn[i * 2]; + int32_t cp_button_max = + configuration->cp_min_max_table_btn[i * 2 + 1]; + if ((cmcp_info->cp_btn_data[i] <= cp_button_min) || + (cmcp_info->cp_btn_data[i] >= cp_button_max)) { + pt_debug(dev, DL_DEBUG, + "%s: Button[%d]:%d (%d,%d)\n", + "Cp button min/max test", + i, cmcp_info->cp_btn_data[i], + cp_button_min, cp_button_max); + result->cp_button_validation_pass = false; + } + } + + result->cp_test_pass = result->cp_test_pass + && result->cp_button_delta_pass + && result->cp_button_average_pass + && result->cp_button_validation_pass; + } + + if (pass) + *pass = result->cp_test_pass; + + return 0; +} + +/******************************************************************************* + * FUNCTION: calculate_gradient_row + * + * SUMMARY: Calculates gradient value for rows. + * + * PARAMETERS: + * *gd_sensor_row_head - pointer to gd_sensor structure + * row_num - number of row + * exclude_row_edge - flag to exclude row edge(1:exclude; 0:include) + * exclude_col_edge - flag to exclude column edge(1:exclude; 0:include) + ******************************************************************************/ +static void calculate_gradient_row(struct gd_sensor *gd_sensor_row_head, + uint16_t row_num, int exclude_row_edge, int exclude_col_edge) +{ + int i = 0; + uint16_t cm_min_cur = 0; + uint16_t cm_max_cur = 0; + uint16_t cm_ave_cur = 0; + uint16_t cm_ave_next = 0; + uint16_t cm_ave_prev = 0; + struct gd_sensor *p = gd_sensor_row_head; + + if (exclude_row_edge) { + for (i = 0; i < row_num; i++) { + if (!exclude_col_edge) { + cm_ave_cur = (p + i)->cm_ave; + cm_min_cur = (p + i)->cm_min; + cm_max_cur = (p + i)->cm_max; + if (i < (row_num-1)) + cm_ave_next = (p + i+1)->cm_ave; + if (i > 0) + cm_ave_prev = (p + i-1)->cm_ave; + } else { + cm_ave_cur = (p + i)->cm_ave_exclude_edge; + cm_min_cur = (p + i)->cm_min_exclude_edge; + cm_max_cur = (p + i)->cm_max_exclude_edge; + if (i < (row_num-1)) + cm_ave_next = + (p + i+1)->cm_ave_exclude_edge; + if (i > 0) + cm_ave_prev = + (p + i-1)->cm_ave_exclude_edge; + } + + if (cm_ave_cur == 0) + cm_ave_cur = 1; + + /*multiple 1000 to increate accuracy*/ + if ((i == 0) || (i == (row_num-1))) { + (p + i)->gradient_val = + (cm_max_cur - cm_min_cur) * 1000 / + cm_ave_cur; + } else if (i == 1) { + (p + i)->gradient_val = (cm_max_cur - cm_min_cur + + ABS(cm_ave_cur - cm_ave_next)) * 1000 / + cm_ave_cur; + } else { + (p + i)->gradient_val = (cm_max_cur - cm_min_cur + + ABS(cm_ave_cur - cm_ave_prev)) * 1000 / + cm_ave_cur; + } + } + } else if (!exclude_row_edge) { + for (i = 0; i < row_num; i++) { + if (!exclude_col_edge) { + cm_ave_cur = (p + i)->cm_ave; + cm_min_cur = (p + i)->cm_min; + cm_max_cur = (p + i)->cm_max; + if (i < (row_num-1)) + cm_ave_next = (p + i+1)->cm_ave; + if (i > 0) + cm_ave_prev = (p + i-1)->cm_ave; + } else { + cm_ave_cur = (p + i)->cm_ave_exclude_edge; + cm_min_cur = (p + i)->cm_min_exclude_edge; + cm_max_cur = (p + i)->cm_max_exclude_edge; + if (i < (row_num-1)) + cm_ave_next = + (p + i+1)->cm_ave_exclude_edge; + if (i > 0) + cm_ave_prev = + (p + i-1)->cm_ave_exclude_edge; + } + + if (cm_ave_cur == 0) + cm_ave_cur = 1; + /*multiple 1000 to increate accuracy*/ + if (i <= 1) + (p + i)->gradient_val = (cm_max_cur - cm_min_cur + + ABS(cm_ave_cur - cm_ave_next)) * 1000 / + cm_ave_cur; + else + (p + i)->gradient_val = (cm_max_cur - cm_min_cur + + ABS(cm_ave_cur - cm_ave_prev)) * 1000 / + cm_ave_cur; + } + } +} + +/******************************************************************************* + * FUNCTION: calculate_gradient_col + * + * SUMMARY: Calculates gradient value for columns. + * + * PARAMETERS: + * *gd_sensor_row_head - pointer to gd_sensor structure + * col_num - number of column + * exclude_row_edge - flag to exclude row edge(1:exclude; 0:include) + * exclude_col_edge - flag to exclude column edge(1:exclude; 0:include) + ******************************************************************************/ +static void calculate_gradient_col(struct gd_sensor *gd_sensor_row_head, + uint16_t col_num, int exclude_row_edge, int exclude_col_edge) +{ + int i = 0; + int32_t cm_min_cur = 0; + int32_t cm_max_cur = 0; + int32_t cm_ave_cur = 0; + int32_t cm_ave_next = 0; + int32_t cm_ave_prev = 0; + struct gd_sensor *p = gd_sensor_row_head; + + if (!exclude_col_edge) { + for (i = 0; i < col_num; i++) { + if (!exclude_row_edge) { + cm_ave_cur = (p + i)->cm_ave; + cm_min_cur = (p + i)->cm_min; + cm_max_cur = (p + i)->cm_max; + if (i < (col_num-1)) + cm_ave_next = (p + i+1)->cm_ave; + if (i > 0) + cm_ave_prev = (p + i-1)->cm_ave; + } else { + cm_ave_cur = (p + i)->cm_ave_exclude_edge; + cm_min_cur = (p + i)->cm_min_exclude_edge; + cm_max_cur = (p + i)->cm_max_exclude_edge; + if (i < (col_num-1)) + cm_ave_next = + (p + i+1)->cm_ave_exclude_edge; + if (i > 0) + cm_ave_prev = + (p + i-1)->cm_ave_exclude_edge; + } + if (cm_ave_cur == 0) + cm_ave_cur = 1; + /*multiple 1000 to increate accuracy*/ + if (i <= 1) + (p + i)->gradient_val = (cm_max_cur - cm_min_cur + + ABS(cm_ave_cur - cm_ave_next)) * 1000 / + cm_ave_cur; + else + (p + i)->gradient_val = (cm_max_cur - cm_min_cur + + ABS(cm_ave_cur - cm_ave_prev)) * 1000 / + cm_ave_cur; + } + } else if (exclude_col_edge) { + for (i = 0; i < col_num; i++) { + if (!exclude_row_edge) { + cm_ave_cur = (p + i)->cm_ave; + cm_min_cur = (p + i)->cm_min; + cm_max_cur = (p + i)->cm_max; + if (i < (col_num-1)) + cm_ave_next = (p + i+1)->cm_ave; + if (i > 0) + cm_ave_prev = (p + i-1)->cm_ave; + } else { + cm_ave_cur = (p + i)->cm_ave_exclude_edge; + cm_min_cur = (p + i)->cm_min_exclude_edge; + cm_max_cur = (p + i)->cm_max_exclude_edge; + if (i < (col_num-1)) + cm_ave_next = + (p + i+1)->cm_ave_exclude_edge; + if (i > 0) + cm_ave_prev = + (p + i-1)->cm_ave_exclude_edge; + } + + if (cm_ave_cur == 0) + cm_ave_cur = 1; + /*multiple 1000 to increate accuracy*/ + if ((i == 0) || (i == (col_num - 1))) + (p + i)->gradient_val = + (cm_max_cur - cm_min_cur) * 1000 / + cm_ave_cur; + else if (i == 1) + (p + i)->gradient_val = + (cm_max_cur - cm_min_cur + + ABS(cm_ave_cur - cm_ave_next)) + * 1000 / cm_ave_cur; + else + (p + i)->gradient_val = + (cm_max_cur - cm_min_cur + + ABS(cm_ave_cur - cm_ave_prev)) + * 1000 / cm_ave_cur; + } + } +} + +/******************************************************************************* + * FUNCTION: fill_gd_sensor_table + * + * SUMMARY: Fills cm calculation result and exclude parameter to gd_sensor + * structure. + * + * PARAMETERS: + * *head - pointer to gd_sensor structure + * index - index of row or column + * cm_max - maximum of cm + * cm_min - minmum of cm + * cm_ave - average of cm + * cm_max_exclude_edge - maximum of cm without edge data + * cm_min_exclude_edge - minmum of cm without edge data + * cm_ave_exclude_edge - average of cm without edge data + ******************************************************************************/ +static void fill_gd_sensor_table(struct gd_sensor *head, int32_t index, + int32_t cm_max, int32_t cm_min, int32_t cm_ave, + int32_t cm_max_exclude_edge, int32_t cm_min_exclude_edge, + int32_t cm_ave_exclude_edge) +{ + (head + index)->cm_max = cm_max; + (head + index)->cm_min = cm_min; + (head + index)->cm_ave = cm_ave; + (head + index)->cm_ave_exclude_edge = cm_ave_exclude_edge; + (head + index)->cm_max_exclude_edge = cm_max_exclude_edge; + (head + index)->cm_min_exclude_edge = cm_min_exclude_edge; +} + +/******************************************************************************* + * FUNCTION: calculate_gd_info + * + * SUMMARY: Calculates gradient panel sensor column and row by calling + * function calculate_gradient_col() & calculate_gradient_row(). + * + * PARAMETERS: + * *head - pointer to gd_sensor structure + * index - index of row or column + * cm_max - maximum of cm + * cm_min - minmum of cm + * cm_ave - average of cm + * cm_max_exclude_edge - maximum of cm without edge data + * cm_min_exclude_edge - minmum of cm without edge data + * cm_ave_exclude_edge - average of cm without edge data + ******************************************************************************/ +static void calculate_gd_info(struct gd_sensor *gd_sensor_col, + struct gd_sensor *gd_sensor_row, int tx_num, int rx_num, + int32_t *cm_sensor_data, int cm_excluding_row_edge, + int cm_excluding_col_edge) +{ + int32_t cm_max; + int32_t cm_min; + int32_t cm_ave; + int32_t cm_max_exclude_edge; + int32_t cm_min_exclude_edge; + int32_t cm_ave_exclude_edge; + int32_t cm_data; + int i; + int j; + + /*calculate all the gradient related info for column*/ + for (i = 0; i < tx_num; i++) { + /*re-initialize for a new col*/ + cm_max = cm_sensor_data[i * rx_num]; + cm_min = cm_max; + cm_ave = 0; + cm_max_exclude_edge = cm_sensor_data[i * rx_num + 1]; + cm_min_exclude_edge = cm_max_exclude_edge; + cm_ave_exclude_edge = 0; + + for (j = 0; j < rx_num; j++) { + cm_data = cm_sensor_data[i * rx_num + j]; + if (cm_data > cm_max) + cm_max = cm_data; + if (cm_data < cm_min) + cm_min = cm_data; + cm_ave += cm_data; + /*calculate exclude edge data*/ + if ((j > 0) && (j < (rx_num-1))) { + if (cm_data > cm_max_exclude_edge) + cm_max_exclude_edge = cm_data; + if (cm_data < cm_min_exclude_edge) + cm_min_exclude_edge = cm_data; + cm_ave_exclude_edge += cm_data; + } + } + cm_ave /= rx_num; + cm_ave_exclude_edge /= (rx_num-2); + fill_gd_sensor_table(gd_sensor_col, i, cm_max, cm_min, cm_ave, + cm_max_exclude_edge, cm_min_exclude_edge, cm_ave_exclude_edge); + } + + calculate_gradient_col(gd_sensor_col, tx_num, cm_excluding_row_edge, + cm_excluding_col_edge); + + /*calculate all the gradient related info for row*/ + for (j = 0; j < rx_num; j++) { + /*re-initialize for a new row*/ + cm_max = cm_sensor_data[j]; + cm_min = cm_max; + cm_ave = 0; + cm_max_exclude_edge = cm_sensor_data[rx_num + j]; + cm_min_exclude_edge = cm_max_exclude_edge; + cm_ave_exclude_edge = 0; + for (i = 0; i < tx_num; i++) { + cm_data = cm_sensor_data[i * rx_num + j]; + if (cm_data > cm_max) + cm_max = cm_data; + if (cm_data < cm_min) + cm_min = cm_data; + cm_ave += cm_data; + /*calculate exclude edge data*/ + if ((i > 0) && (i < (tx_num-1))) { + if (cm_data > cm_max_exclude_edge) + cm_max_exclude_edge = cm_data; + if (cm_data < cm_min_exclude_edge) + cm_min_exclude_edge = cm_data; + cm_ave_exclude_edge += cm_data; + } + } + cm_ave /= tx_num; + cm_ave_exclude_edge /= (tx_num-2); + fill_gd_sensor_table(gd_sensor_row, j, cm_max, cm_min, cm_ave, + cm_max_exclude_edge, cm_min_exclude_edge, cm_ave_exclude_edge); + } + calculate_gradient_row(gd_sensor_row, rx_num, cm_excluding_row_edge, + cm_excluding_col_edge); +} + +/******************************************************************************* + * FUNCTION: pt_get_cmcp_info + * + * SUMMARY: Function to include following work: + * 1) run short test and get result + * 2) run selftest to get cm_panel data, cm_cal_data_panel data, calculate + * cm_ave_data_panel, cm_sensor_delta and gradient by column and row. + * 3) run selftest to get cp_panel data, cp_cal_data_panel data, cacluate + * cp_ave_data_panel, cp_sensor_delta for tx and rx. + * 4) run selftest to get cm_btn data, cm_cal_data_btn data, cacluate + * cm_delta_data_btn data, cm_ave_data_btn data. + * 5) run selftest to get cp_btn data, cp_btn_cal data, cacluate + * cp_button_delta data, cp_button_ave data. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dad - pointer to pt_device_access_data structure + * *cmcp_info - pointer to cmcp_data structure + ******************************************************************************/ +static int pt_get_cmcp_info(struct pt_device_access_data *dad, + struct cmcp_data *cmcp_info) +{ + struct device *dev; + int32_t *cm_data_panel = cmcp_info->cm_data_panel; + int32_t *cp_tx_data_panel = cmcp_info->cp_tx_data_panel; + int32_t *cp_rx_data_panel = cmcp_info->cp_rx_data_panel; + int32_t *cp_tx_cal_data_panel = cmcp_info->cp_tx_cal_data_panel; + int32_t *cp_rx_cal_data_panel = cmcp_info->cp_rx_cal_data_panel; + int32_t *cm_btn_data = cmcp_info->cm_btn_data; + int32_t *cp_btn_data = cmcp_info->cp_btn_data; + struct gd_sensor *gd_sensor_col = cmcp_info->gd_sensor_col; + struct gd_sensor *gd_sensor_row = cmcp_info->gd_sensor_row; + struct result *result = dad->result; + int32_t cp_btn_cal = 0; + int32_t cp_btn_ave = 0; + int32_t cm_ave_data_panel = 0; + int32_t cm_ave_data_btn = 0; + int32_t cp_tx_ave_data_panel = 0; + int32_t cp_rx_ave_data_panel = 0; + u8 tmp_buf[3]; + int tx_num; + int rx_num; + int btn_num; + int rc = 0; + int i; + + dev = dad->dev; + cmcp_info->tx_num = dad->si->sensing_conf_data.tx_num; + cmcp_info->rx_num = dad->si->sensing_conf_data.rx_num; + cmcp_info->btn_num = dad->si->num_btns; + + tx_num = cmcp_info->tx_num; + rx_num = cmcp_info->rx_num; + btn_num = cmcp_info->btn_num; + pt_debug(dev, DL_INFO, "%s tx_num=%d", __func__, tx_num); + pt_debug(dev, DL_INFO, "%s rx_num=%d", __func__, rx_num); + pt_debug(dev, DL_INFO, "%s btn_num=%d", __func__, btn_num); + + /*short test*/ + result->short_test_pass = true; + rc = pt_run_and_get_selftest_result(dev, PT_CORE_CMD_UNPROTECTED, + tmp_buf, sizeof(tmp_buf), + PT_ST_ID_AUTOSHORTS, PIP_CMD_MAX_LENGTH, + PT_ST_DONT_GET_RESULTS, PT_ST_NOPRINT, PT_PR_FORMAT_DEFAULT); + if (rc) { + pt_debug(dev, DL_ERROR, "short test not supported"); + goto exit; + } + if (dad->ic_buf[1] != 0) + result->short_test_pass = false; + + /*Get cm_panel data*/ + rc = pt_run_and_get_selftest_result(dev, PT_CORE_CMD_UNPROTECTED, + tmp_buf, sizeof(tmp_buf), + PT_ST_ID_CM_PANEL, PIP_CMD_MAX_LENGTH, + PT_ST_GET_RESULTS, PT_ST_NOPRINT, PT_PR_FORMAT_DEFAULT); + if (rc) { + pt_debug(dev, DL_ERROR, "Get CM Panel not supported"); + goto exit; + } + if (cm_data_panel != NULL) { + for (i = 0; i < tx_num * rx_num; i++) { + cm_data_panel[i] = + 10*(dad->ic_buf[CM_PANEL_DATA_OFFSET+i*2] + 256 + * dad->ic_buf[CM_PANEL_DATA_OFFSET+i*2+1]); + pt_debug(dev, DL_DEBUG, + "cm_data_panel[%d]=%d\n", + i, cm_data_panel[i]); + cm_ave_data_panel += cm_data_panel[i]; + } + cm_ave_data_panel /= (tx_num * rx_num); + cmcp_info->cm_ave_data_panel = cm_ave_data_panel; + + /* Calculate gradient panel sensor column/row here */ + calculate_gd_info(gd_sensor_col, gd_sensor_row, tx_num, rx_num, + cm_data_panel, 1, 1); + for (i = 0; i < tx_num; i++) { + pt_debug(dev, DL_DEBUG, + "i=%d max=%d,min=%d,ave=%d, gradient=%d", i, + gd_sensor_col[i].cm_max, + gd_sensor_col[i].cm_min, + gd_sensor_col[i].cm_ave, + gd_sensor_col[i].gradient_val); + } + + for (i = 0; i < rx_num; i++) { + pt_debug(dev, DL_DEBUG, + "i=%d max=%d,min=%d,ave=%d, gradient=%d", i, + gd_sensor_row[i].cm_max, + gd_sensor_row[i].cm_min, + gd_sensor_row[i].cm_ave, + gd_sensor_row[i].gradient_val); + } + } + + /*Get cp data*/ + rc = pt_run_and_get_selftest_result(dev, PT_CORE_CMD_UNPROTECTED, + tmp_buf, sizeof(tmp_buf), + PT_ST_ID_CP_PANEL, PIP_CMD_MAX_LENGTH, + PT_ST_GET_RESULTS, PT_ST_NOPRINT, PT_PR_FORMAT_DEFAULT); + if (rc) { + pt_debug(dev, DL_ERROR, "Get CP Panel not supported"); + goto exit; + } + /*Get cp_tx_data_panel*/ + if (cp_tx_data_panel != NULL) { + for (i = 0; i < tx_num; i++) { + cp_tx_data_panel[i] = + 10*(dad->ic_buf[CP_PANEL_DATA_OFFSET+i*2] + + 256 * dad->ic_buf[CP_PANEL_DATA_OFFSET+i*2+1]); + pt_debug(dev, DL_DEBUG, + "cp_tx_data_panel[%d]=%d\n", + i, cp_tx_data_panel[i]); + cp_tx_ave_data_panel += cp_tx_data_panel[i]; + } + cp_tx_ave_data_panel /= tx_num; + cmcp_info->cp_tx_ave_data_panel = cp_tx_ave_data_panel; + } + + /*Get cp_tx_cal_data_panel*/ + if (cp_tx_cal_data_panel != NULL) { + for (i = 0; i < tx_num; i++) { + cp_tx_cal_data_panel[i] = + 10*(dad->ic_buf[CP_PANEL_DATA_OFFSET+tx_num*2+i*2] + + 256 * dad->ic_buf[CP_PANEL_DATA_OFFSET+tx_num*2+i*2+1]); + pt_debug(dev, DL_DEBUG, "cp_tx_cal_data_panel[%d]=%d\n", + i, cp_tx_cal_data_panel[i]); + } + + /*get cp_sensor_tx_delta,using the first sensor cal value for temp */ + /*multiple 1000 to increase accuracy*/ + cmcp_info->cp_sensor_tx_delta = ABS((cp_tx_cal_data_panel[0] + - cp_tx_ave_data_panel) * 1000 / cp_tx_ave_data_panel); + } + + /*Get cp_rx_data_panel*/ + if (cp_rx_data_panel != NULL) { + for (i = 0; i < rx_num; i++) { + cp_rx_data_panel[i] = + 10*(dad->ic_buf[CP_PANEL_DATA_OFFSET+tx_num*4+i*2] + + 256 * dad->ic_buf[CP_PANEL_DATA_OFFSET+tx_num*4+i*2+1]); + pt_debug(dev, DL_DEBUG, + "cp_rx_data_panel[%d]=%d\n", i, + cp_rx_data_panel[i]); + cp_rx_ave_data_panel += cp_rx_data_panel[i]; + } + cp_rx_ave_data_panel /= rx_num; + cmcp_info->cp_rx_ave_data_panel = cp_rx_ave_data_panel; + } + + /*Get cp_rx_cal_data_panel*/ + if (cp_rx_cal_data_panel != NULL) { + for (i = 0; i < rx_num; i++) { + cp_rx_cal_data_panel[i] = + 10 * (dad->ic_buf[CP_PANEL_DATA_OFFSET+tx_num*4+rx_num*2+i*2] + + 256 * + dad->ic_buf[CP_PANEL_DATA_OFFSET+tx_num*4+rx_num*2+i*2+1]); + pt_debug(dev, DL_DEBUG, + "cp_rx_cal_data_panel[%d]=%d\n", i, + cp_rx_cal_data_panel[i]); + } + + /*get cp_sensor_rx_delta,using the first sensor cal value for temp */ + /*multiple 1000 to increase accuracy*/ + cmcp_info->cp_sensor_rx_delta = ABS((cp_rx_cal_data_panel[0] + - cp_rx_ave_data_panel) * 1000 / cp_rx_ave_data_panel); + } + + if (btn_num == 0) { + pt_debug(dev, DL_INFO, "%s: Skip Button Test\n", __func__); + goto skip_button_test; + } + + /*get cm btn data*/ + rc = pt_run_and_get_selftest_result(dev, PT_CORE_CMD_UNPROTECTED, + tmp_buf, sizeof(tmp_buf), + PT_ST_ID_CM_BUTTON, PIP_CMD_MAX_LENGTH, + PT_ST_GET_RESULTS, PT_ST_NOPRINT, PT_PR_FORMAT_DEFAULT); + if (rc) { + pt_debug(dev, DL_ERROR, "Get CM BTN not supported"); + goto exit; + } + if (cm_btn_data != NULL) { + for (i = 0; i < btn_num; i++) { + cm_btn_data[i] = + 10 * (dad->ic_buf[CM_BTN_DATA_OFFSET+i*2] + + 256 * dad->ic_buf[CM_BTN_DATA_OFFSET+i*2+1]); + pt_debug(dev, DL_DEBUG, + " cm_btn_data[%d]=%d\n", + i, cm_btn_data[i]); + cm_ave_data_btn += cm_btn_data[i]; + } + cm_ave_data_btn /= btn_num; + cmcp_info->cm_ave_data_btn = cm_ave_data_btn; + } + + /*get cp btn data*/ + rc = pt_run_and_get_selftest_result(dev, PT_CORE_CMD_UNPROTECTED, + tmp_buf, sizeof(tmp_buf), + PT_ST_ID_CP_BUTTON, PIP_CMD_MAX_LENGTH, + PT_ST_GET_RESULTS, PT_ST_NOPRINT, PT_PR_FORMAT_DEFAULT); + if (rc) { + pt_debug(dev, DL_ERROR, "Get CP BTN not supported"); + goto exit; + } + if (cp_btn_data != NULL) { + for (i = 0; i < btn_num; i++) { + cp_btn_data[i] = + 10 * (dad->ic_buf[CP_BTN_DATA_OFFSET+i*2] + + 256 * dad->ic_buf[CP_BTN_DATA_OFFSET+i*2+1]); + cp_btn_ave += cp_btn_data[i]; + pt_debug(dev, DL_DEBUG, + "cp_btn_data[%d]=%d\n", + i, cp_btn_data[i]); + } + cp_btn_ave /= btn_num; + cp_btn_cal = 10*(dad->ic_buf[CP_BTN_DATA_OFFSET+i*2] + + 256 * dad->ic_buf[CP_BTN_DATA_OFFSET+i*2+1]); + cmcp_info->cp_button_ave = cp_btn_ave; + cmcp_info->cp_btn_cal = cp_btn_cal; + /*multiple 1000 to increase accuracy*/ + cmcp_info->cp_button_delta = ABS((cp_btn_cal + - cp_btn_ave) * 1000 / cp_btn_ave); + pt_debug(dev, DL_INFO, " cp_btn_cal=%d\n", + cp_btn_cal); + pt_debug(dev, DL_INFO, " cp_btn_ave=%d\n", + cp_btn_ave); + } + +skip_button_test: +exit: + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_get_cm_cal + * + * SUMMARY: Function to include following work: + * 1) run selftest to get cm_cal_data_panel, cm_sensor_delta + * 2) run selftest to get cm_cal_data_btn, cm_delta_data_btn + * + * NOTE: + * This function depends on the calculation result of pt_get_cmcp_info() + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dad - pointer to pt_device_access_data structure + * *cmcp_info - pointer to cmcp_data structure + ******************************************************************************/ +static int pt_get_cm_cal(struct pt_device_access_data *dad, + struct cmcp_data *cmcp_info) +{ + struct device *dev; + int32_t *cm_data_panel = cmcp_info->cm_data_panel; + int32_t *cm_btn_data = cmcp_info->cm_btn_data; + u8 tmp_buf[3]; + int rc = 0; + int i; + + dev = dad->dev; + + /*Get cm_cal data*/ + rc = pt_run_and_get_selftest_result(dev, PT_CORE_CMD_UNPROTECTED, + tmp_buf, sizeof(tmp_buf), + PT_ST_ID_CM_PANEL, PIP_CMD_MAX_LENGTH, + PT_ST_GET_RESULTS, PT_ST_NOPRINT, PT_PR_FORMAT_DEFAULT); + if (rc) { + pt_debug(dev, DL_ERROR, "Get CM Panel not supported"); + goto exit; + } + if (cm_data_panel != NULL) { + i = cmcp_info->tx_num * cmcp_info->rx_num; + cmcp_info->cm_cal_data_panel = + 10 * (dad->ic_buf[CM_PANEL_DATA_OFFSET + i * 2] + + 256 * dad->ic_buf[CM_PANEL_DATA_OFFSET + i * 2 + 1]); + /*multiple 1000 to increase accuracy*/ + cmcp_info->cm_sensor_delta = + ABS((cmcp_info->cm_ave_data_panel - + cmcp_info->cm_cal_data_panel) * + 1000 / cmcp_info->cm_ave_data_panel); + } + + if (cmcp_info->btn_num == 0) { + pt_debug(dev, DL_INFO, "%s: Skip Button Test\n", __func__); + goto skip_button_test; + } + + /*get cm_btn_cal data*/ + rc = pt_run_and_get_selftest_result(dev, PT_CORE_CMD_UNPROTECTED, + tmp_buf, sizeof(tmp_buf), + PT_ST_ID_CM_BUTTON, PIP_CMD_MAX_LENGTH, + PT_ST_GET_RESULTS, PT_ST_NOPRINT, PT_PR_FORMAT_DEFAULT); + if (rc) { + pt_debug(dev, DL_ERROR, "Get CM BTN not supported"); + goto exit; + } + if (cm_btn_data != NULL) { + i = cmcp_info->btn_num; + cmcp_info->cm_cal_data_btn = + 10 * (dad->ic_buf[CM_BTN_DATA_OFFSET + i * 2] + + 256 * dad->ic_buf[CM_BTN_DATA_OFFSET + i * 2 + 1]); + /*multiple 1000 to increase accuracy*/ + cmcp_info->cm_delta_data_btn = ABS( + (cmcp_info->cm_ave_data_btn - cmcp_info->cm_cal_data_btn) * + 1000 / cmcp_info->cm_ave_data_btn); + pt_debug(dev, DL_INFO, " cm_btn_cal=%d\n", + cmcp_info->cm_cal_data_btn); + } + +skip_button_test: +exit: + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_free_cmcp_buf + * + * SUMMARY: Free pointers in cmcp_data structure + * + * PARAMETERS: + * *cmcp_info - pointer to cmcp_data structure + ******************************************************************************/ +static void pt_free_cmcp_buf(struct cmcp_data *cmcp_info) +{ + if (cmcp_info->gd_sensor_col != NULL) + kfree(cmcp_info->gd_sensor_col); + if (cmcp_info->gd_sensor_row != NULL) + kfree(cmcp_info->gd_sensor_row); + if (cmcp_info->cm_data_panel != NULL) + kfree(cmcp_info->cm_data_panel); + if (cmcp_info->cp_tx_data_panel != NULL) + kfree(cmcp_info->cp_tx_data_panel); + if (cmcp_info->cp_rx_data_panel != NULL) + kfree(cmcp_info->cp_rx_data_panel); + if (cmcp_info->cp_tx_cal_data_panel != NULL) + kfree(cmcp_info->cp_tx_cal_data_panel); + if (cmcp_info->cp_rx_cal_data_panel != NULL) + kfree(cmcp_info->cp_rx_cal_data_panel); + if (cmcp_info->cm_btn_data != NULL) + kfree(cmcp_info->cm_btn_data); + if (cmcp_info->cp_btn_data != NULL) + kfree(cmcp_info->cp_btn_data); + if (cmcp_info->cm_sensor_column_delta != NULL) + kfree(cmcp_info->cm_sensor_column_delta); + if (cmcp_info->cm_sensor_row_delta != NULL) + kfree(cmcp_info->cm_sensor_row_delta); +} + +/******************************************************************************* + * FUNCTION: pt_cmcp_get_test_item + * + * SUMMARY: Parses enum cmcp_test_item to integer value test_item as bitwise + * type. + * + * RETURN: integer value to indidate available test item with bitwise type + * + * PARAMETERS: + * item_input - enum cmcp_test_item + ******************************************************************************/ +static int pt_cmcp_get_test_item(int item_input) +{ + int test_item = 0; + + switch (item_input) { + case CMCP_FULL: + test_item = CMCP_FULL_CASE; + break; + case CMCP_CM_PANEL: + test_item = CM_PANEL; + break; + case CMCP_CP_PANEL: + test_item = CP_PANEL; + break; + case CMCP_CM_BTN: + test_item = CM_BTN; + break; + case CMCP_CP_BTN: + test_item = CP_BTN; + break; + } + return test_item; +} + +/******************************************************************************* + * FUNCTION: pt_cmcp_test_show + * + * SUMMARY: Show method for cmcp_test sysfs node. Allows to perform cmcp test + * with following steps: + * 1) Get cmcp test items which from threhold file + * 2) Check whether cmcp test items match with firmware + * 3) Set parameter to force single TX + * 4) Do calibration if requested + * 5) Get all cmcp data from FW and do calculation + * 6) Set parameter to restore to multi tx + * 7) Do calibration if requested + * 8) Check scan state,try to fix if it is not right + * 9) Start watchdog + * 10) Validate cm and cp test results if requested + * 11) Fill the test result + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to output buffer + ******************************************************************************/ +static ssize_t pt_cmcp_test_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pt_device_access_data *dad + = pt_get_device_access_data(dev); + struct cmcp_data *cmcp_info = dad->cmcp_info; + struct result *result = dad->result; + struct configuration *configuration = dad->configs; + bool final_pass = true; + static const char * const cmcp_test_case_array[] = {"Full Cm/Cp test", + "Cm panel test", "Cp panel test", + "Cm button test", "Cp button test"}; + int index = 0; + int test_item = 0; + int no_builtin_file = 0; + int rc = 0; + int self_test_result_1 = 0; + int self_test_result_2 = 0; + u8 sys_mode = FW_SYS_MODE_UNDEFINED; + u8 retry = 3; + + dev = dad->dev; + if ((configuration == NULL) || (cmcp_info == NULL)) + goto exit; + + mutex_lock(&dad->sysfs_lock); + + if (dad->cmcp_test_in_progress) { + mutex_unlock(&dad->sysfs_lock); + goto cmcp_not_ready; + } + dad->cmcp_test_in_progress = 1; + + dad->test_executed = 0; + test_item = pt_cmcp_get_test_item(dad->cmcp_test_items); + + if (dad->builtin_cmcp_threshold_status < 0) { + pt_debug(dev, DL_WARN, "%s: No cmcp threshold file.\n", + __func__); + no_builtin_file = 1; + mutex_unlock(&dad->sysfs_lock); + goto start_testing; + } + + if (dad->cmcp_test_items < 0) { + pt_debug(dev, DL_ERROR, + "%s: Invalid test item! Should be 0~4!\n", __func__); + mutex_unlock(&dad->sysfs_lock); + goto invalid_item; + } + + pt_debug(dev, DL_INFO, "%s: Test item is %s, %d\n", + __func__, cmcp_test_case_array[dad->cmcp_test_items], + test_item); + + if ((dad->si->num_btns == 0) + && ((dad->cmcp_test_items == CMCP_CM_BTN) + || (dad->cmcp_test_items == CMCP_CP_BTN))) { + pt_debug(dev, DL_WARN, + "%s: FW doesn't support button!\n", __func__); + mutex_unlock(&dad->sysfs_lock); + goto invalid_item_btn; + } + + mutex_unlock(&dad->sysfs_lock); + + if (cmcp_check_config_fw_match(dev, configuration)) + goto mismatch; + +start_testing: + pt_debug(dev, DL_INFO, "%s: Start Cm/Cp test!\n", __func__); + result->cm_test_pass = true; + result->cp_test_pass = true; + + /*stop watchdog*/ + rc = cmd->request_stop_wd(dev); + if (rc) + pt_debug(dev, DL_ERROR, "stop watchdog failed"); + + /* Make sure the device is awake */ + pm_runtime_get_sync(dev); + /* Resource protect */ + rc = cmd->request_exclusive(dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Error on request exclusive rc = %d\n", + __func__, rc); + } + + /*force single tx*/ + rc = cmd->nonhid_cmd->set_param(dev, + PT_CORE_CMD_UNPROTECTED, 0x1F, 1, 1); + if (rc) + pt_debug(dev, DL_ERROR, "force single tx failed"); + + /*suspend_scanning */ + rc = cmd->nonhid_cmd->suspend_scanning(dev, PT_CORE_CMD_UNPROTECTED); + if (rc) + pt_debug(dev, DL_ERROR, "suspend_scanning failed"); + + /* Do calibration if requested */ + if (!dad->cmcp_force_calibrate) { + pt_debug(dev, DL_INFO, "do calibration in single tx mode"); + rc = pt_perform_calibration(dev); + if (rc) + pt_debug(dev, DL_ERROR, "calibration failed"); + } + /*resume_scanning */ + rc = cmd->nonhid_cmd->resume_scanning(dev, PT_CORE_CMD_UNPROTECTED); + if (rc) + pt_debug(dev, DL_ERROR, "resume_scanning failed"); + + /*get all cmcp data from FW*/ + self_test_result_1 = pt_get_cmcp_info(dad, cmcp_info); + if (self_test_result_1) + pt_debug(dev, DL_ERROR, "pt_get_cmcp_info failed"); + + /*restore to multi tx*/ + rc = cmd->nonhid_cmd->set_param(dev, + PT_CORE_CMD_UNPROTECTED, 0x1F, 0, 1); + if (rc) + pt_debug(dev, DL_ERROR, "restore multi tx failed"); + + /*suspend_scanning */ + rc = cmd->nonhid_cmd->suspend_scanning(dev, 0); + if (rc) + pt_debug(dev, DL_ERROR, "suspend_scanning failed"); + + /* Do calibration if requested */ + if (!dad->cmcp_force_calibrate) { + pt_debug(dev, DL_INFO, "do calibration in multi tx mode"); + rc = pt_perform_calibration(dev); + if (rc) + pt_debug(dev, DL_ERROR, "calibration failed"); + } + /*resume_scanning */ + rc = cmd->nonhid_cmd->resume_scanning(dev, PT_CORE_CMD_UNPROTECTED); + if (rc) + pt_debug(dev, DL_ERROR, "resume_scanning failed"); + + /*get cm cal data from FW*/ + self_test_result_2 = pt_get_cm_cal(dad, cmcp_info); + if (self_test_result_2) + pt_debug(dev, DL_ERROR, "pt_get_cm_cal failed"); + + /* check scan state,try to fix if it is not right*/ + while (retry--) { + rc = cmd->request_get_fw_mode(dev, PT_CORE_CMD_UNPROTECTED, + &sys_mode, NULL); + + if (sys_mode != FW_SYS_MODE_SCANNING) { + pt_debug(dev, DL_ERROR, + "%s: fw mode: %d, retry: %d, rc = %d\n", + __func__, sys_mode, retry, rc); + rc = cmd->nonhid_cmd->resume_scanning(dev, + PT_CORE_CMD_UNPROTECTED); + } + } + + rc = cmd->release_exclusive(dev); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Error on release exclusive rc = %d\n", + __func__, rc); + } + pm_runtime_put(dev); + + /*start watchdog*/ + rc = cmd->request_start_wd(dev); + if (rc) + pt_debug(dev, DL_ERROR, "start watchdog failed"); + + if (self_test_result_1 || self_test_result_2) + goto self_test_failed; + + /* The tests are finished without failure */ + mutex_lock(&dad->sysfs_lock); + dad->test_executed = 1; + mutex_unlock(&dad->sysfs_lock); + + if (no_builtin_file) + goto no_builtin; + + if ((test_item) & (CM_ENABLED)) + validate_cm_test_results(dev, configuration, cmcp_info, + result, &final_pass, test_item); + + if ((test_item) & (CP_ENABLED)) + validate_cp_test_results(dev, configuration, cmcp_info, + result, &final_pass, test_item); + + if ((dad->cmcp_test_items == CMCP_FULL) + && (dad->cmcp_range_check == 0)) { + /*full test and full check*/ + result->test_summary = result->cm_test_pass + && result->cp_test_pass + && result->short_test_pass; + } else if ((dad->cmcp_test_items == CMCP_FULL) + && (dad->cmcp_range_check == 1)) { + /*full test and basic check*/ + result->test_summary = result->cm_sensor_gd_col_pass + && result->cm_sensor_gd_row_pass + && result->cm_sensor_validation_pass + && result->cp_rx_validation_pass + && result->cp_tx_validation_pass + && result->short_test_pass; + } else if (dad->cmcp_test_items == CMCP_CM_PANEL) { + /*cm panel test result only*/ + result->test_summary = result->cm_sensor_gd_col_pass + && result->cm_sensor_gd_row_pass + && result->cm_sensor_validation_pass + && result->cm_sensor_row_delta_pass + && result->cm_sensor_col_delta_pass + && result->cm_sensor_calibration_pass + && result->cm_sensor_delta_pass; + } else if (dad->cmcp_test_items == CMCP_CP_PANEL) { + /*cp panel test result only*/ + result->test_summary = result->cp_sensor_delta_pass + && result->cp_rx_validation_pass + && result->cp_tx_validation_pass; + } else if (dad->cmcp_test_items == CMCP_CM_BTN) { + /*cm button test result only*/ + result->test_summary = result->cm_button_validation_pass + && result->cm_button_delta_pass; + } else if (dad->cmcp_test_items == CMCP_CP_BTN) { + /*cp button test result only*/ + result->test_summary = result->cp_button_delta_pass + && result->cp_button_average_pass + && result->cp_button_validation_pass; + } + + if (result->test_summary) { + pt_debug(dev, DL_INFO, + "%s: Finish Cm/Cp test! All Test Passed\n", __func__); + index = scnprintf(buf, PT_MAX_PRBUF_SIZE, "Status: 1\n"); + } else { + pt_debug(dev, DL_INFO, + "%s: Finish Cm/Cp test! Range Check Failure\n", + __func__); + index = scnprintf(buf, PT_MAX_PRBUF_SIZE, "Status: 6\n"); + } + goto cmcp_ready; + +mismatch: + index = scnprintf(buf, PT_MAX_PRBUF_SIZE, + "Status: 2\nInput cmcp threshold file mismatches with FW\n"); + goto cmcp_ready; +invalid_item_btn: + index = scnprintf(buf, PT_MAX_PRBUF_SIZE, + "Status: 3\nFW doesn't support button!\n"); + goto cmcp_ready; +invalid_item: + index = scnprintf(buf, PT_MAX_PRBUF_SIZE, + "Status: 4\nWrong test item or range check input!\nOnly support below items:\n0 - Cm/Cp Panel & Button with Gradient (Typical)\n1 - Cm Panel with Gradient\n2 - Cp Panel\n3 - Cm Button\n4 - Cp Button\nOnly support below range check:\n0 - Full Range Checking (default)\n1 - Basic Range Checking(TSG5 style)\n"); + goto cmcp_ready; +self_test_failed: + index = scnprintf(buf, PT_MAX_PRBUF_SIZE, + "Status: 5\nget self test ID not supported!\n"); + goto cmcp_ready; +cmcp_not_ready: + index = scnprintf(buf, PT_MAX_PRBUF_SIZE, "Status: 0\n"); + goto cmcp_ready; +no_builtin: + index = scnprintf(buf, PT_MAX_PRBUF_SIZE, + "Status: 7\nNo cmcp threshold file!\n"); +cmcp_ready: + mutex_lock(&dad->sysfs_lock); + dad->cmcp_test_in_progress = 0; + mutex_unlock(&dad->sysfs_lock); +exit: + return index; +} + +/******************************************************************************* + * FUNCTION: pt_cmcp_test_store + * + * SUMMARY: The store method for cmcp_test sysfs node.Allows the user to + * configure which cm/cp tests will be executed on the "cat" of this node. + * + * RETURN: Size of passed in buffer + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to buffer that hold the command parameters + * size - size of buf + ******************************************************************************/ +static ssize_t pt_cmcp_test_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct pt_device_access_data *dad + = pt_get_device_access_data(dev); + u8 test_item = 0; + u8 range_check = 0; + u8 force_calibrate = 0; + u32 input_data[4]; + int ret = 0; + static const char * const cmcp_test_case_array[] = {"Full Cm/Cp test", + "Cm panel test", "Cp panel test", + "Cm button test", "Cp button test"}; + static const char * const cmcp_test_range_check_array[] = { + "Full (default)", "Basic"}; + static const char * const cmcp_test_force_cal_array[] = { + "Calibrate When Testing (default)", "No Calibration"}; + ssize_t length = 0; + + pm_runtime_get_sync(dev); + mutex_lock(&dad->sysfs_lock); + + length = cmd->parse_sysfs_input(dev, buf, size, input_data, + ARRAY_SIZE(input_data)); + if (length <= 0 || length > 3) { + pt_debug(dev, DL_ERROR, "%s: Input format error!\n", + __func__); + dad->cmcp_test_items = -EINVAL; + ret = -EINVAL; + goto error; + } + + /* Get test item */ + test_item = input_data[0]; + /* Get range check */ + if (length >= 2) + range_check = input_data[1]; + /* Get force calibration */ + if (length == 3) + force_calibrate = input_data[2]; + + /* + * Test item limitation: + * 0: Perform all Tests + * 1: CM Panel with Gradient + * 2: CP Panel + * 3: CM Button + * 4: CP Button + * Ranage check limitation: + * 0: full check + * 1: basic check + * Force calibrate limitation: + * 0: do calibration + * 1: don't do calibration + */ + if ((test_item < 0) || (test_item > 4) || (range_check > 1) + || (force_calibrate > 1)) { + pt_debug(dev, DL_ERROR, + "%s: Test item should be 0~4; Range check should be 0~1; Force calibrate should be 0~1\n", + __func__); + dad->cmcp_test_items = -EINVAL; + ret = -EINVAL; + goto error; + } + /* + * If it is not all Test, then range_check should be 0 + * because other test does not has concept of basic check + */ + if (test_item > 0 && test_item < 5) + range_check = 0; + + dad->cmcp_test_items = test_item; + dad->cmcp_range_check = range_check; + dad->cmcp_force_calibrate = force_calibrate; + pt_debug(dev, DL_INFO, + "%s: Test item=%s; Range check=%s; Force cal=%s.\n", + __func__, + cmcp_test_case_array[test_item], + cmcp_test_range_check_array[range_check], + cmcp_test_force_cal_array[force_calibrate]); + +error: + mutex_unlock(&dad->sysfs_lock); + pm_runtime_put(dev); + + if (ret) + return ret; + + return size; +} + +static DEVICE_ATTR(cmcp_test, 0600, + pt_cmcp_test_show, pt_cmcp_test_store); + +/******************************************************************************* + * FUNCTION: prepare_print_string + * + * SUMMARY: Formats input buffer to out buffer with string type,and increases + * the index by size of formated data. + * + * RETURN: + * index plus with size of formated data + * + * PARAMETERS: + * *out_buf - output buffer to store formated data + * *in_buf - input buffer to be formated + * index - index in output buffer for appending content + ******************************************************************************/ +int prepare_print_string(char *out_buf, char *in_buf, int index) +{ + if ((out_buf == NULL) || (in_buf == NULL)) + return index; + index += scnprintf(&out_buf[index], MAX_BUF_LEN - index, + "%s", in_buf); + return index; +} + +/******************************************************************************* + * FUNCTION: prepare_print_string + * + * SUMMARY: Formats input buffer to out buffer with decimal base,and increases + * the index by size of formated data. + * + * RETURN: + * index plus with size of formated data + * + * PARAMETERS: + * *out_buf - output buffer to store formated data + * *in_buf - input buffer to be formated + * index - index in output buffer for appending content + * data_num - data number in input buffer + ******************************************************************************/ +int prepare_print_data(char *out_buf, int32_t *in_buf, int index, int data_num) +{ + int i; + + if ((out_buf == NULL) || (in_buf == NULL)) + return index; + for (i = 0; i < data_num; i++) + index += scnprintf(&out_buf[index], MAX_BUF_LEN - index, + "%d,", in_buf[i]); + return index; +} + +/******************************************************************************* + * FUNCTION: save_header + * + * SUMMARY: Appends "header" for cmcp test result to output buffer. + * + * RETURN: + * index plus with size of formated data + * + * PARAMETERS: + * *out_buf - output buffer to store formated data + * index - index in output buffer for appending content + * *result - pointer to result structure + ******************************************************************************/ +static int save_header(char *out_buf, int index, struct result *result) +{ + struct rtc_time tm; + char time_buf[100] = {0}; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0)) + struct timespec64 ts; + + ktime_get_real_ts64(&ts); + rtc_time64_to_tm(ts.tv_sec, &tm); +#else + struct timex txc; + + do_gettimeofday(&(txc.time)); + rtc_time_to_tm(txc.time.tv_sec, &tm); +#endif + + scnprintf(time_buf, 100, "%d/%d/%d,TIME,%d:%d:%d,", tm.tm_year+1900, + tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); + + index = prepare_print_string(out_buf, ",.header,\n", index); + index = prepare_print_string(out_buf, ",DATE,", index); + index = prepare_print_string(out_buf, &time_buf[0], index); + index = prepare_print_string(out_buf, ",\n", index); + index = prepare_print_string(out_buf, ",SW_VERSION,", index); + index = prepare_print_string(out_buf, PT_DRIVER_VERSION, index); + index = prepare_print_string(out_buf, ",\n", index); + index = prepare_print_string(out_buf, ",.end,\n", index); + index = prepare_print_string(out_buf, ",.engineering data,\n", index); + + return index; +} + +/******************************************************************************* + * FUNCTION: print_silicon_id + * + * SUMMARY: Formats input buffer(silicon id) to out buffer with + * string type,and increases the index by size of formated data. + * + * RETURN: + * index plus with size of formated data + * + * PARAMETERS: + * *out_buf - output buffer to store formated data + * *in_buf - input buffer to be formated + * index - index in output buffer for appending content + ******************************************************************************/ +static int print_silicon_id(char *out_buf, char *in_buf, int index) +{ + index = prepare_print_string(out_buf, ",1,", index); + index = prepare_print_string(out_buf, &in_buf[0], index); + return index; +} + +/******************************************************************************* + * FUNCTION: save_engineering_data + * + * SUMMARY: Generates cmcp test result with *.csv format to output buffer, but + * it doesn't include the header. + * + * RETURN: + * index plus with size of formated data + * + * PARAMETERS: + * *dev - pointer to device structure + * *out_buf - output buffer to store formated data + * index - index in output buffer for appending content + * *cmcp_info - pointer to cmcp_data structure + * *configuration - pointer to configuration structure + * *result - pointer to result structure + * test_item - test control in bitwise + * no_builtin_file - flag to determin if builtin-file exist + ******************************************************************************/ +int save_engineering_data(struct device *dev, char *out_buf, int index, + struct cmcp_data *cmcp_info, struct configuration *configuration, + struct result *result, int test_item, int no_builtin_file) +{ + int i; + int j; + int tx_num = 0; + int rx_num = 0; + int btn_num = 0; + int tmp = 0; + uint32_t fw_revision_control; + uint32_t fw_config_ver; + char device_id[20] = {0}; + struct pt_device_access_data *dad + = pt_get_device_access_data(dev); + + if ((result == NULL) || (cmcp_info == NULL)) + return -EINVAL; + + tx_num = cmcp_info->tx_num; + rx_num = cmcp_info->rx_num; + btn_num = cmcp_info->btn_num; + fw_revision_control = dad->si->ttdata.revctrl; + fw_config_ver = dad->si->ttdata.fw_ver_conf; + /*calculate silicon id*/ + result->device_id_low = 0; + result->device_id_high = 0; + + for (i = 0; i < 4; i++) + result->device_id_low = + (result->device_id_low << 8) + dad->si->ttdata.mfg_id[i]; + + for (i = 4; i < 8; i++) + result->device_id_high = + (result->device_id_high << 8) + dad->si->ttdata.mfg_id[i]; + + scnprintf(device_id, 20, "%x%x", + result->device_id_high, result->device_id_low); + + /*print test summary*/ + index = print_silicon_id(out_buf, &device_id[0], index); + if (result->test_summary) + index = prepare_print_string(out_buf, ",PASS,\n", index); + else + index = prepare_print_string(out_buf, ",FAIL,\n", index); + + /*revision ctrl number*/ + index = print_silicon_id(out_buf, &device_id[0], index); + index = prepare_print_string(out_buf, ",FW revision Control,", index); + index = prepare_print_data(out_buf, &fw_revision_control, index, 1); + index = prepare_print_string(out_buf, "\n", index); + + /*config version*/ + index = print_silicon_id(out_buf, &device_id[0], index); + index = prepare_print_string(out_buf, ",CONFIG_VER,", index); + index = prepare_print_data(out_buf, &fw_config_ver, index, 1); + index = prepare_print_string(out_buf, "\n", index); + + /* Shorts test */ + index = print_silicon_id(out_buf, &device_id[0], index); + if (result->short_test_pass) + index = prepare_print_string(out_buf, ",Shorts,PASS,\n", index); + else + index = prepare_print_string(out_buf, ",Shorts,FAIL,\n", index); + + if ((test_item & CM_ENABLED) == CM_ENABLED) { + /*print BUTNS_CM_DATA_ROW00*/ + if (((test_item & CM_BTN) == CM_BTN) && (btn_num > 0)) { + index = print_silicon_id(out_buf, &device_id[0], index); + index = prepare_print_string(out_buf, + ",Sensor Cm Validation,BUTNS_CM_DATA_ROW00,", + index); + index = prepare_print_data(out_buf, + &cmcp_info->cm_btn_data[0], + index, + btn_num); + index = prepare_print_string(out_buf, "\n", index); + } + + if ((test_item & CM_PANEL) == CM_PANEL) { + /*print CM_DATA_ROW*/ + for (i = 0; i < rx_num; i++) { + index = print_silicon_id(out_buf, &device_id[0], + index); + index = prepare_print_string(out_buf, + ",Sensor Cm Validation,CM_DATA_ROW", + index); + index = prepare_print_data(out_buf, &i, + index, 1); + for (j = 0; j < tx_num; j++) + index = prepare_print_data(out_buf, + &cmcp_info->cm_data_panel[j*rx_num+i], + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + } + + if (!no_builtin_file) { + /*print CM_MAX_GRADIENT_COLS_PERCENT*/ + index = print_silicon_id(out_buf, + &device_id[0], index); + index = prepare_print_string(out_buf, + ",Sensor Cm Validation,CM_MAX_GRADIENT_COLS_PERCENT,", + index); + for (i = 0; i < tx_num; i++) { + char tmp_buf[10] = {0}; + + scnprintf(tmp_buf, 10, "%d.%d,", + cmcp_info->gd_sensor_col[i].gradient_val / 10, + cmcp_info->gd_sensor_col[i].gradient_val % 10); + index = prepare_print_string(out_buf, + &tmp_buf[0], index); + } + index = prepare_print_string(out_buf, + "\n", index); + + /*print CM_MAX_GRADIENT_ROWS_PERCENT*/ + index = print_silicon_id(out_buf, + &device_id[0], index); + index = prepare_print_string(out_buf, + ",Sensor Cm Validation,CM_MAX_GRADIENT_ROWS_PERCENT,", + index); + for (i = 0; i < rx_num; i++) { + char tmp_buf[10] = {0}; + + scnprintf(tmp_buf, 10, "%d.%d,", + cmcp_info->gd_sensor_row[i].gradient_val / 10, + cmcp_info->gd_sensor_row[i].gradient_val % 10); + index = prepare_print_string(out_buf, + &tmp_buf[0], index); + } + index = prepare_print_string(out_buf, + "\n", index); + + if (!dad->cmcp_range_check) { + /*print CM_DELTA_COLUMN*/ + for (i = 0; i < rx_num; i++) { + index = print_silicon_id( + out_buf, + &device_id[0], index); + index = prepare_print_string( + out_buf, + ",Sensor Cm Validation,DELTA_COLUMNS_ROW", + index); + index = prepare_print_data( + out_buf, + &i, index, 1); + index = prepare_print_data( + out_buf, + &tmp, index, 1); + for (j = 1; j < tx_num; j++) + index = prepare_print_data( + out_buf, + &cmcp_info->cm_sensor_column_delta[(j-1)*rx_num+i], + index, 1); + index = prepare_print_string( + out_buf, + "\n", index); + } + + /*print CM_DELTA_ROW*/ + index = print_silicon_id(out_buf, + &device_id[0], + index); + index = prepare_print_string(out_buf, + ",Sensor Cm Validation,DELTA_ROWS_ROW", + index); + index = prepare_print_data(out_buf, + &tmp, index, 1); + for (j = 0; j < tx_num; j++) + index = prepare_print_data( + out_buf, + &tmp, index, 1); + index = prepare_print_string(out_buf, + "\n", index); + + for (i = 1; i < rx_num; i++) { + index = print_silicon_id( + out_buf, + &device_id[0], + index); + index = prepare_print_string( + out_buf, + ",Sensor Cm Validation,DELTA_ROWS_ROW", + index); + index = prepare_print_data( + out_buf, &i, + index, 1); + for (j = 0; j < tx_num; j++) + index = prepare_print_data( + out_buf, + &cmcp_info->cm_sensor_row_delta[j*rx_num+i-1], + index, 1); + index = prepare_print_string( + out_buf, + "\n", index); + } + + /*print pass/fail Sensor Cm Validation*/ + index = print_silicon_id(out_buf, &device_id[0], + index); + if (result->cm_test_pass) + index = prepare_print_string(out_buf, + ",Sensor Cm Validation,PASS,\n", + index); + else + index = prepare_print_string(out_buf, + ",Sensor Cm Validation,FAIL,\n", + index); + } + } + } + + if (!no_builtin_file) { + if (((test_item & CM_BTN) == CM_BTN) && (btn_num > 0) + && (!dad->cmcp_range_check)) { + char tmp_buf[10] = {0}; + /*print Button Element by Element */ + index = print_silicon_id(out_buf, &device_id[0], + index); + if (result->cm_button_validation_pass) + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Button Element by Element,PASS\n", + index); + else + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Button Element by Element,FAIL\n", + index); + + /* + *print Sensor Cm Validation + *- Buttons Range Buttons Range + */ + index = print_silicon_id(out_buf, + &device_id[0], index); + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Buttons Range,Buttons Range,", + index); + scnprintf(tmp_buf, 10, "%d.%d,", + cmcp_info->cm_delta_data_btn / 10, + cmcp_info->cm_delta_data_btn % 10); + index = prepare_print_string(out_buf, + &tmp_buf[0], index); + index = prepare_print_string(out_buf, + "\n", index); + + /*print Sensor Cm Validation + *-Buttons Range Cm_button_avg + */ + index = print_silicon_id(out_buf, + &device_id[0], index); + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Buttons Range,Cm_button_avg,", + index); + index = prepare_print_data(out_buf, + &cmcp_info->cm_ave_data_btn, + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + + /*print Sensor Cm Validation + * -Buttons Range Cm_button_avg + */ + index = print_silicon_id(out_buf, + &device_id[0], index); + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Buttons Range,Cm_button_cal,", + index); + index = prepare_print_data(out_buf, + &cmcp_info->cm_cal_data_btn, + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + + /*print Sensor Cm Validation + *-Buttons Range pass/fail + */ + index = print_silicon_id(out_buf, + &device_id[0], index); + if (result->cm_button_delta_pass) + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Buttons Range,PASS,LIMITS,", + index); + else + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Buttons Range,FAIL,LIMITS,", + index); + index = prepare_print_data(out_buf, + &configuration->cm_max_delta_button_percent, + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + } + + if ((test_item & CM_PANEL) == CM_PANEL && + !dad->cmcp_range_check) { + char tmp_buf[10] = {0}; + /*print Cm_sensor_cal */ + index = print_silicon_id(out_buf, + &device_id[0], index); + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Calibration,Cm_sensor_cal,", + index); + index = prepare_print_data(out_buf, + &cmcp_info->cm_cal_data_panel, + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + + /*print Cm_sensor_cal limit*/ + index = print_silicon_id(out_buf, + &device_id[0], index); + if (result->cm_sensor_calibration_pass) + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Calibration,PASS,LIMITS,", + index); + else + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Calibration,FAIL,LIMITS,", + index); + index = prepare_print_data(out_buf, + &configuration->cm_min_limit_cal, + index, 1); + index = prepare_print_data(out_buf, + &configuration->cm_max_limit_cal, + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + + /*print Columns Delta Matrix*/ + index = print_silicon_id(out_buf, + &device_id[0], index); + if (result->cm_sensor_col_delta_pass) + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Columns Delta Matrix,PASS,LIMITS,", + index); + else + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Columns Delta Matrix,FAIL,LIMITS,", + index); + index = prepare_print_data(out_buf, + &configuration->cm_range_limit_col, + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + + /*print Cm Validation - Element by Element*/ + index = print_silicon_id(out_buf, + &device_id[0], index); + if (result->cm_sensor_validation_pass) + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Element by Element,PASS,", + index); + else + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Element by Element,FAIL,", + index); + index = prepare_print_string(out_buf, + "\n", index); + + /*print Cm Validation -Gradient Cols*/ + index = print_silicon_id(out_buf, + &device_id[0], index); + if (result->cm_sensor_gd_col_pass) + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Gradient Cols,PASS,", + index); + else + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Gradient Cols,FAIL,", + index); + index = prepare_print_string(out_buf, + "\n", index); + + /*print Cm Validation -Gradient Rows*/ + index = print_silicon_id(out_buf, + &device_id[0], index); + if (result->cm_sensor_gd_row_pass) + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Gradient Rows,PASS,", + index); + else + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Gradient Rows,FAIL,", + index); + index = prepare_print_string(out_buf, + "\n", index); + + + /* + * Print Sensor Cm Validation + * -Rows Delta Matrix + */ + index = print_silicon_id(out_buf, + &device_id[0], index); + if (result->cm_sensor_row_delta_pass) + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Rows Delta Matrix,PASS,LIMITS,", + index); + else + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Rows Delta Matrix,FAIL,LIMITS,", + index); + index = prepare_print_data(out_buf, + &configuration->cm_range_limit_row, + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + + /*print Cm_sensor_avg */ + index = print_silicon_id(out_buf, + &device_id[0], index); + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Sensor Range,Cm_sensor_avg,", + index); + index = prepare_print_data(out_buf, + &cmcp_info->cm_ave_data_panel, + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + + /*printSensor Cm Validation - + * Sensor Range, Sensor Range + */ + index = print_silicon_id(out_buf, + &device_id[0], index); + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Sensor Range,Sensor Range,", + index); + scnprintf(tmp_buf, 10, "%d.%d,", + cmcp_info->cm_sensor_delta / 10, + cmcp_info->cm_sensor_delta % 10); + index = prepare_print_string(out_buf, + &tmp_buf[0], index); + index = prepare_print_string(out_buf, + "\n", index); + + /*print Sensor Cm Validation - Sensor Range*/ + index = print_silicon_id(out_buf, + &device_id[0], index); + if (result->cm_sensor_delta_pass) + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Sensor Range,PASS,LIMITS,", + index); + else + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Sensor Range,FAIL,LIMITS,", + index); + index = prepare_print_data(out_buf, + &configuration->cm_max_delta_sensor_percent, + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + } + } + } + + if ((test_item & CP_ENABLED) == CP_ENABLED) { + if (((test_item & CP_BTN) == CP_BTN) && (btn_num > 0)) { + /*print BUTNS_CP_DATA_ROW00 */ + index = print_silicon_id(out_buf, &device_id[0], index); + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,BUTNS_CP_DATA_ROW00,", + index); + index = prepare_print_data(out_buf, + &cmcp_info->cp_btn_data[0], + index, btn_num); + index = prepare_print_string(out_buf, + "\n", index); + + if (!no_builtin_file && !dad->cmcp_range_check) { + /*print Cp Button Element by Element */ + index = print_silicon_id(out_buf, &device_id[0], + index); + if (result->cp_button_validation_pass) + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check - Button Element by Element,PASS\n", + index); + else + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check - Button Element by Element,FAIL\n", + index); + + /*print cp_button_ave */ + index = print_silicon_id(out_buf, + &device_id[0], index); + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,Cp_button_avg,", + index); + index = prepare_print_data(out_buf, + &cmcp_info->cp_button_ave, + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + + /*print Cp_button_cal */ + index = print_silicon_id(out_buf, + &device_id[0], index); + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,Cp_button_cal,", + index); + index = prepare_print_data(out_buf, + &cmcp_info->cp_btn_cal, + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + } + } + + if ((test_item & CP_PANEL) == CP_PANEL) { + /*print CP_DATA_RX */ + index = print_silicon_id(out_buf, &device_id[0], index); + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,CP_DATA_RX,", index); + index = prepare_print_data(out_buf, + &cmcp_info->cp_rx_data_panel[0], index, rx_num); + index = prepare_print_string(out_buf, "\n", index); + + /*print CP_DATA_TX */ + index = print_silicon_id(out_buf, &device_id[0], index); + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,CP_DATA_TX,", index); + index = prepare_print_data(out_buf, + &cmcp_info->cp_tx_data_panel[0], index, tx_num); + index = prepare_print_string(out_buf, "\n", index); + } + if (((test_item & CP_BTN) == CP_BTN) && (btn_num > 0) + && !dad->cmcp_range_check) { + if (!no_builtin_file) { + char tmp_buf[10] = {0}; + /*print Cp_delta_button */ + index = print_silicon_id(out_buf, &device_id[0], + index); + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,Cp_delta_button,", + index); + scnprintf(tmp_buf, 10, "%d.%d,", + cmcp_info->cp_button_delta / 10, + cmcp_info->cp_button_delta % 10); + index = prepare_print_string(out_buf, + &tmp_buf[0], index); + index = prepare_print_string(out_buf, "\n", + index); + } + } + if ((test_item & CP_PANEL) == CP_PANEL && + !dad->cmcp_range_check) { + if (!no_builtin_file) { + char tmp_buf[10] = {0}; + /*print Cp_delta_rx */ + index = print_silicon_id(out_buf, &device_id[0], + index); + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,Cp_delta_rx,", + index); + scnprintf(tmp_buf, 10, "%d.%d,", + cmcp_info->cp_sensor_rx_delta / 10, + cmcp_info->cp_sensor_rx_delta % 10); + index = prepare_print_string(out_buf, + &tmp_buf[0], index); + index = prepare_print_string(out_buf, "\n", + index); + + /*print Cp_delta_tx */ + index = print_silicon_id(out_buf, &device_id[0], + index); + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,Cp_delta_tx,", + index); + scnprintf(tmp_buf, 10, "%d.%d,", + cmcp_info->cp_sensor_tx_delta / 10, + cmcp_info->cp_sensor_tx_delta % 10); + index = prepare_print_string(out_buf, + &tmp_buf[0], index); + index = prepare_print_string(out_buf, "\n", + index); + + /*print Cp_sensor_avg_rx */ + index = print_silicon_id(out_buf, &device_id[0], + index); + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,Cp_sensor_avg_rx,", + index); + index = prepare_print_data(out_buf, + &cmcp_info->cp_rx_ave_data_panel, + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + + /*print Cp_sensor_avg_tx */ + index = print_silicon_id(out_buf, + &device_id[0], index); + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,Cp_sensor_avg_tx,", + index); + index = prepare_print_data(out_buf, + &cmcp_info->cp_tx_ave_data_panel, + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + + /*print Cp_sensor_cal_rx */ + index = print_silicon_id(out_buf, + &device_id[0], index); + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,Cp_sensor_cal_rx,", + index); + index = prepare_print_data(out_buf, + &cmcp_info->cp_rx_cal_data_panel[0], + index, rx_num); + index = prepare_print_string(out_buf, + "\n", index); + + /*print Cp_sensor_cal_tx */ + index = print_silicon_id(out_buf, + &device_id[0], index); + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,Cp_sensor_cal_tx,", + index); + index = prepare_print_data(out_buf, + &cmcp_info->cp_tx_cal_data_panel[0], + index, tx_num); + index = prepare_print_string(out_buf, + "\n", index); + } + } + + if (!no_builtin_file && !dad->cmcp_range_check) { + /*print cp test limits */ + index = print_silicon_id(out_buf, &device_id[0], index); + if (result->cp_test_pass) + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,PASS, LIMITS,", + index); + else + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,FAIL, LIMITS,", + index); + + index = prepare_print_string(out_buf, + "CP_MAX_DELTA_SENSOR_RX_PERCENT,", index); + index = prepare_print_data(out_buf, + &configuration->cp_max_delta_sensor_rx_percent, + index, 1); + index = prepare_print_string(out_buf, + "CP_MAX_DELTA_SENSOR_TX_PERCENT,", index); + index = prepare_print_data(out_buf, + &configuration->cp_max_delta_sensor_tx_percent, + index, 1); + index = prepare_print_string(out_buf, + "CP_MAX_DELTA_BUTTON_PERCENT,", index); + index = prepare_print_data(out_buf, + &configuration->cp_max_delta_button_percent, + index, 1); + index = prepare_print_string(out_buf, "\n", index); + } + } + + if (!no_builtin_file) { + if ((test_item & CM_ENABLED) == CM_ENABLED) { + if ((test_item & CM_PANEL) == CM_PANEL) { + /*print columns gradient limit*/ + index = prepare_print_string(out_buf, + ",Sensor Cm Validation,MAX_LIMITS,CM_MAX_GRADIENT_COLS_PERCENT,", + index); + index = prepare_print_data(out_buf, + &configuration->cm_max_table_gradient_cols_percent[0], + index, + configuration->cm_max_table_gradient_cols_percent_size); + index = prepare_print_string(out_buf, + "\n", index); + /*print rows gradient limit*/ + index = prepare_print_string(out_buf, + ",Sensor Cm Validation,MAX_LIMITS,CM_MAX_GRADIENT_ROWS_PERCENT,", + index); + index = prepare_print_data(out_buf, + &configuration->cm_max_table_gradient_rows_percent[0], + index, + configuration->cm_max_table_gradient_rows_percent_size); + index = prepare_print_string(out_buf, + "\n", index); + + /*print cm max limit*/ + for (i = 0; i < rx_num; i++) { + index = prepare_print_string(out_buf, + ",Sensor Cm Validation,MAX_LIMITS,CM_DATA_ROW", + index); + index = prepare_print_data(out_buf, + &i, index, 1); + for (j = 0; j < tx_num; j++) + index = prepare_print_data( + out_buf, + &configuration->cm_min_max_table_sensor[i*tx_num*2+j*2+1], + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + } + } + + if (((test_item & CM_BTN) == CM_BTN) && (btn_num > 0)) { + index = prepare_print_string(out_buf, + ",Sensor Cm Validation,MAX LIMITS,M_BUTNS,", + index); + for (j = 0; j < btn_num; j++) { + index = prepare_print_data(out_buf, + &configuration->cm_min_max_table_btn[2*j+1], + index, 1); + } + index = prepare_print_string(out_buf, + "\n", index); + } + + index = prepare_print_string(out_buf, + ",Sensor Cm Validation MAX LIMITS\n", index); + + if ((test_item & CM_PANEL) == CM_PANEL) { + /*print cm min limit*/ + for (i = 0; i < rx_num; i++) { + index = prepare_print_string(out_buf, + ",Sensor Cm Validation,MIN_LIMITS,CM_DATA_ROW", + index); + index = prepare_print_data(out_buf, &i, + index, 1); + for (j = 0; j < tx_num; j++) + index = prepare_print_data( + out_buf, + &configuration->cm_min_max_table_sensor[i*tx_num*2 + j*2], + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + } + } + + if (((test_item & CM_BTN) == CM_BTN) && (btn_num > 0)) { + index = prepare_print_string(out_buf, + ",Sensor Cm Validation,MIN LIMITS,M_BUTNS,", + index); + for (j = 0; j < btn_num; j++) { + index = prepare_print_data(out_buf, + &configuration->cm_min_max_table_btn[2*j], + index, 1); + } + index = prepare_print_string(out_buf, + "\n", index); + } + index = prepare_print_string(out_buf, + ",Sensor Cm Validation MIN LIMITS\n", index); + } + + if ((test_item & CP_ENABLED) == CP_ENABLED) { + if ((test_item & CP_PANEL) == CP_PANEL) { + /*print cp tx max limit*/ + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,MAX_LIMITS,TX,", + index); + for (i = 0; i < tx_num; i++) + index = prepare_print_data(out_buf, + &configuration->cp_min_max_table_tx[i*2+1], + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + + /*print cp rx max limit*/ + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,MAX_LIMITS,RX,", + index); + for (i = 0; i < rx_num; i++) + index = prepare_print_data(out_buf, + &configuration->cp_min_max_table_rx[i*2+1], + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + } + + /*print cp btn max limit*/ + if (((test_item & CP_BTN) == CP_BTN) && (btn_num > 0)) { + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,MAX_LIMITS,S_BUTNS,", + index); + for (i = 0; i < btn_num; i++) + index = prepare_print_data(out_buf, + &configuration->cp_min_max_table_btn[i*2+1], + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + } + + if ((test_item & CP_PANEL) == CP_PANEL) { + /*print cp tx min limit*/ + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,MIN_LIMITS,TX,", + index); + for (i = 0; i < tx_num; i++) + index = prepare_print_data(out_buf, + &configuration->cp_min_max_table_tx[i*2], + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + + /*print cp rx min limit*/ + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,MIN_LIMITS,RX,", + index); + for (i = 0; i < rx_num; i++) + index = prepare_print_data(out_buf, + &configuration->cp_min_max_table_rx[i*2], + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + } + + /*print cp btn min limit*/ + if (((test_item & CP_BTN) == CP_BTN) && (btn_num > 0)) { + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,MIN_LIMITS,S_BUTNS,", + index); + for (i = 0; i < btn_num; i++) + index = prepare_print_data(out_buf, + &configuration->cp_min_max_table_btn[i*2], + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + } + } + } + return index; +} + +/******************************************************************************* + * FUNCTION: result_save + * + * SUMMARY: Malloc memory for output buffer and populate with the cmcp test + * header and results in the csv file format. + * + * NOTE: It supports simple_read_from_buffer() to read data multiple times to + * the buffer. + * + * RETURN: + * Size of data printed to "buf" + * + * PARAMETERS: + * *dev - pointer to device structure + * *buf - the user space buffer to read to + * *configuration - pointer to configuration structure + * *result - pointer to result structure + * *cmcp_info - pointer to cmcp_data structure + * *ppos - the current position in the buffer + * count - the maximum number of bytes to read + * test_item - test control in bitwise + * no_builtin_file - flag to determine if builtin-file exist + ******************************************************************************/ +int result_save(struct device *dev, char *buf, + struct configuration *configuration, struct result *result, + struct cmcp_data *cmcp_info, loff_t *ppos, size_t count, int test_item, + int no_builtin_file) +{ + u8 *out_buf = NULL; + int index = 0; + int byte_left; + + out_buf = kzalloc(MAX_BUF_LEN, GFP_KERNEL); + if (configuration == NULL) { + pt_debug(dev, DL_WARN, "config is NULL"); + return -ENOMEM; + } + if (result == NULL) { + pt_debug(dev, DL_WARN, "result is NULL"); + return -ENOMEM; + } + if (cmcp_info == NULL) { + pt_debug(dev, DL_WARN, "cmcp_info is NULL"); + return -ENOMEM; + } + + index = save_header(out_buf, index, result); + index = save_engineering_data(dev, out_buf, index, + cmcp_info, configuration, result, + test_item, no_builtin_file); + byte_left = simple_read_from_buffer(buf, count, ppos, out_buf, index); + + kfree(out_buf); + return byte_left; +} + +/******************************************************************************* + * FUNCTION: cmcp_results_debugfs_open + * + * SUMMARY: Open method for cmcp_results debugfs node. + * + * RETURN: 0 = success + * + * PARAMETERS: + * *inode - file inode number + * *filp - file pointer to debugfs file + ******************************************************************************/ +static int cmcp_results_debugfs_open(struct inode *inode, + struct file *filp) +{ + filp->private_data = inode->i_private; + return 0; +} + +/******************************************************************************* + * FUNCTION: cmcp_results_debugfs_close + * + * SUMMARY: Close method for cmcp_results debugfs node. + * + * RETURN: 0 = success + * + * PARAMETERS: + * *inode - file inode number + * *filp - file pointer to debugfs file + ******************************************************************************/ +static int cmcp_results_debugfs_close(struct inode *inode, + struct file *filp) +{ + filp->private_data = NULL; + return 0; +} + +/******************************************************************************* + * FUNCTION: cmcp_results_debugfs_read + * + * SUMMARY: Read method for cmcp_results debugfs node. This function prints + * cmcp test results to user buffer. + * + * RETURN: Size of debugfs data print + * + * PARAMETERS: + * *filp - file pointer to debugfs file + * *buf - the user space buffer to read to + * count - the maximum number of bytes to read + * *ppos - the current position in the buffer + ******************************************************************************/ +static ssize_t cmcp_results_debugfs_read(struct file *filp, + char __user *buf, size_t count, loff_t *ppos) +{ + struct pt_device_access_data *dad = filp->private_data; + struct device *dev; + struct cmcp_data *cmcp_info = dad->cmcp_info; + struct result *result = dad->result; + struct configuration *configuration = dad->configs; + int ret = 0; + int test_item; + int no_builtin_file = 0; + int test_executed = 0; + + dev = dad->dev; + + mutex_lock(&dad->sysfs_lock); + test_executed = dad->test_executed; + test_item = pt_cmcp_get_test_item(dad->cmcp_test_items); + if (dad->builtin_cmcp_threshold_status < 0) { + pt_debug(dev, DL_WARN, + "%s: No cmcp threshold file.\n", __func__); + no_builtin_file = 1; + } + mutex_unlock(&dad->sysfs_lock); + + if (test_executed) + /*save result to buf*/ + ret = result_save(dev, buf, configuration, result, cmcp_info, + ppos, count, test_item, no_builtin_file); + else { + char warning_info[] = + "No test result available!\n"; + pt_debug(dev, DL_ERROR, + "%s: No test result available!\n", __func__); + + return simple_read_from_buffer(buf, count, ppos, warning_info, + strlen(warning_info)); + } + + return ret; +} + +static const struct file_operations cmcp_results_debugfs_fops = { + .open = cmcp_results_debugfs_open, + .release = cmcp_results_debugfs_close, + .read = cmcp_results_debugfs_read, + .write = NULL, +}; + +/******************************************************************************* + * FUNCTION: cmcp_return_offset_of_new_case + * + * SUMMARY: Returns the buffer offset of new test case + * + * NOTE: There are two static variable inside this function. + * + * RETURN: offset index for new case + * + * PARAMETERS: + * *bufPtr - pointer to input buffer + * first_time - flag to initialize some static variable + * (0:init; 1:don't init) + * *pFileEnd - pointer to the end of file for safe check + ******************************************************************************/ +u32 cmcp_return_offset_of_new_case(const char *bufPtr, u32 first_time, + const char *pFileEnd) +{ + static u32 offset, first_search; + + if (first_time == 0) { + first_search = 0; + offset = 0; + } + + if (first_search != 0) { + /* Search one case */ + for (;;) { + /* Search ASCII_LF */ + while (bufPtr < pFileEnd) { + if (*bufPtr++ != ASCII_LF) + offset++; + else + break; + } + if (bufPtr >= pFileEnd) + break; + offset++; + /* + * Single line: end loop + * Multiple lines: continue loop + */ + if (*bufPtr != ASCII_COMMA) + break; + } + } else + first_search = 1; + + return offset; +} + +/******************************************************************************* + * FUNCTION: cmcp_get_case_info_from_threshold_file + * + * SUMMARY: Gets test case information from cmcp threshold file + * + * RETURN: + * Number of test cases + * + * PARAMETERS: + * *dev - pointer to Device structure + * *buf - pointer to input file + * *search_array - pointer to test_case_search structure + * file_size - size of input file for safe check + ******************************************************************************/ +u32 cmcp_get_case_info_from_threshold_file(struct device *dev, const char *buf, + struct test_case_search *search_array, u32 file_size) +{ + u32 case_num = 0, buffer_offset = 0, name_count = 0, first_search = 0; + const char *pFileEnd = buf + file_size; + + pt_debug(dev, DL_INFO, "%s: Search cmcp threshold file\n", + __func__); + + /* Get all the test cases */ + for (case_num = 0; case_num < MAX_CASE_NUM; case_num++) { + buffer_offset = + cmcp_return_offset_of_new_case(&buf[buffer_offset], + first_search, pFileEnd); + first_search = 1; + + if (buf[buffer_offset] == 0) + break; + + for (name_count = 0; name_count < NAME_SIZE_MAX; name_count++) { + /* File end */ + if (buf[buffer_offset + name_count] == ASCII_COMMA) + break; + + search_array[case_num].name[name_count] = + buf[buffer_offset + name_count]; + } + + /* Exit when buffer offset is larger than file size */ + if (buffer_offset >= file_size) + break; + + search_array[case_num].name_size = name_count; + search_array[case_num].offset = buffer_offset; + /* + * pt_debug(dev, DL_INFO, "Find case %d: Name is %s; + * Name size is %d; Case offset is %d\n", + * case_num, + * search_array[case_num].name, + * search_array[case_num].name_size, + * search_array[case_num].offset); + */ + } + + return case_num; +} + +/******************************************************************************* + * FUNCTION: cmcp_compose_data + * + * SUMMARY: Composes one value based on data of each bit + * + * RETURN: + * Value that composed from buffer + * + * PARAMETERS: + * *buf - pointer to input file + * count - number of data elements in *buf in decimal + ******************************************************************************/ +int cmcp_compose_data(char *buf, u32 count) +{ + u32 base_array[] = {1, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9}; + int value = 0; + u32 index = 0; + + for (index = 0; index < count; index++) + value += buf[index] * base_array[count - 1 - index]; + + return value; +} + +/******************************************************************************* + * FUNCTION: cmcp_return_one_value + * + * SUMMARY: Parses csv file at a given row and offset and combines multiple + * "bits' as a single value. Handles data over multiple lines and various + * end-of-line characters. + * + * NOTE: There is a static value to calculate line count inside this function. + * + * RETURN: + * Value that parsed from buffer + * + * PARAMETERS: + * *dev - pointer to devices structure + * *buf - pointer to input buffer + * *offset - offset index of input buffer + * *line_num - store line count + * pFileEnd - pointer to the end of threshold file + ******************************************************************************/ +int cmcp_return_one_value(struct device *dev, const char *buf, u32 *offset, + u32 *line_num, const char *pFileEnd) +{ + int value = -1; + char tmp_buffer[10]; + u32 count = 0; + u32 tmp_offset = *offset; + static u32 line_count = 1; + + /* Bypass extra commas */ + while (((buf + tmp_offset + 1) < pFileEnd) + && buf[tmp_offset] == ASCII_COMMA + && buf[tmp_offset + 1] == ASCII_COMMA) + tmp_offset++; + + if ((buf + tmp_offset + 1) >= pFileEnd) + goto exit; + + /* Windows and Linux difference at the end of one line */ + if (buf[tmp_offset] == ASCII_COMMA && buf[tmp_offset + 1] == ASCII_CR) { + if ((buf + tmp_offset + 2) < pFileEnd) { + if (buf[tmp_offset + 2] == ASCII_LF) + tmp_offset += 2; + } else + goto exit; + } else if (buf[tmp_offset] == ASCII_COMMA && + buf[tmp_offset + 1] == ASCII_LF) + tmp_offset += 1; + else if (buf[tmp_offset] == ASCII_COMMA + && buf[tmp_offset + 1] == ASCII_CR) + tmp_offset += 1; + + if ((buf + tmp_offset + 1) >= pFileEnd) + goto exit; + + /* New line for multiple lines */ + if ((buf[tmp_offset] == ASCII_LF || buf[tmp_offset] == ASCII_CR) && + buf[tmp_offset + 1] == ASCII_COMMA) { + tmp_offset++; + line_count++; + pt_debug(dev, DL_DEBUG, "%s: Line Count = %d\n", + __func__, line_count); + } + + /* Beginning */ + if (buf[tmp_offset] == ASCII_COMMA) { + tmp_offset++; + for (;;) { + if ((buf + tmp_offset) >= pFileEnd) + break; + + if ((buf[tmp_offset] >= ASCII_ZERO) + && (buf[tmp_offset] <= ASCII_NINE)) { + tmp_buffer[count++] = + buf[tmp_offset] - ASCII_ZERO; + tmp_offset++; + } else { + if (count != 0) { + value = cmcp_compose_data(tmp_buffer, + count); + /*pt_debug(dev, DL_DEBUG, */ + /* ",%d", value);*/ + } else { + /* 0 indicates no data available */ + value = -1; + } + break; + } + } + } else { + /* Multiple line: line count */ + if (line_num) + *line_num = line_count; + /* Reset for next case */ + line_count = 1; + } + +exit: + *offset = tmp_offset; + + return value; +} + +/******************************************************************************* + * FUNCTION: cmcp_get_configuration_info + * + * SUMMARY: Gets cmcp configuration information. + * + * PARAMETERS: + * *dev - pointer to devices structure + * *buf - pointer to input buffer + * *search_array - pointer to test_case_search structure + * case_count - number of test cases + * *field_array - pointer to test_case_field structure + * *config - pointer to configuration structure + * file_size - file size of threshold file + ******************************************************************************/ +void cmcp_get_configuration_info(struct device *dev, + const char *buf, struct test_case_search *search_array, + u32 case_count, struct test_case_field *field_array, + struct configuration *config, u32 file_size) +{ + u32 count = 0, sub_count = 0; + u32 exist_or_not = 0; + u32 value_offset = 0; + int retval = 0; + u32 data_num = 0; + u32 line_num = 1; + const char *pFileEnd = buf + file_size; + + pt_debug(dev, DL_INFO, + "%s: Fill configuration struct per cmcp threshold file\n", + __func__); + + /* Search cases */ + for (count = 0; count < MAX_CASE_NUM; count++) { + exist_or_not = 0; + for (sub_count = 0; sub_count < case_count; sub_count++) { + if (!strncmp(field_array[count].name, + search_array[sub_count].name, + field_array[count].name_size)) { + exist_or_not = 1; + break; + } + } + + field_array[count].exist_or_not = exist_or_not; + + pt_debug(dev, DL_DEBUG, + "%s: Field Array[%d] exists: %d, type: %d\n", + __func__, count, exist_or_not, field_array[count].type); + + /* Clear data number */ + data_num = 0; + + if (exist_or_not == 1) { + switch (field_array[count].type) { + case TEST_CASE_TYPE_NO: + field_array[count].data_num = 0; + field_array[count].line_num = 1; + break; + case TEST_CASE_TYPE_ONE: + value_offset = search_array[sub_count].offset + + search_array[sub_count].name_size; + *field_array[count].bufptr = + cmcp_return_one_value(dev, buf, + &value_offset, 0, pFileEnd); + field_array[count].data_num = 1; + field_array[count].line_num = 1; + break; + case TEST_CASE_TYPE_MUL: + case TEST_CASE_TYPE_MUL_LINES: + line_num = 1; + value_offset = search_array[sub_count].offset + + search_array[sub_count].name_size; + for (;;) { + retval = cmcp_return_one_value( + dev, buf, &value_offset, &line_num, + pFileEnd); + if (retval >= 0) { + *field_array[count].bufptr++ = + retval; + data_num++; + } else + break; + } + + field_array[count].data_num = data_num; + field_array[count].line_num = line_num; + break; + default: + break; + } + pt_debug(dev, DL_DEBUG, + "%s: %s: Data count is %d, line number is %d\n", + __func__, + field_array[count].name, + field_array[count].data_num, + field_array[count].line_num); + } else + pt_debug(dev, DL_ERROR, "%s: !!! %s doesn't exist\n", + __func__, field_array[count].name); + } +} + +/******************************************************************************* + * FUNCTION: cmcp_get_basic_info + * + * SUMMARY: Gets basic information for cmcp test, such as available test item, + * number of tx, rx, button. + * + * PARAMETERS: + * *dev - pointer to devices structure + * *field_array - pointer to test_case_field structure + * *config - pointer to configuration structure + ******************************************************************************/ +void cmcp_get_basic_info(struct device *dev, + struct test_case_field *field_array, struct configuration *config) +{ + u32 tx_num = 0; + u32 index = 0; + + config->is_valid_or_not = 1; /* Set to valid by default */ + config->cm_enabled = 0; + config->cp_enabled = 0; + + if (field_array[CM_TEST_INPUTS].exist_or_not) + config->cm_enabled = 1; + if (field_array[CP_TEST_INPUTS].exist_or_not) + config->cp_enabled = 1; + + /* Get basic information only when CM and CP are enabled */ + if (config->cm_enabled && config->cp_enabled) { + pt_debug(dev, DL_INFO, + "%s: Find CM and CP thresholds\n", __func__); + + config->rx_num = + field_array[PER_ELEMENT_MIN_MAX_TABLE_SENSOR].line_num; + tx_num = + (field_array[PER_ELEMENT_MIN_MAX_TABLE_SENSOR].data_num >> 1) + /field_array[PER_ELEMENT_MIN_MAX_TABLE_SENSOR].line_num; + config->tx_num = tx_num; + + config->btn_num = + field_array[PER_ELEMENT_MIN_MAX_TABLE_BUTTON].data_num >> 1; + + config->cm_min_max_table_btn_size = + field_array[PER_ELEMENT_MIN_MAX_TABLE_BUTTON].data_num; + config->cm_min_max_table_sensor_size = + field_array[PER_ELEMENT_MIN_MAX_TABLE_SENSOR].data_num; + config->cp_min_max_table_rx_size = + field_array[PER_ELEMENT_MIN_MAX_RX].data_num; + config->cp_min_max_table_tx_size = + field_array[PER_ELEMENT_MIN_MAX_TX].data_num; + config->cm_max_table_gradient_cols_percent_size = + field_array[CM_GRADIENT_CHECK_COL].data_num; + config->cm_max_table_gradient_rows_percent_size = + field_array[CM_GRADIENT_CHECK_ROW].data_num; + config->cp_min_max_table_btn_size = + field_array[CP_PER_ELEMENT_MIN_MAX_BUTTON].data_num; + + /* *** Detailed Debug Information *** */ + pt_debug(dev, DL_DEBUG, "%d\n", + config->cm_excluding_col_edge); + pt_debug(dev, DL_DEBUG, "%d\n", + config->cm_excluding_row_edge); + for (index = 0; + index < config->cm_max_table_gradient_cols_percent_size; + index++) + pt_debug(dev, DL_DEBUG, "%d\n", + config->cm_max_table_gradient_cols_percent[index]); + for (index = 0; + index < config->cm_max_table_gradient_rows_percent_size; + index++) + pt_debug(dev, DL_DEBUG, "%d\n", + config->cm_max_table_gradient_rows_percent[index]); + pt_debug(dev, DL_DEBUG, "%d\n", + config->cm_range_limit_row); + pt_debug(dev, DL_DEBUG, "%d\n", + config->cm_range_limit_col); + pt_debug(dev, DL_DEBUG, "%d\n", + config->cm_min_limit_cal); + pt_debug(dev, DL_DEBUG, "%d\n", + config->cm_max_limit_cal); + pt_debug(dev, DL_DEBUG, "%d\n", + config->cm_max_delta_sensor_percent); + pt_debug(dev, DL_DEBUG, "%d\n", + config->cm_max_delta_button_percent); + for (index = 0; + index < config->cm_min_max_table_btn_size; index++) + pt_debug(dev, DL_DEBUG, "%d\n", + config->cm_min_max_table_btn[index]); + for (index = 0; + index < config->cm_min_max_table_sensor_size; index++) + pt_debug(dev, DL_DEBUG, "%d\n", + config->cm_min_max_table_sensor[index]); + pt_debug(dev, DL_DEBUG, "%d\n", + config->cp_max_delta_sensor_rx_percent); + pt_debug(dev, DL_DEBUG, "%d\n", + config->cp_max_delta_sensor_tx_percent); + pt_debug(dev, DL_DEBUG, "%d\n", + config->cp_max_delta_button_percent); + pt_debug(dev, DL_DEBUG, "%d\n", + config->min_button); + pt_debug(dev, DL_DEBUG, "%d\n", + config->max_button); + + for (index = 0; + index < config->cp_min_max_table_btn_size; index++) + pt_debug(dev, DL_DEBUG, "%d\n", + config->cp_min_max_table_btn[index]); + for (index = 0; + index < config->cp_min_max_table_rx_size; index++) + pt_debug(dev, DL_DEBUG, "%d\n", + config->cp_min_max_table_rx[index]); + for (index = 0; + index < config->cp_min_max_table_tx_size; index++) + pt_debug(dev, DL_DEBUG, "%d\n", + config->cp_min_max_table_tx[index]); + /* *** End of Detailed Debug Information *** */ + + /* Invalid mutual data length */ + if ((field_array[PER_ELEMENT_MIN_MAX_TABLE_SENSOR].data_num >> + 1) % field_array[PER_ELEMENT_MIN_MAX_TABLE_SENSOR].line_num) { + config->is_valid_or_not = 0; + pt_debug(dev, DL_ERROR, "Invalid mutual data length\n"); + } + } else { + if (!config->cm_enabled) + pt_debug(dev, DL_ERROR, + "%s: Miss CM thresholds or CM data format is wrong!\n", + __func__); + + if (!config->cp_enabled) + pt_debug(dev, DL_ERROR, + "%s: Miss CP thresholds or CP data format is wrong!\n", + __func__); + + config->rx_num = 0; + config->tx_num = 0; + config->btn_num = 0; + config->is_valid_or_not = 0; + } + + pt_debug(dev, DL_DEBUG, + "%s:\n" + "Input file is %s!\n" + "CM test: %s\n" + "CP test: %s\n" + "rx_num is %d\n" + "tx_num is %d\n" + "btn_num is %d\n", + __func__, + config->is_valid_or_not == 1 ? "VALID" : "!!! INVALID !!!", + config->cm_enabled == 1 ? "Found" : "Not found", + config->cp_enabled == 1 ? "Found" : "Not found", + config->rx_num, + config->tx_num, + config->btn_num); +} + +/******************************************************************************* + * FUNCTION: cmcp_test_case_field_init + * + * SUMMARY: Initialize the structure test_field_array. + * + * PARAMETERS: + * *test_field_array - pointer to test_case_field structure + * *configuration - pointer to configuration structure + ******************************************************************************/ +void cmcp_test_case_field_init(struct test_case_field *test_field_array, + struct configuration *configs) +{ + struct test_case_field test_case_field_array[MAX_CASE_NUM] = { + {"CM TEST INPUTS", 14, TEST_CASE_TYPE_NO, + NULL, 0, 0, 0}, + {"CM_EXCLUDING_COL_EDGE", 21, TEST_CASE_TYPE_ONE, + &configs->cm_excluding_col_edge, 0, 0, 0}, + {"CM_EXCLUDING_ROW_EDGE", 21, TEST_CASE_TYPE_ONE, + &configs->cm_excluding_row_edge, 0, 0, 0}, + {"CM_GRADIENT_CHECK_COL", 21, TEST_CASE_TYPE_MUL, + &configs->cm_max_table_gradient_cols_percent[0], + 0, 0, 0}, + {"CM_GRADIENT_CHECK_ROW", 21, TEST_CASE_TYPE_MUL, + &configs->cm_max_table_gradient_rows_percent[0], + 0, 0, 0}, + {"CM_RANGE_LIMIT_ROW", 18, TEST_CASE_TYPE_ONE, + &configs->cm_range_limit_row, 0, 0, 0}, + {"CM_RANGE_LIMIT_COL", 18, TEST_CASE_TYPE_ONE, + &configs->cm_range_limit_col, 0, 0, 0}, + {"CM_MIN_LIMIT_CAL", 16, TEST_CASE_TYPE_ONE, + &configs->cm_min_limit_cal, 0, 0, 0}, + {"CM_MAX_LIMIT_CAL", 16, TEST_CASE_TYPE_ONE, + &configs->cm_max_limit_cal, 0, 0, 0}, + {"CM_MAX_DELTA_SENSOR_PERCENT", 27, TEST_CASE_TYPE_ONE, + &configs->cm_max_delta_sensor_percent, 0, 0, 0}, + {"CM_MAX_DELTA_BUTTON_PERCENT", 27, TEST_CASE_TYPE_ONE, + &configs->cm_max_delta_button_percent, 0, 0, 0}, + {"PER_ELEMENT_MIN_MAX_TABLE_BUTTON", 32, TEST_CASE_TYPE_MUL, + &configs->cm_min_max_table_btn[0], 0, 0, 0}, + {"PER_ELEMENT_MIN_MAX_TABLE_SENSOR", 32, + TEST_CASE_TYPE_MUL_LINES, + &configs->cm_min_max_table_sensor[0], 0, 0, 0}, + {"CP TEST INPUTS", 14, TEST_CASE_TYPE_NO, + NULL, 0, 0, 0}, + {"CP_PER_ELEMENT_MIN_MAX_BUTTON", 29, TEST_CASE_TYPE_MUL, + &configs->cp_min_max_table_btn[0], 0, 0, 0}, + {"CP_MAX_DELTA_SENSOR_RX_PERCENT", 30, TEST_CASE_TYPE_ONE, + &configs->cp_max_delta_sensor_rx_percent, + 0, 0, 0}, + {"CP_MAX_DELTA_SENSOR_TX_PERCENT", 30, TEST_CASE_TYPE_ONE, + &configs->cp_max_delta_sensor_tx_percent, + 0, 0, 0}, + {"CP_MAX_DELTA_BUTTON_PERCENT", 27, TEST_CASE_TYPE_ONE, + &configs->cp_max_delta_button_percent, 0, 0, 0}, + {"MIN_BUTTON", 10, TEST_CASE_TYPE_ONE, + &configs->min_button, 0, 0, 0}, + {"MAX_BUTTON", 10, TEST_CASE_TYPE_ONE, + &configs->max_button, 0, 0, 0}, + {"PER_ELEMENT_MIN_MAX_RX", 22, TEST_CASE_TYPE_MUL, + &configs->cp_min_max_table_rx[0], 0, 0, 0}, + {"PER_ELEMENT_MIN_MAX_TX", 22, TEST_CASE_TYPE_MUL, + &configs->cp_min_max_table_tx[0], 0, 0, 0}, + }; + + memcpy(test_field_array, test_case_field_array, + sizeof(struct test_case_field) * MAX_CASE_NUM); +} + +/******************************************************************************* + * FUNCTION: pt_parse_cmcp_threshold_file_common + * + * SUMMARY: Parses cmcp threshold file and stores to the data structure. + * + * PARAMETERS: + * *dev - pointer to devices structure + * *buf - pointer to input buffer + * file_size - file size + ******************************************************************************/ +static ssize_t pt_parse_cmcp_threshold_file_common( + struct device *dev, const char *buf, u32 file_size) +{ + struct pt_device_access_data *dad + = pt_get_device_access_data(dev); + ssize_t rc = 0; + u32 case_count = 0; + + pt_debug(dev, DL_INFO, + "%s: Start parsing cmcp threshold file. File size is %d\n", + __func__, file_size); + + cmcp_test_case_field_init(dad->test_field_array, dad->configs); + + /* Get all the cases from .csv file */ + case_count = cmcp_get_case_info_from_threshold_file(dev, + buf, dad->test_search_array, file_size); + + pt_debug(dev, DL_INFO, + "%s: Number of cases found in CSV file: %d\n", + __func__, case_count); + + /* Search cases */ + cmcp_get_configuration_info(dev, + buf, + dad->test_search_array, case_count, dad->test_field_array, + dad->configs, file_size); + + /* Get basic information */ + cmcp_get_basic_info(dev, dad->test_field_array, dad->configs); + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_cmcp_threshold_loading_store + * + * SUMMARY: The store method for the cmcp_threshold_loading sysfs node. The + * passed in value controls if threshold loading is performed. + * + * RETURN: Size of passed in buffer is success + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to buffer that hold the command parameters + * size - size of buf + ******************************************************************************/ +static ssize_t pt_cmcp_threshold_loading_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct pt_device_access_data *dad = pt_get_device_access_data(dev); + ssize_t length; + u32 input_data[3]; + int rc = 0; + + length = cmd->parse_sysfs_input(dev, buf, size, input_data, + ARRAY_SIZE(input_data)); + + if (length != 1) { + pt_debug(dev, DL_WARN, "%s: Invalid number of arguments\n", + __func__); + rc = -EINVAL; + goto exit; + } + + mutex_lock(&dad->cmcp_threshold_lock); + + if (input_data[0] == 1) + dad->cmcp_threshold_loading = true; + else if (input_data[0] == -1) + dad->cmcp_threshold_loading = false; + else if (input_data[0] == 0 && dad->cmcp_threshold_loading) { + dad->cmcp_threshold_loading = false; + + if (dad->cmcp_threshold_size == 0) { + pt_debug(dev, DL_ERROR, "%s: No cmcp threshold data\n", + __func__); + goto exit_free; + } + + /* Clear test executed flag */ + dad->test_executed = 0; + + pt_parse_cmcp_threshold_file_common(dev, + &dad->cmcp_threshold_data[0], dad->cmcp_threshold_size); + + /* Mark valid */ + dad->builtin_cmcp_threshold_status = 0; + /* Restore test item to default value when new file input */ + dad->cmcp_test_items = 0; + } else { + pt_debug(dev, DL_WARN, "%s: Invalid value\n", __func__); + rc = -EINVAL; + mutex_unlock(&dad->cmcp_threshold_lock); + goto exit; + } + +exit_free: + kfree(dad->cmcp_threshold_data); + dad->cmcp_threshold_data = NULL; + dad->cmcp_threshold_size = 0; + mutex_unlock(&dad->cmcp_threshold_lock); + +exit: + if (rc) + return rc; + return size; +} + +static DEVICE_ATTR(cmcp_threshold_loading, 0200, + NULL, pt_cmcp_threshold_loading_store); + +/******************************************************************************* + * FUNCTION: pt_cmcp_threshold_data_write + * + * SUMMARY: The write method for the cmcp_threshold_data_sysfs node. The passed + * in data (threshold file) is written to the threshold buffer. + * + * RETURN: Size of passed in buffer is success + * + * PARAMETERS: + * *filp - pointer to file structure + * *kobj - pointer to kobject structure + * *bin_attr - pointer to bin_attribute structure + * buf - pointer to cmd input buffer + * offset - offset index to store input buffer + * count - size of data in buffer + ******************************************************************************/ +static ssize_t pt_cmcp_threshold_data_write(struct file *filp, + struct kobject *kobj, struct bin_attribute *bin_attr, + char *buf, loff_t offset, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct pt_device_access_data *dad + = pt_get_device_access_data(dev); + u8 *p; + + pt_debug(dev, DL_INFO, "%s: offset:%lld count:%zu\n", + __func__, offset, count); + + mutex_lock(&dad->cmcp_threshold_lock); + + if (!dad->cmcp_threshold_loading) { + mutex_unlock(&dad->cmcp_threshold_lock); + return -ENODEV; + } + + p = krealloc(dad->cmcp_threshold_data, offset + count, GFP_KERNEL); + if (!p) { + kfree(dad->cmcp_threshold_data); + dad->cmcp_threshold_data = NULL; + mutex_unlock(&dad->cmcp_threshold_lock); + return -ENOMEM; + } + dad->cmcp_threshold_data = p; + + memcpy(&dad->cmcp_threshold_data[offset], buf, count); + dad->cmcp_threshold_size += count; + + mutex_unlock(&dad->cmcp_threshold_lock); + + return count; +} + +static struct bin_attribute bin_attr_cmcp_threshold_data = { + .attr = { + .name = "cmcp_threshold_data", + .mode = 0200, + }, + .size = 0, + .write = pt_cmcp_threshold_data_write, +}; + + +/******************************************************************************* + * FUNCTION: pt_suspend_scan_cmd_ + * + * SUMMARY: Non-protected wrapper function for suspend scan command + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to devices structure + ******************************************************************************/ +static int pt_suspend_scan_cmd_(struct device *dev) +{ + int rc; + + rc = cmd->nonhid_cmd->suspend_scanning(dev, 0); + if (rc) + pt_debug(dev, DL_ERROR, "%s: Suspend scan failed rc = %d\n", + __func__, rc); + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_resume_scan_cmd_ + * + * SUMMARY: Non-protected wrapper function for resume scan command + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to devices structure + ******************************************************************************/ +static int pt_resume_scan_cmd_(struct device *dev) +{ + int rc; + + rc = cmd->nonhid_cmd->resume_scanning(dev, 0); + if (rc) + pt_debug(dev, DL_ERROR, "%s: Resume scan failed rc = %d\n", + __func__, rc); + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_exec_scan_cmd_ + * + * SUMMARY: Non-protected wrapper function for execute scan command + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to devices structure + * scan_type - type of panel scan to perform (PIP2 only) + ******************************************************************************/ +static int pt_exec_scan_cmd_(struct device *dev, u8 scan_type) +{ + int rc; + + rc = cmd->nonhid_cmd->exec_panel_scan(dev, PT_CORE_CMD_UNPROTECTED, + scan_type); + if (rc) + pt_debug(dev, DL_ERROR, + "%s: Heatmap start scan failed rc = %d\n", + __func__, rc); + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_ret_scan_data_cmd_ + * + * SUMMARY: Non-protected wrapper function for retrieve panel data command + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * read_offset - read pointer offset + * read_count - length of data to read + * data_id - enumerated test ID to read selftest results from + * *response - pointer to store the read response status + * *config - pointer to store config data + * *actual_read_len - pointer to store data length actually read + * *return_buf - pointer to the read buffer + ******************************************************************************/ +static int pt_ret_scan_data_cmd_(struct device *dev, u16 read_offset, + u16 read_count, u8 data_id, u8 *response, u8 *config, + u16 *actual_read_len, u8 *return_buf) +{ + int rc; + + rc = cmd->nonhid_cmd->retrieve_panel_scan(dev, 0, read_offset, + read_count, data_id, response, config, actual_read_len, + return_buf); + if (rc) + pt_debug(dev, DL_ERROR, + "%s: Retrieve scan data failed rc = %d\n", + __func__, rc); + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_get_data_structure_cmd_ + * + * SUMMARY: Non-protected wrapper function for get data structure command + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * read_offset - read pointer offset + * read_length - length of data to read + * data_id - data ID to read + * *status - pointer to store the read response status + * *data_format - pointer to store format of data read + * *actual_read_len - pointer to store data length actually read + * *data - pointer to store data read + ******************************************************************************/ +static int pt_get_data_structure_cmd_(struct device *dev, u16 read_offset, + u16 read_length, u8 data_id, u8 *status, u8 *data_format, + u16 *actual_read_len, u8 *data) +{ + int rc; + + rc = cmd->nonhid_cmd->get_data_structure(dev, 0, read_offset, + read_length, data_id, status, data_format, + actual_read_len, data); + if (rc) + pt_debug(dev, DL_ERROR, + "%s: Get data structure failed rc = %d\n", + __func__, rc); + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_run_selftest_cmd_ + * + * SUMMARY: Non-protected wrapper function for run self test command + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * test_id - enumerated test ID to run + * write_idacs_to_flash - flag whether to write new IDACS to flash + * *status - pointer to store the read response status + * *summary_results - pointer to store the results summary + * *results_available - pointer to store if results are available + ******************************************************************************/ +static int pt_run_selftest_cmd_(struct device *dev, u8 test_id, + u8 write_idacs_to_flash, u8 *status, u8 *summary_result, + u8 *results_available) +{ + int rc; + + rc = cmd->nonhid_cmd->run_selftest(dev, 0, test_id, + write_idacs_to_flash, status, summary_result, + results_available); + if (rc) + pt_debug(dev, DL_ERROR, "%s: Run self test failed rc = %d\n", + __func__, rc); + return rc; +} + + +/******************************************************************************* + * FUNCTION: pt_get_selftest_result_cmd_ + * + * SUMMARY: Non-protected wrapper function for get self test result command + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * read_offset - read pointer offset + * read_length - length of data to read + * test_id - enumerated test ID to read selftest results from + * *status - pointer to store the read response status + * *actual_read_len - pointer to store data length actually read + * *data - pointer to where the data read is stored + ******************************************************************************/ +static int pt_get_selftest_result_cmd_(struct device *dev, + u16 read_offset, u16 read_length, u8 test_id, u8 *status, + u16 *actual_read_len, u8 *data) +{ + int rc; + + rc = cmd->nonhid_cmd->get_selftest_result(dev, 0, read_offset, + read_length, test_id, status, actual_read_len, data); + if (rc) + pt_debug(dev, DL_ERROR, + "%s: Get self test result failed rc = %d\n", + __func__, rc); + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_calibrate_ext_cmd + * + * SUMMARY: Wrapper function to function calibrate_ext() in pt_core_commands + * structure + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *cal_data - pointer to extended calibration data structure + * *status - pointer to where the command response status is stored + ******************************************************************************/ +static int _pt_calibrate_ext_cmd(struct device *dev, + struct pt_cal_ext_data *cal_data, u8 *status) +{ + int rc; + + rc = cmd->nonhid_cmd->calibrate_ext(dev, + PT_CORE_CMD_UNPROTECTED, cal_data, status); + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_calibrate_idacs_cmd + * + * SUMMARY: Wrapper function to function calibrate_idacs() in pt_core_commands + * structure + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * sensing_mode - sense mode to calibrate (0-5) + * *status - pointer to where the command response status is stored + ******************************************************************************/ +static int _pt_calibrate_idacs_cmd(struct device *dev, + u8 sensing_mode, u8 *status) +{ + int rc; + + rc = cmd->nonhid_cmd->calibrate_idacs(dev, 0, sensing_mode, status); + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_initialize_baselines_cmd + * + * SUMMARY: Wrapper function to call initialize_baselines() in pt_core_commands + * structure + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * sensing_mode - enumerated ID against which to initialize the baseline + * *status - pointer to where the command response statas is stored + ******************************************************************************/ +static int _pt_initialize_baselines_cmd(struct device *dev, + u8 sensing_mode, u8 *status) +{ + int rc; + + rc = cmd->nonhid_cmd->initialize_baselines(dev, 0, sensing_mode, + status); + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_perform_calibration + * + * SUMMARY: For Gen5/6, Send the PIP1 Calibrate IDACs command (0x28). For TT/TC, + * send PIP1 Extended Calibrate command (0x30). + * + * NOTE: Panel scan must be suspended prior to calling this function. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int pt_perform_calibration(struct device *dev) +{ + struct pt_cal_ext_data cal_data = {0}; + u8 dut_gen = cmd->request_dut_generation(dev); + u8 mode; + u8 status; + int rc; + + if (dut_gen == DUT_PIP1_ONLY) { + for (mode = 0; mode < 3; mode++) { + rc = _pt_calibrate_idacs_cmd(dev, mode, &status); + if (rc < 0) { + pt_debug(dev, DL_ERROR, + "%s: calibrate idac error, mode= %d, rc = %d\n", + __func__, mode, rc); + break; + } + } + } else { + memset(&cal_data, 0, sizeof(struct pt_cal_ext_data)); + rc = _pt_calibrate_ext_cmd(dev, &cal_data, &status); + if (rc < 0) + pt_debug(dev, DL_ERROR, + "%s: extended calibrate error, rc = %d\n", + __func__, rc); + } + + return rc; +} + +/******************************************************************************* + * FUNCTION: prepare_print_buffer + * + * SUMMARY: Format input buffer to out buffer with Hex base,and format "status" + * to decimal base. + * + * RETURN: + * size of formated data in output buffer + * + * PARAMETERS: + * status - Indicate test result:0(STATUS_SUCCESS),-1(STATUS_FAIL) + * *in_buf - input buffer to be formated + * length - length of input buffer + * *out_buf - output buffer to store formated data + * out_buf_size - length of output buffer + * out_format - format of output data (5 supported formats): + * PT_PR_FORMAT_DEFAULT : format all data as a column + * PT_PR_FORMAT_U8_SPACE : sort status bytes and self test results, + * and format the results as a row, each element include 1 byte + * PT_PR_FORMAT_U16_SPACE : sort status bytes and self test results, + * and format the results as a row, each element include 2 byte + * PT_PR_FORMAT_U8_NO_SPACE : sort status bytes and self test results, + * and format the results as a row, no space between the elements + * PT_PR_FORMAT_U32_SPACE : sort status bytes and self test results, + * and format the results as a row, each element include 4 byte + ******************************************************************************/ +static int prepare_print_buffer(int status, u8 *in_buf, int length, + u8 *out_buf, size_t out_buf_size, u8 out_format) +{ + int index = 0; + int data_length; + int i; + + index += scnprintf(out_buf, out_buf_size, "Status: %d\n", status); + + if (out_format == PT_PR_FORMAT_DEFAULT) { + for (i = 0; i < length; i++) + index += scnprintf(&out_buf[index], + out_buf_size - index, + "%02X\n", in_buf[i]); + } else { + index += scnprintf(&out_buf[index], + out_buf_size - index, + "Response Status[1-%d]: ", MIN(length, 3)); + for (i = 0; i < MIN(length, 3); i++) + index += scnprintf(&out_buf[index], + out_buf_size - index, + "%02X ", in_buf[i]); + index += scnprintf(&out_buf[index], out_buf_size - index, "\n"); + if (length <= 6) { + goto exit; + } else { + data_length = get_unaligned_le16(&in_buf[4]); + index += scnprintf(&out_buf[index], + out_buf_size - index, "RAW_DATA: "); + } + + if (out_format == PT_PR_FORMAT_U8_SPACE) { + for (i = 6; i < length; i++) + index += scnprintf(&out_buf[index], + out_buf_size - index, + "%02X ", in_buf[i]); + index += scnprintf(&out_buf[index], + out_buf_size - index, + ":(%d bytes)\n", data_length); + } else if (out_format == PT_PR_FORMAT_U16_SPACE) { + for (i = 6; (i + 1) < length; i += 2) + index += scnprintf(&out_buf[index], + out_buf_size - index, "%04X ", + get_unaligned_le16(&in_buf[i])); + index += scnprintf(&out_buf[index], + out_buf_size - index, + ":(%d words)\n", (length-6)/2); + } else if (out_format == PT_PR_FORMAT_U8_NO_SPACE) { + for (i = 6; i < length; i++) + index += scnprintf(&out_buf[index], + out_buf_size - index, + "%02X", in_buf[i]); + index += scnprintf(&out_buf[index], + out_buf_size - index, + ":(%d bytes)\n", data_length); + } else if (out_format == PT_PR_FORMAT_U32_SPACE) { + for (i = 6; (i + 1) < length; i += 4) + index += scnprintf(&out_buf[index], + out_buf_size - index, "%08X ", + get_unaligned_le32(&in_buf[i])); + index += scnprintf(&out_buf[index], + out_buf_size - index, + ":(%d 32bit values)\n", (length-6)/4); + } + } + +exit: + return index; +} + +/******************************************************************************* + * FUNCTION: pt_run_and_get_selftest_result + * + * SUMMARY: Run the selftest and store the test result in the + * pt_device_access_data struct. + * + * RETURN: + * >0 : Size of debugfs data to print + * <0 : failure + * 0 : success + * + * NOTE: "Status: x" - x will contain the first error code if any + * + * PARAMETERS: + * *dev - pointer to device structure + * protect - flag to call protected or non-protected + * *buf - pointer to print buf of return data + * buf_len - length of print buf of return data + * test_id - selftest id + * read_length - max length to stor return data + * get_result_on_pass - indicate whether to get result when finish test + * print_results - print results to log + * (true:get result;false:don't get result ) + * print_format - format of print results + ******************************************************************************/ +static ssize_t pt_run_and_get_selftest_result(struct device *dev, + int protect, char *buf, size_t buf_len, u8 test_id, + u16 read_length, bool get_result_on_pass, bool print_results, + u8 print_format) +{ + struct pt_device_access_data *dad = pt_get_device_access_data(dev); + int status = STATUS_SUCCESS; + u8 cmd_status = STATUS_SUCCESS; + u8 summary_result = 0; + u8 sys_mode = FW_SYS_MODE_UNDEFINED; + u16 act_length = 0; + int length = 0; + int size = 0; + int rc; + + mutex_lock(&dad->sysfs_lock); + + pm_runtime_get_sync(dev); + + if (protect == PT_CORE_CMD_PROTECTED) { + rc = cmd->request_exclusive(dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Error on request exclusive rc = %d\n", + __func__, rc); + status = -EPERM; + goto put_pm_runtime; + } + } + + /* Get the current scan state so we restore to the same at the end */ + rc = cmd->request_get_fw_mode(dev, PT_CORE_CMD_UNPROTECTED, &sys_mode, + NULL); + if (rc) { + status = rc; + goto release_exclusive; + } + + if (sys_mode != FW_SYS_MODE_TEST) { + rc = pt_suspend_scan_cmd_(dev); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Error on suspend scan rc = %d\n", + __func__, rc); + status = -EPERM; + goto release_exclusive; + } + } + + /* Sleep for 20ms to allow the last scan to be available in FW */ + msleep(20); + + rc = pt_run_selftest_cmd_(dev, test_id, 0, + &cmd_status, &summary_result, NULL); + if (rc) { + /* Error sending self test */ + pt_debug(dev, DL_ERROR, + "%s: Error on run self test for test_id:%d rc = %d\n", + __func__, test_id, rc); + status = rc; + goto resume_scan; + } + if (cmd_status) { + /* Self test response status failure */ + pt_debug(dev, DL_WARN, + "%s: Test ID: 0x%02X resulted in status: 0x%02X\n", + __func__, test_id, cmd_status); + status = cmd_status; + } + + dad->si = cmd->request_sysinfo(dad->dev); + if (!dad->si) { + pt_debug(dad->dev, DL_ERROR, + "%s: Fail get sysinfo pointer from core\n", __func__); + if (status == STATUS_SUCCESS) + status = -EINVAL; + goto resume_scan; + } + if (IS_PIP_VER_GE(dad->si, 1, 11)) { + /* PIP1.11+ does not report the summary_result in byte 6 */ + summary_result = cmd_status; + } + + /* Form response buffer */ + dad->ic_buf[0] = cmd_status; + dad->ic_buf[1] = summary_result; + + pt_debug(dev, DL_INFO, "%s: Run Self Test cmd status = %d\n", + __func__, cmd_status); + pt_debug(dev, DL_INFO, "%s: Run Self Test result summary = %d\n", + __func__, summary_result); + + length = 2; + + /* + * Get data if requested and the cmd status indicates that the test + * completed with either a pass or a fail. All other status codes + * indicate the test itself was not run so there is no data to retrieve + */ + if ((cmd_status == PT_ST_RESULT_PASS || + cmd_status == PT_ST_RESULT_FAIL) && get_result_on_pass) { + rc = pt_get_selftest_result_cmd_(dev, 0, read_length, + test_id, &cmd_status, &act_length, &dad->ic_buf[6]); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Error on get self test result rc = %d\n", + __func__, rc); + if (status == STATUS_SUCCESS) + status = rc; + goto resume_scan; + } + + pt_debug(dev, DL_INFO, "%s: Get Self Test result status = %d\n", + __func__, cmd_status); + + /* Only store new status if no error on running self test */ + if (status == STATUS_SUCCESS) + status = cmd_status; + + dad->ic_buf[2] = cmd_status; + dad->ic_buf[3] = test_id; + dad->ic_buf[4] = LOW_BYTE(act_length); + dad->ic_buf[5] = HI_BYTE(act_length); + + length = 6 + act_length; + } + +resume_scan: + /* Only resume scanning if we suspended it */ + if (sys_mode == FW_SYS_MODE_SCANNING) + pt_resume_scan_cmd_(dev); + +release_exclusive: + if (protect == PT_CORE_CMD_PROTECTED) + cmd->release_exclusive(dev); + +put_pm_runtime: + pm_runtime_put(dev); + + /* Communication error */ + if (status < 0) + length = 0; + + if (print_results) { + size = prepare_print_buffer(status, dad->ic_buf, length, + buf, buf_len, print_format); + rc = size; + } + + mutex_unlock(&dad->sysfs_lock); + + return rc; +} + +struct pt_device_access_debugfs_data { + struct pt_device_access_data *dad; + ssize_t pr_buf_len; + u8 pr_buf[10 * PT_MAX_PRBUF_SIZE]; +}; + +/******************************************************************************* + * FUNCTION: pt_device_access_debugfs_open + * + * SUMMARY: Open the device_access debugfs node to initialize. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *inode - pointer to inode structure + * *filp - pointer to file structure + ******************************************************************************/ +static int pt_device_access_debugfs_open(struct inode *inode, + struct file *filp) +{ + struct pt_device_access_data *dad = inode->i_private; + struct pt_device_access_debugfs_data *data; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->dad = dad; + + filp->private_data = data; + + return nonseekable_open(inode, filp); +} + +/******************************************************************************* + * FUNCTION: pt_device_access_debugfs_release + * + * SUMMARY: Close the device_access debugfs node to free pointer. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *inode - pointer to inode structure + * *filp - pointer to file structure + ******************************************************************************/ +static int pt_device_access_debugfs_release(struct inode *inode, + struct file *filp) +{ + kfree(filp->private_data); + + return 0; +} + +#define PT_DEBUGFS_FOPS(_name, _read, _write) \ +static const struct file_operations _name##_debugfs_fops = { \ + .open = pt_device_access_debugfs_open, \ + .release = pt_device_access_debugfs_release, \ + .read = _read, \ + .write = _write, \ +} + +/******************************************************************************* + * FUNCTION: panel_scan_debugfs_read + * + * SUMMARY: This function retrieves a full panel scan by sending the following + * PIP commands: + * 1) Suspend Scanning + * 2) Execute Panel Scan + * 3) Retrieve Panel Scan (n times to retrieve full scan) + * 4) Resume Scanning + * + * RETURN: Size of debugfs data print + * + * PARAMETERS: + * *filp - file pointer to debugfs file + * *buf - the user space buffer to read to + * count - the maximum number of bytes to read + * *ppos - the current position in the buffer + ******************************************************************************/ +static ssize_t panel_scan_debugfs_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct pt_device_access_debugfs_data *data = filp->private_data; + struct pt_device_access_data *dad = data->dad; + struct device *dev = dad->dev; + struct pt_core_data *cd = dev_get_drvdata(dev); + int status = STATUS_FAIL; + u8 config; + u16 num_elem_read; + int length = 0; + u8 element_size = 0; + u8 *buf_out; + u8 *buf_offset; + u8 sys_mode = FW_SYS_MODE_UNDEFINED; + int elem_offset = 0; + int rc; + int print_idx = 0; + int i; + + mutex_lock(&dad->debugfs_lock); + buf_out = dad->panel_scan_data_buf; + if (!buf_out) + goto release_mutex; + + pm_runtime_get_sync(dev); + + /* + * This function will re-enter if the panel_scan_size is greater than + * count (count is the kernel page size which is typically 4096), on + * re-entry, *ppos will retain how far the last copy to user space + * completed + */ + if (*ppos) { + if (*ppos >= dad->panel_scan_size) + goto release_mutex; + + print_idx = simple_read_from_buffer(buf, count, ppos, + buf_out, dad->panel_scan_size); + + pt_debug(dev, DL_DEBUG, "%s: Sent %d bytes to user space\n", + __func__, print_idx); + + goto release_mutex; + } + + rc = cmd->request_exclusive(dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Error on request exclusive rc = %d\n", + __func__, rc); + goto put_pm_runtime; + } + + /* Get the current scan state so we restore to the same at the end */ + rc = cmd->request_get_fw_mode(dev, PT_CORE_CMD_UNPROTECTED, &sys_mode, + NULL); + if (rc) { + status = rc; + goto release_exclusive; + } + + if (sys_mode != FW_SYS_MODE_TEST) { + rc = pt_suspend_scan_cmd_(dev); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Error on suspend scan rc = %d\n", + __func__, rc); + goto release_exclusive; + } + } + + rc = pt_exec_scan_cmd_(dev, dad->panel_scan_type_id); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Error on execute panel scan rc = %d\n", + __func__, rc); + goto resume_scan; + } + + /* Set length to max to read all */ + rc = pt_ret_scan_data_cmd_(dev, 0, 0xFFFF, + dad->panel_scan_retrieve_id, dad->ic_buf, &config, + &num_elem_read, NULL); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Error on retrieve panel scan rc = %d\n", + __func__, rc); + goto resume_scan; + } + + length = get_unaligned_le16(&dad->ic_buf[0]); + buf_offset = dad->ic_buf + length; + element_size = config & 0x07; + elem_offset = num_elem_read; + while (num_elem_read > 0) { + rc = pt_ret_scan_data_cmd_(dev, elem_offset, 0xFFFF, + dad->panel_scan_retrieve_id, NULL, &config, + &num_elem_read, buf_offset); + if (rc) + goto resume_scan; + + length += num_elem_read * element_size; + buf_offset = dad->ic_buf + length; + elem_offset += num_elem_read; + if (num_elem_read < 0x7A) + break; + } + /* Reconstruct cmd header */ + put_unaligned_le16(length, &dad->ic_buf[0]); + put_unaligned_le16(elem_offset, &dad->ic_buf[7]); + + status = STATUS_SUCCESS; + +resume_scan: + /* Only resume scanning if we suspended it */ + if (sys_mode == FW_SYS_MODE_SCANNING) + pt_resume_scan_cmd_(dev); + +release_exclusive: + cmd->release_exclusive(dev); + +put_pm_runtime: + pm_runtime_put(dev); + + if (status == STATUS_FAIL) + length = 0; + if (cd->show_timestamp) + print_idx += scnprintf(buf_out + print_idx, TTHE_TUNER_MAX_BUF, + "[%u] SCAN_DATA:", pt_get_time_stamp()); + else + print_idx += scnprintf(buf_out + print_idx, TTHE_TUNER_MAX_BUF, + "SCAN_DATA:"); + + for (i = 0; i < length; i++) + print_idx += scnprintf(buf_out + print_idx, + TTHE_TUNER_MAX_BUF - print_idx, + "%02X ", dad->ic_buf[i]); + print_idx += scnprintf(buf_out + print_idx, + TTHE_TUNER_MAX_BUF - print_idx, + ":(%d bytes)\n", length); + + /* + * Save the size of the full scan which this function uses on re-entry + * to send the data back to user space in 'count' size chuncks + */ + dad->panel_scan_size = print_idx; + print_idx = simple_read_from_buffer(buf, count, ppos, buf_out, + print_idx); + pt_debug(dev, DL_DEBUG, "%s: Sent %d bytes to user space\n", + __func__, print_idx); + +release_mutex: + mutex_unlock(&dad->debugfs_lock); + return print_idx; +} + +/******************************************************************************* + * FUNCTION: panel_scan_debugfs_write + * + * SUMMARY: Store the type of panel scan the read method will perform. + * + * RETURN: Size of debugfs data write + * + * PARAMETERS: + * *filp - file pointer to debugfs file + * *buf - the user space buffer to write to + * count - the maximum number of bytes to write + * *ppos - the current position in the buffer + ******************************************************************************/ +static ssize_t panel_scan_debugfs_write(struct file *filp, + const char __user *buf, size_t count, loff_t *ppos) +{ + struct pt_device_access_debugfs_data *data = filp->private_data; + struct pt_device_access_data *dad = data->dad; + ssize_t length; + u32 input_data[3]; + int rc = 0; + + rc = simple_write_to_buffer(data->pr_buf, sizeof(data->pr_buf), ppos, + buf, count); + if (rc < 0) + return rc; + + count = rc; + + mutex_lock(&dad->debugfs_lock); + length = cmd->parse_sysfs_input(dad->dev, data->pr_buf, count, + input_data, ARRAY_SIZE(input_data)); + switch (length) { + case 1: + dad->panel_scan_retrieve_id = input_data[0]; + dad->panel_scan_type_id = 0; + break; + case 2: + dad->panel_scan_retrieve_id = input_data[0]; + dad->panel_scan_type_id = input_data[1]; + break; + default: + pt_debug(dad->dev, DL_ERROR, + "%s: Malformed input\n", __func__); + rc = -EINVAL; + } + mutex_unlock(&dad->debugfs_lock); + + if (rc) + return rc; + return count; +} + +/******************************************************************************* + * FUNCTION: panel_scan_debugfs_open + * + * SUMMARY: Open the panel_scan debugfs node to initialize. + * + * RETURN: 0 = success + * !0 = failure + * + * PARAMETERS: + * *inode - file inode number + * *filp - file pointer to debugfs file + ******************************************************************************/ +static int panel_scan_debugfs_open(struct inode *inode, + struct file *filp) +{ + struct pt_device_access_data *dad = inode->i_private; + struct pt_device_access_debugfs_data *data; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->dad = dad; + data->pr_buf_len = 4 * PT_MAX_PRBUF_SIZE; + + filp->private_data = data; + + return nonseekable_open(inode, filp); +} + +/******************************************************************************* + * FUNCTION: panel_scan_debugfs_close + * + * SUMMARY: Close the panel_scan debugfs node to free pointer. + * + * RETURN: 0 = success + * + * PARAMETERS: + * *inode - file inode number + * *filp - file pointer to debugfs file + ******************************************************************************/ +static int panel_scan_debugfs_close(struct inode *inode, + struct file *filp) +{ + kfree(filp->private_data); + filp->private_data = NULL; + return 0; +} + +static const struct file_operations panel_scan_fops = { + .open = panel_scan_debugfs_open, + .release = panel_scan_debugfs_close, + .read = panel_scan_debugfs_read, + .write = panel_scan_debugfs_write, +}; + +/******************************************************************************* + * FUNCTION: get_idac_debugfs_read + * + * SUMMARY: Retrieve data structure with idac data id by sending the following + * PIP commands: + * 1) Suspend Scanning + * 2) Retrieve data structure + * 3) Resume Scanning + * The "Status: n" this node prints, 'n' will be: + * - zero for a full pass + * - negative for TTDL communication errors + * - positive for any FW status errors + * + * RETURN: Size of debugfs data print + * + * PARAMETERS: + * *filp - file pointer to debugfs file + * *buf - the user space buffer to read to + * count - the maximum number of bytes to read + * *ppos - the current position in the buffer + ******************************************************************************/ +static ssize_t get_idac_debugfs_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct pt_device_access_debugfs_data *data = filp->private_data; + struct pt_device_access_data *dad = data->dad; + struct device *dev = dad->dev; + int status = STATUS_FAIL; + u8 cmd_status = 0; + u8 data_format = 0; + u16 act_length = 0; + int length = 0; + int rc; + + if (*ppos) + goto exit; + + mutex_lock(&dad->sysfs_lock); + + pm_runtime_get_sync(dev); + + rc = cmd->request_exclusive(dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc) { + status = rc; + pt_debug(dev, DL_ERROR, + "%s: Error on request exclusive rc = %d\n", + __func__, rc); + goto put_pm_runtime; + } + + rc = pt_suspend_scan_cmd_(dev); + if (rc) { + status = rc; + pt_debug(dev, DL_ERROR, "%s: Error on suspend scan rc = %d\n", + __func__, rc); + goto release_exclusive; + } + + rc = pt_get_data_structure_cmd_(dev, 0, PIP_CMD_MAX_LENGTH, + dad->get_idac_data_id, &cmd_status, &data_format, + &act_length, &dad->ic_buf[5]); + if (rc) { + status = rc; + pt_debug(dev, DL_ERROR, + "%s: Error on get data structure rc = %d\n", + __func__, rc); + goto resume_scan; + } + + dad->ic_buf[0] = cmd_status; + dad->ic_buf[1] = dad->get_idac_data_id; + dad->ic_buf[2] = LOW_BYTE(act_length); + dad->ic_buf[3] = HI_BYTE(act_length); + dad->ic_buf[4] = data_format; + + length = 5 + act_length; + + status = cmd_status; + +resume_scan: + pt_resume_scan_cmd_(dev); + +release_exclusive: + cmd->release_exclusive(dev); + +put_pm_runtime: + pm_runtime_put(dev); + + if (status == STATUS_FAIL) + length = 0; + + data->pr_buf_len = prepare_print_buffer(status, dad->ic_buf, length, + data->pr_buf, sizeof(data->pr_buf), PT_PR_FORMAT_DEFAULT); + + mutex_unlock(&dad->sysfs_lock); + +exit: + return simple_read_from_buffer(buf, count, ppos, data->pr_buf, + data->pr_buf_len); +} + +/******************************************************************************* + * FUNCTION: get_idac_debugfs_write + * + * SUMMARY: Store the data id of idac,the read method will perform. + * + * RETURN: Size of debugfs data write + * + * PARAMETERS: + * *filp - file pointer to debugfs file + * *buf - the user space buffer to write to + * count - the maximum number of bytes to write + * *ppos - the current position in the buffer + ******************************************************************************/ +static ssize_t get_idac_debugfs_write(struct file *filp, + const char __user *buf, size_t count, loff_t *ppos) +{ + struct pt_device_access_debugfs_data *data = filp->private_data; + struct pt_device_access_data *dad = data->dad; + ssize_t length; + u32 input_data[2]; + int rc = 0; + + rc = simple_write_to_buffer(data->pr_buf, sizeof(data->pr_buf), ppos, + buf, count); + if (rc < 0) + return rc; + + count = rc; + + mutex_lock(&dad->sysfs_lock); + + length = cmd->parse_sysfs_input(dad->dev, data->pr_buf, count, + input_data, ARRAY_SIZE(input_data)); + if (length != 1) { + pt_debug(dad->dev, DL_ERROR, + "%s: Malformed input\n", __func__); + rc = -EINVAL; + goto exit_unlock; + } + + dad->get_idac_data_id = input_data[0]; + +exit_unlock: + mutex_unlock(&dad->sysfs_lock); + + if (rc) + return rc; + + return count; +} + +PT_DEBUGFS_FOPS(get_idac, get_idac_debugfs_read, get_idac_debugfs_write); + +/******************************************************************************* + * FUNCTION: calibrate_ext_debugfs_read + * + * SUMMARY: Perform extended calibration command(0x30) which is flexible to + * calibrate each individual feature by adding extra parameter for calibration + * mode. + * + * NOTE: + * - This calibrate command requires the DUT to support PIP version >= 1.10 + * - The "Status:" included in the printout will be one of the following: + * <0 - Linux error code (PIP transmission error) + * 0 - Full pass + * >0 - PIP error status + * + * RETURN: Size of debugfs data print + * + * PARAMETERS: + * *filp - file pointer to debugfs file + * *buf - the user space buffer to read to + * count - the maximum number of bytes to read + * *ppos - the current position in the buffer + ******************************************************************************/ +static ssize_t calibrate_ext_debugfs_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct pt_device_access_debugfs_data *data = filp->private_data; + struct pt_device_access_data *dad = data->dad; + struct device *dev = dad->dev; + int status = STATUS_FAIL; + int length = 0; + int rc; + + if (*ppos) + goto exit; + + dad->si = cmd->request_sysinfo(dad->dev); + if (!dad->si) { + pt_debug(dad->dev, DL_ERROR, + "%s: Fail get sysinfo pointer from core\n", + __func__); + status = -EIO; + data->pr_buf_len = prepare_print_buffer(status, dad->ic_buf, 0, + data->pr_buf, sizeof(data->pr_buf), + PT_PR_FORMAT_DEFAULT); + goto exit; + } + + if (!IS_PIP_VER_GE(dad->si, 1, 10)) { + pt_debug(dad->dev, DL_ERROR, + "%s: extended calibration command is not supported\n", + __func__); + status = -EPROTONOSUPPORT; + data->pr_buf_len = prepare_print_buffer(status, dad->ic_buf, 0, + data->pr_buf, sizeof(data->pr_buf), + PT_PR_FORMAT_DEFAULT); + goto exit; + } + + if (dad->cal_ext_data.mode == PT_CAL_EXT_MODE_UNDEFINED) { + pt_debug(dad->dev, DL_ERROR, + "%s: No parameters provided for calibration command\n", + __func__); + status = -EINVAL; + data->pr_buf_len = prepare_print_buffer(status, dad->ic_buf, 0, + data->pr_buf, sizeof(data->pr_buf), + PT_PR_FORMAT_DEFAULT); + goto exit; + } + + mutex_lock(&dad->sysfs_lock); + + pm_runtime_get_sync(dev); + + rc = cmd->request_exclusive(dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc) { + status = rc; + pt_debug(dev, DL_ERROR, + "%s: Error on request exclusive rc = %d\n", + __func__, rc); + goto put_pm_runtime; + } + + rc = pt_suspend_scan_cmd_(dev); + if (rc) { + status = rc; + pt_debug(dev, DL_ERROR, "%s: Error on suspend scan rc = %d\n", + __func__, rc); + goto release_exclusive; + } + + rc = _pt_calibrate_ext_cmd(dev, &dad->cal_ext_data, &dad->ic_buf[0]); + if (rc) { + status = rc; + pt_debug(dev, DL_ERROR, + "%s: Error on calibrate_ext rc = %d\n", + __func__, rc); + goto resume_scan; + } + + /* + * Include PIP errors as positive status codes and report the data. + * No PIP error "0x00" in the response indicates full success + */ + length = 1; + status = dad->ic_buf[0]; + +resume_scan: + pt_resume_scan_cmd_(dev); + +release_exclusive: + cmd->release_exclusive(dev); + +put_pm_runtime: + pm_runtime_put(dev); + + /* Negative status codes are bus transmission errors and have no data */ + if (status < 0) + length = 0; + + data->pr_buf_len = prepare_print_buffer(status, dad->ic_buf, length, + data->pr_buf, sizeof(data->pr_buf), PT_PR_FORMAT_DEFAULT); + + mutex_unlock(&dad->sysfs_lock); + +exit: + return simple_read_from_buffer(buf, count, ppos, data->pr_buf, + data->pr_buf_len); +} + +/******************************************************************************* + * FUNCTION: calibrate_ext_debugfs_write + * + * SUMMARY: Stores the calibration mode and up to three parameters to perform + * individual features. + * + * RETURN: Size of debugfs data write + * + * PARAMETERS: + * *filp - file pointer to debugfs file + * *buf - the user space buffer to write to + * count - the maximum number of bytes to write + * *ppos - the current position in the buffer + ******************************************************************************/ +static ssize_t calibrate_ext_debugfs_write(struct file *filp, + const char __user *buf, size_t count, loff_t *ppos) +{ + struct pt_device_access_debugfs_data *data = filp->private_data; + struct pt_device_access_data *dad = data->dad; + ssize_t length; + u32 input_data[5]; + int rc = 0; + int i = 0; + + rc = simple_write_to_buffer(data->pr_buf, sizeof(data->pr_buf), ppos, + buf, count); + if (rc < 0) + return rc; + + count = rc; + + mutex_lock(&dad->sysfs_lock); + + length = cmd->parse_sysfs_input(dad->dev, data->pr_buf, count, + input_data, ARRAY_SIZE(input_data)); + if ((length <= 4) && (length > 0)) { + for (i = length; i < 4; i++) + input_data[i] = 0; + + dad->cal_ext_data.mode = (u8)input_data[0]; + dad->cal_ext_data.data0 = (u8)input_data[1]; + dad->cal_ext_data.data1 = (u8)input_data[2]; + dad->cal_ext_data.data2 = (u8)input_data[3]; +#ifdef TTDL_DIAGNOSTICS + pt_debug(dad->dev, DL_INFO, + "%s: calibration mode=%d, data[0..2]=0x%02X %02X %02X\n", + __func__, + dad->cal_ext_data.mode, dad->cal_ext_data.data0, + dad->cal_ext_data.data1, dad->cal_ext_data.data2); +#endif + } else { + pt_debug(dad->dev, DL_ERROR, + "%s: Malformed input\n", __func__); + rc = -EINVAL; + goto exit_unlock; + } + +exit_unlock: + mutex_unlock(&dad->sysfs_lock); + + if (rc) + return rc; + + return count; +} + +PT_DEBUGFS_FOPS(calibrate_ext, + calibrate_ext_debugfs_read, calibrate_ext_debugfs_write); + +/******************************************************************************* + * FUNCTION: calibrate_debugfs_read + * + * SUMMARY: Perform calibration by sending the following PIP commands: + * 1) Suspend Scanning + * 2) Execute calibrate + * 3) Initialize baseline conditionally + * 4) Resume Scanning + * + * RETURN: Size of debugfs data print + * + * PARAMETERS: + * *filp - file pointer to debugfs file + * *buf - the user space buffer to read to + * count - the maximum number of bytes to read + * *ppos - the current position in the buffer + ******************************************************************************/ +static ssize_t calibrate_debugfs_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct pt_device_access_debugfs_data *data = filp->private_data; + struct pt_device_access_data *dad = data->dad; + struct device *dev = dad->dev; + int status = STATUS_FAIL; + int length = 0; + int rc; + + if (*ppos) + goto exit; + + mutex_lock(&dad->sysfs_lock); + + pm_runtime_get_sync(dev); + + rc = cmd->request_exclusive(dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc) { + status = rc; + pt_debug(dev, DL_ERROR, + "%s: Error on request exclusive rc = %d\n", + __func__, rc); + goto put_pm_runtime; + } + + rc = pt_suspend_scan_cmd_(dev); + if (rc) { + status = rc; + pt_debug(dev, DL_ERROR, "%s: Error on suspend scan rc = %d\n", + __func__, rc); + goto release_exclusive; + } + + rc = _pt_calibrate_idacs_cmd(dev, dad->calibrate_sensing_mode, + &dad->ic_buf[0]); + if (rc) { + status = rc; + pt_debug(dev, DL_ERROR, + "%s: Error on calibrate idacs rc = %d\n", + __func__, rc); + goto resume_scan; + } + + length = 1; + + /* Check if baseline initialization is requested */ + if (dad->calibrate_initialize_baselines) { + /* Perform baseline initialization for all modes */ + rc = _pt_initialize_baselines_cmd(dev, PT_IB_SM_MUTCAP | + PT_IB_SM_SELFCAP | PT_IB_SM_BUTTON, + &dad->ic_buf[length]); + if (rc) { + status = rc; + pt_debug(dev, DL_ERROR, + "%s: Error on initialize baselines rc = %d\n", + __func__, rc); + goto resume_scan; + } + + length++; + } + + status = STATUS_SUCCESS; + +resume_scan: + pt_resume_scan_cmd_(dev); + +release_exclusive: + cmd->release_exclusive(dev); + +put_pm_runtime: + pm_runtime_put(dev); + + if (status == STATUS_FAIL) + length = 0; + + data->pr_buf_len = prepare_print_buffer(status, dad->ic_buf, length, + data->pr_buf, sizeof(data->pr_buf), PT_PR_FORMAT_DEFAULT); + + mutex_unlock(&dad->sysfs_lock); + +exit: + return simple_read_from_buffer(buf, count, ppos, data->pr_buf, + data->pr_buf_len); +} + +/******************************************************************************* + * FUNCTION: calibrate_debugfs_write + * + * SUMMARY: Stores the calibration sense mode and a flag to control if the + * baseline will be initialized for the read method of this node. + * + * RETURN: Size of debugfs data write + * + * PARAMETERS: + * *filp - file pointer to debugfs file + * *buf - the user space buffer to write to + * count - the maximum number of bytes to write + * *ppos - the current position in the buffer + ******************************************************************************/ +static ssize_t calibrate_debugfs_write(struct file *filp, + const char __user *buf, size_t count, loff_t *ppos) +{ + struct pt_device_access_debugfs_data *data = filp->private_data; + struct pt_device_access_data *dad = data->dad; + ssize_t length; + u32 input_data[3]; + int rc = 0; + + rc = simple_write_to_buffer(data->pr_buf, sizeof(data->pr_buf), ppos, + buf, count); + if (rc < 0) + return rc; + + count = rc; + + mutex_lock(&dad->sysfs_lock); + + length = cmd->parse_sysfs_input(dad->dev, data->pr_buf, count, + input_data, ARRAY_SIZE(input_data)); + if (length != 2) { + pt_debug(dad->dev, DL_ERROR, + "%s: Malformed input\n", __func__); + rc = -EINVAL; + goto exit_unlock; + } + + dad->calibrate_sensing_mode = input_data[0]; + dad->calibrate_initialize_baselines = input_data[1]; + +exit_unlock: + mutex_unlock(&dad->sysfs_lock); + + if (rc) + return rc; + + return count; +} + +PT_DEBUGFS_FOPS(calibrate, calibrate_debugfs_read, calibrate_debugfs_write); + +/******************************************************************************* + * FUNCTION: baseline_debugfs_read + * + * SUMMARY: Perform baseline initialization by sending the following PIP + * commands: + * 1) Suspend Scanning + * 2) Execute initialize baseline + * 3) Resume Scanning + * + * RETURN: Size of debugfs data print + * + * PARAMETERS: + * *filp - file pointer to debugfs file + * *buf - the user space buffer to read to + * count - the maximum number of bytes to read + * *ppos - the current position in the buffer + ******************************************************************************/ +static ssize_t baseline_debugfs_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct pt_device_access_debugfs_data *data = filp->private_data; + struct pt_device_access_data *dad = data->dad; + struct device *dev = dad->dev; + int status = STATUS_FAIL; + int length = 0; + int rc; + + if (*ppos) + goto exit; + + mutex_lock(&dad->sysfs_lock); + + pm_runtime_get_sync(dev); + + rc = cmd->request_exclusive(dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc) { + status = rc; + pt_debug(dev, DL_ERROR, + "%s: Error on request exclusive rc = %d\n", + __func__, rc); + goto put_pm_runtime; + } + + rc = pt_suspend_scan_cmd_(dev); + if (rc) { + status = rc; + pt_debug(dev, DL_ERROR, "%s: Error on suspend scan rc = %d\n", + __func__, rc); + goto release_exclusive; + } + + rc = _pt_initialize_baselines_cmd(dev, dad->baseline_sensing_mode, + &dad->ic_buf[0]); + if (rc) { + status = rc; + pt_debug(dev, DL_ERROR, + "%s: Error on initialize baselines rc = %d\n", + __func__, rc); + goto resume_scan; + } + + length = 1; + + status = STATUS_SUCCESS; + +resume_scan: + pt_resume_scan_cmd_(dev); + +release_exclusive: + cmd->release_exclusive(dev); + +put_pm_runtime: + pm_runtime_put(dev); + + if (status == STATUS_FAIL) + length = 0; + + data->pr_buf_len = prepare_print_buffer(status, dad->ic_buf, length, + data->pr_buf, sizeof(data->pr_buf), PT_PR_FORMAT_DEFAULT); + + mutex_unlock(&dad->sysfs_lock); + +exit: + return simple_read_from_buffer(buf, count, ppos, data->pr_buf, + data->pr_buf_len); +} + +/******************************************************************************* + * FUNCTION: baseline_debugfs_write + * + * SUMMARY: Store the sense mode of base initialization, the read method will + * perform. + * + * RETURN: Size of debugfs data write + * + * PARAMETERS: + * *filp - file pointer to debugfs file + * *buf - the user space buffer to write to + * count - the maximum number of bytes to write + * *ppos - the current position in the buffer + ******************************************************************************/ +static ssize_t baseline_debugfs_write(struct file *filp, + const char __user *buf, size_t count, loff_t *ppos) +{ + struct pt_device_access_debugfs_data *data = filp->private_data; + struct pt_device_access_data *dad = data->dad; + ssize_t length; + u32 input_data[2]; + int rc = 0; + + rc = simple_write_to_buffer(data->pr_buf, sizeof(data->pr_buf), ppos, + buf, count); + if (rc < 0) + return rc; + + count = rc; + + mutex_lock(&dad->sysfs_lock); + + length = cmd->parse_sysfs_input(dad->dev, data->pr_buf, count, + input_data, ARRAY_SIZE(input_data)); + if (length != 1) { + pt_debug(dad->dev, DL_ERROR, + "%s: Malformed input\n", __func__); + rc = -EINVAL; + goto exit_unlock; + } + + dad->baseline_sensing_mode = input_data[0]; + +exit_unlock: + mutex_unlock(&dad->sysfs_lock); + + if (rc) + return rc; + + return count; +} + +PT_DEBUGFS_FOPS(baseline, baseline_debugfs_read, baseline_debugfs_write); + +/******************************************************************************* + * FUNCTION: auto_shorts_debugfs_read + * + * SUMMARY: Performs the "auto shorts" test and prints the result to the output + * buffer. + * + * RETURN: Size of debugfs data print + * + * PARAMETERS: + * *filp - file pointer to debugfs file + * *buf - the user space buffer to read to + * count - the maximum number of bytes to read + * *ppos - the current position in the buffer + ******************************************************************************/ +static ssize_t auto_shorts_debugfs_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct pt_device_access_debugfs_data *data = filp->private_data; + + if (!*ppos) + /* Set length to PIP_CMD_MAX_LENGTH to read all */ + data->pr_buf_len = pt_run_and_get_selftest_result( + data->dad->dev, PT_CORE_CMD_PROTECTED, + data->pr_buf, sizeof(data->pr_buf), + PT_ST_ID_AUTOSHORTS, PIP_CMD_MAX_LENGTH, + PT_ST_DONT_GET_RESULTS, PT_ST_PRINT_RESULTS, + PT_PR_FORMAT_DEFAULT); + + return simple_read_from_buffer(buf, count, ppos, data->pr_buf, + data->pr_buf_len); +} + +PT_DEBUGFS_FOPS(auto_shorts, auto_shorts_debugfs_read, NULL); + +/******************************************************************************* + * FUNCTION: opens_debugfs_read + * + * SUMMARY: Performs the "opens" test and prints the results to the output + * buffer. + * + * RETURN: Size of debugfs data print + * + * PARAMETERS: + * *filp - file pointer to debugfs file + * *buf - the user space buffer to read to + * count - the maximum number of bytes to read + * *ppos - the current position in the buffer + ******************************************************************************/ +static ssize_t opens_debugfs_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct pt_device_access_debugfs_data *data = filp->private_data; + + if (!*ppos) + /* Set length to PIP_CMD_MAX_LENGTH to read all */ + data->pr_buf_len = pt_run_and_get_selftest_result( + data->dad->dev, PT_CORE_CMD_PROTECTED, + data->pr_buf, sizeof(data->pr_buf), + PT_ST_ID_OPENS, PIP_CMD_MAX_LENGTH, + PT_ST_DONT_GET_RESULTS, PT_ST_PRINT_RESULTS, + PT_PR_FORMAT_DEFAULT); + + return simple_read_from_buffer(buf, count, ppos, data->pr_buf, + data->pr_buf_len); +} + +PT_DEBUGFS_FOPS(opens, opens_debugfs_read, NULL); + +/******************************************************************************* + * FUNCTION: cm_panel_debugfs_read + * + * SUMMARY: Performs the "CM panel" test and prints the result to the output + * buffer. + * + * RETURN: Size of debugfs data print + * + * PARAMETERS: + * *filp - file pointer to debugfs file + * *buf - the user space buffer to read to + * count - the maximum number of bytes to read + * *ppos - the current position in the buffer + ******************************************************************************/ +static ssize_t cm_panel_debugfs_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct pt_device_access_debugfs_data *data = filp->private_data; + + if (!*ppos) + /* Set length to PIP_CMD_MAX_LENGTH to read all */ + data->pr_buf_len = pt_run_and_get_selftest_result( + data->dad->dev, PT_CORE_CMD_PROTECTED, + data->pr_buf, sizeof(data->pr_buf), + PT_ST_ID_CM_PANEL, PIP_CMD_MAX_LENGTH, + PT_ST_GET_RESULTS, PT_ST_PRINT_RESULTS, + PT_PR_FORMAT_DEFAULT); + + return simple_read_from_buffer(buf, count, ppos, data->pr_buf, + data->pr_buf_len); +} + +PT_DEBUGFS_FOPS(cm_panel, cm_panel_debugfs_read, NULL); + +/******************************************************************************* + * FUNCTION: cp_panel_debugfs_read + * + * SUMMARY: Performs the "CP panel" test and prints the result to the output + * buffer. + * + * RETURN: Size of debugfs data print + * + * PARAMETERS: + * *filp - file pointer to debugfs file + * *buf - the user space buffer to read to + * count - the maximum number of bytes to read + * *ppos - the current position in the buffer + ******************************************************************************/ +static ssize_t cp_panel_debugfs_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct pt_device_access_debugfs_data *data = filp->private_data; + + if (!*ppos) + /* Set length to PIP_CMD_MAX_LENGTH to read all */ + data->pr_buf_len = pt_run_and_get_selftest_result( + data->dad->dev, PT_CORE_CMD_PROTECTED, + data->pr_buf, sizeof(data->pr_buf), + PT_ST_ID_CP_PANEL, PIP_CMD_MAX_LENGTH, + PT_ST_GET_RESULTS, PT_ST_PRINT_RESULTS, + PT_PR_FORMAT_DEFAULT); + + return simple_read_from_buffer(buf, count, ppos, data->pr_buf, + data->pr_buf_len); +} + +PT_DEBUGFS_FOPS(cp_panel, cp_panel_debugfs_read, NULL); + +/******************************************************************************* + * FUNCTION: cm_button_debugfs_read + * + * SUMMARY: Performs the "CM buttons" test and prints the result to the output + * buffer. + * + * RETURN: Size of debugfs data print + * + * PARAMETERS: + * *filp - file pointer to debugfs file + * *buf - the user space buffer to read to + * count - the maximum number of bytes to read + * *ppos - the current position in the buffer + ******************************************************************************/ +static ssize_t cm_button_debugfs_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct pt_device_access_debugfs_data *data = filp->private_data; + + if (!*ppos) + /* Set length to PIP_CMD_MAX_LENGTH to read all */ + data->pr_buf_len = pt_run_and_get_selftest_result( + data->dad->dev, PT_CORE_CMD_PROTECTED, + data->pr_buf, sizeof(data->pr_buf), + PT_ST_ID_CM_BUTTON, PIP_CMD_MAX_LENGTH, + PT_ST_GET_RESULTS, PT_ST_PRINT_RESULTS, + PT_PR_FORMAT_DEFAULT); + + return simple_read_from_buffer(buf, count, ppos, data->pr_buf, + data->pr_buf_len); +} + +PT_DEBUGFS_FOPS(cm_button, cm_button_debugfs_read, NULL); + +/******************************************************************************* + * FUNCTION: cp_button_debugfs_read + * + * SUMMARY: Performs the "CP buttons" test and prints the result to the output + * buffer. + * + * RETURN: Size of debugfs data print + * + * PARAMETERS: + * *filp - file pointer to debugfs file + * *buf - the user space buffer to read to + * count - the maximum number of bytes to read + * *ppos - the current position in the buffer + ******************************************************************************/ +static ssize_t cp_button_debugfs_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct pt_device_access_debugfs_data *data = filp->private_data; + + if (!*ppos) + /* Set length to PIP_CMD_MAX_LENGTH to read all */ + data->pr_buf_len = pt_run_and_get_selftest_result( + data->dad->dev, PT_CORE_CMD_PROTECTED, + data->pr_buf, sizeof(data->pr_buf), + PT_ST_ID_CP_BUTTON, PIP_CMD_MAX_LENGTH, + PT_ST_GET_RESULTS, PT_ST_PRINT_RESULTS, + PT_PR_FORMAT_DEFAULT); + + return simple_read_from_buffer(buf, count, ppos, data->pr_buf, + data->pr_buf_len); +} + +PT_DEBUGFS_FOPS(cp_button, cp_button_debugfs_read, NULL); + +/******************************************************************************* + * FUNCTION: fw_self_test_debugfs_read + * + * SUMMARY: Performs the self test by firmware and prints the results to the + * output buffer. + * + * RETURN: Size of debugfs data print + * + * PARAMETERS: + * *filp - file pointer to debugfs file + * *buf - the user space buffer to read to + * count - the maximum number of bytes to read + * *ppos - the current position in the buffer + ******************************************************************************/ +static ssize_t fw_self_test_debugfs_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + u8 ret_status; + u8 ret_self_test_id; + u8 sys_mode = FW_SYS_MODE_UNDEFINED; + u16 ret_act_load_len; + int rc; + struct pt_device_access_debugfs_data *data = filp->private_data; + struct pt_device_access_data *dad = data->dad; + + if (!*ppos) { + if (dad->fw_self_test_id == PT_ST_ID_INVALID || + dad->fw_self_test_format >= PT_PR_FORMAT_UNDEFINE) { + data->pr_buf_len = scnprintf(data->pr_buf, + sizeof(data->pr_buf), "Status: %d\n", -EINVAL); + pt_debug(dad->dev, DL_ERROR, + "%s: Malformed input\n", __func__); + goto exit; + } + + /* only send the load parameters cmd if param data exists */ + if (dad->fw_self_test_param_len > 0) { + rc = cmd->request_get_fw_mode(dad->dev, + PT_CORE_CMD_UNPROTECTED, &sys_mode, NULL); + if (rc) { + pt_debug(dad->dev, DL_ERROR, + "%s: ERR on request mode rc=%d\n", + __func__, rc); + data->pr_buf_len = scnprintf( + data->pr_buf, + sizeof(data->pr_buf), + "Status: %d\n", rc); + goto exit; + } + + if (sys_mode != FW_SYS_MODE_TEST) { + rc = pt_suspend_scan_cmd_(dad->dev); + if (rc) { + pt_debug(dad->dev, DL_ERROR, + "%s: ERR on sus scan rc=%d\n", + __func__, rc); + data->pr_buf_len = scnprintf( + data->pr_buf, + sizeof(data->pr_buf), + "Status: %d\n", rc); + goto exit; + } + } + cmd->nonhid_cmd->load_self_test_param(dad->dev, + PT_CORE_CMD_PROTECTED, + dad->fw_self_test_id, 0, + dad->fw_self_test_param_len, + dad->fw_self_test_param, &ret_status, + &ret_self_test_id, &ret_act_load_len); + if (ret_status) { + data->pr_buf_len = scnprintf(data->pr_buf, + sizeof(data->pr_buf), + "Status: %d\n", -EINVAL); + pt_debug(dad->dev, DL_ERROR, + "%s: Load Param Malformed input\n", + __func__); + goto resume_scan; + } + } + + /* Set length to PIP_CMD_MAX_LENGTH to read all */ + data->pr_buf_len = pt_run_and_get_selftest_result( + dad->dev, PT_CORE_CMD_PROTECTED, + data->pr_buf, sizeof(data->pr_buf), + dad->fw_self_test_id, PIP_CMD_MAX_LENGTH, + PT_ST_GET_RESULTS, PT_ST_PRINT_RESULTS, + dad->fw_self_test_format); + + /* Clear the parameters so next test won't use them */ + if (dad->fw_self_test_param_len > 0) { + cmd->nonhid_cmd->load_self_test_param(dad->dev, + PT_CORE_CMD_PROTECTED, + dad->fw_self_test_id, 0, 0, NULL, + &ret_status, &ret_self_test_id, + &ret_act_load_len); + } + + dad->fw_self_test_id = PT_ST_ID_INVALID; + dad->fw_self_test_format = PT_PR_FORMAT_UNDEFINE; + dad->fw_self_test_param_len = 0; + +resume_scan: + /* Only resume scanning if we suspended it */ + if (sys_mode == FW_SYS_MODE_SCANNING) + pt_resume_scan_cmd_(dad->dev); + } + +exit: + return simple_read_from_buffer(buf, count, ppos, data->pr_buf, + data->pr_buf_len); +} + +/******************************************************************************* + * FUNCTION: fw_self_test_debugfs_write + * + * SUMMARY: Store the self test ID and ouptut format, the read method will + * perform. + * + * RETURN: Size of debugfs data write + * + * PARAMETERS: + * *filp - file pointer to debugfs file + * *buf - the user space buffer to write to + * count - the maximum number of bytes to write + * *ppos - the current position in the buffer + ******************************************************************************/ +static ssize_t fw_self_test_debugfs_write(struct file *filp, + const char __user *buf, size_t count, loff_t *ppos) +{ + struct pt_device_access_debugfs_data *data = filp->private_data; + struct pt_device_access_data *dad = data->dad; + ssize_t length; + u32 input_data[PT_FW_SELF_TEST_MAX_PARM + 1]; + int rc = 0; + int i; + + rc = simple_write_to_buffer(data->pr_buf, sizeof(data->pr_buf), ppos, + buf, count); + if (rc < 0) + return rc; + + count = rc; + + mutex_lock(&dad->sysfs_lock); + + length = cmd->parse_sysfs_input(dad->dev, data->pr_buf, count, + input_data, PT_FW_SELF_TEST_MAX_PARM + 1); + if (length == 1) { + dad->fw_self_test_id = input_data[0]; + dad->fw_self_test_format = PT_PR_FORMAT_DEFAULT; + dad->fw_self_test_param_len = 0; + } else if (length == 2) { + dad->fw_self_test_id = input_data[0]; + dad->fw_self_test_format = input_data[1]; + dad->fw_self_test_param_len = 0; + } else if (length > 2 && (length <= PT_FW_SELF_TEST_MAX_PARM)) { + dad->fw_self_test_id = input_data[0]; + dad->fw_self_test_format = input_data[1]; + dad->fw_self_test_param_len = length - 2; + pt_debug(dad->dev, DL_INFO, + "%s: test_id=%d, format=%d, param_len=%d", + __func__, dad->fw_self_test_id, + dad->fw_self_test_format, dad->fw_self_test_param_len); + for (i = 0; i < dad->fw_self_test_param_len; i++) + dad->fw_self_test_param[i] = input_data[i + 2]; + } else { + pt_debug(dad->dev, DL_ERROR, + "%s: Malformed input\n", __func__); + rc = -EINVAL; + goto exit_unlock; + } + +exit_unlock: + mutex_unlock(&dad->sysfs_lock); + + if (rc) + return rc; + + return count; +} + +PT_DEBUGFS_FOPS(fw_self_test, + fw_self_test_debugfs_read, fw_self_test_debugfs_write); + +#ifdef TTHE_TUNER_SUPPORT +/******************************************************************************* + * FUNCTION: tthe_get_panel_data_debugfs_read + * + * SUMMARY: Performs a panel scan and prints the panel data into the output + * buffer. + * + * RETURN: Size of debugfs data print + * + * PARAMETERS: + * *filp - file pointer to debugfs file + * *buf - the user space buffer to read to + * count - the maximum number of bytes to read + * *ppos - the current position in the buffer + ******************************************************************************/ +static ssize_t tthe_get_panel_data_debugfs_read(struct file *filp, + char __user *buf, size_t count, loff_t *ppos) +{ + struct pt_device_access_data *dad = filp->private_data; + struct device *dev; + struct pt_core_data *cd; + u8 config; + u16 actual_read_len; + u16 length = 0; + u8 element_size = 0; + u8 *buf_offset; + u8 *buf_out; + int elem; + int elem_offset = 0; + int print_idx = 0; + int rc; + int rc1; + int i; + + mutex_lock(&dad->debugfs_lock); + dev = dad->dev; + cd = dev_get_drvdata(dev); + buf_out = dad->panel_scan_data_buf; + if (!buf_out) + goto release_mutex; + + pm_runtime_get_sync(dev); + + rc = cmd->request_exclusive(dev, PT_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc) + goto put_runtime; + + if (dad->heatmap.scan_start) { + /* + * To fix CDT206291: avoid multiple scans when + * return data is larger than 4096 bytes in one cycle + */ + dad->heatmap.scan_start = 0; + + /* Start scan */ + rc = pt_exec_scan_cmd_(dev, 0); + if (rc) + goto release_exclusive; + } + + elem = dad->heatmap.num_element; + +#if defined(PT_ENABLE_MAX_ELEN) + if (elem > PT_MAX_ELEN) { + rc = pt_ret_scan_data_cmd_(dev, elem_offset, + PT_MAX_ELEN, dad->heatmap.data_type, dad->ic_buf, + &config, &actual_read_len, NULL); + } else { + rc = pt_ret_scan_data_cmd_(dev, elem_offset, elem, + dad->heatmap.data_type, dad->ic_buf, &config, + &actual_read_len, NULL); + } +#else + rc = pt_ret_scan_data_cmd_(dev, elem_offset, elem, + dad->heatmap.data_type, dad->ic_buf, &config, + &actual_read_len, NULL); +#endif + if (rc) + goto release_exclusive; + + length = get_unaligned_le16(&dad->ic_buf[0]); + buf_offset = dad->ic_buf + length; + + element_size = config & PT_CMD_RET_PANEL_ELMNT_SZ_MASK; + + elem -= actual_read_len; + elem_offset = actual_read_len; + while (elem > 0) { +#ifdef PT_ENABLE_MAX_ELEN + if (elem > PT_MAX_ELEN) { + rc = pt_ret_scan_data_cmd_(dev, elem_offset, + PT_MAX_ELEN, dad->heatmap.data_type, NULL, &config, + &actual_read_len, buf_offset); + } else { + rc = pt_ret_scan_data_cmd_(dev, elem_offset, elem, + dad->heatmap.data_type, NULL, &config, + &actual_read_len, buf_offset); + } +#else + + rc = pt_ret_scan_data_cmd_(dev, elem_offset, elem, + dad->heatmap.data_type, NULL, &config, + &actual_read_len, buf_offset); +#endif + if (rc) + goto release_exclusive; + + if (!actual_read_len) + break; + + length += actual_read_len * element_size; + buf_offset = dad->ic_buf + length; + elem -= actual_read_len; + elem_offset += actual_read_len; + } + + /* Reconstruct cmd header */ + put_unaligned_le16(length, &dad->ic_buf[0]); + put_unaligned_le16(elem_offset, &dad->ic_buf[7]); + +release_exclusive: + rc1 = cmd->release_exclusive(dev); +put_runtime: + pm_runtime_put(dev); + + if (rc) + goto release_mutex; + if (cd->show_timestamp) + print_idx += scnprintf(buf_out, TTHE_TUNER_MAX_BUF, + "[%u] PT_DATA:", pt_get_time_stamp()); + else + print_idx += scnprintf(buf_out, TTHE_TUNER_MAX_BUF, + "PT_DATA:"); + for (i = 0; i < length; i++) + print_idx += scnprintf(buf_out + print_idx, + TTHE_TUNER_MAX_BUF - print_idx, + "%02X ", dad->ic_buf[i]); + print_idx += scnprintf(buf_out + print_idx, + TTHE_TUNER_MAX_BUF - print_idx, + ":(%d bytes)\n", length); + rc = simple_read_from_buffer(buf, count, ppos, buf_out, print_idx); + print_idx = rc; + +release_mutex: + mutex_unlock(&dad->debugfs_lock); + return print_idx; +} + +/******************************************************************************* + * FUNCTION: tthe_get_panel_data_debugfs_write + * + * SUMMARY: Store the panel data type to retrieve and size of panel data, the + * read method will perform. + * + * RETURN: Size of debugfs data write + * + * PARAMETERS: + * *filp - file pointer to debugfs file + * *buf - the user space buffer to write to + * count - the maximum number of bytes to write + * *ppos - the current position in the buffer + ******************************************************************************/ +static ssize_t tthe_get_panel_data_debugfs_write(struct file *filp, + const char __user *buf, size_t count, loff_t *ppos) +{ + struct pt_device_access_data *dad = filp->private_data; + struct device *dev = dad->dev; + ssize_t length; + int max_read; + u32 input_data[8]; + u8 *buf_in = dad->panel_scan_data_buf; + int ret; + + mutex_lock(&dad->debugfs_lock); + ret = copy_from_user(buf_in + (*ppos), buf, count); + if (ret) + goto exit; + buf_in[count] = 0; + + length = cmd->parse_sysfs_input(dev, buf_in, count, input_data, + ARRAY_SIZE(input_data)); + if (length <= 0) { + pt_debug(dev, DL_ERROR, + "%s: %s Group Data store\n", + __func__, "Malformed input for"); + goto exit; + } + + /* update parameter value */ + dad->heatmap.num_element = input_data[3] + (input_data[4] << 8); + dad->heatmap.data_type = input_data[5]; + + if (input_data[6] > 0) + dad->heatmap.scan_start = true; + else + dad->heatmap.scan_start = false; + + /* elem can not be bigger then buffer size */ + max_read = PT_CMD_RET_PANEL_HDR; + max_read += dad->heatmap.num_element * PT_CMD_RET_PANEL_ELMNT_SZ_MAX; + + if (max_read >= PT_MAX_PRBUF_SIZE) { + dad->heatmap.num_element = + (PT_MAX_PRBUF_SIZE - PT_CMD_RET_PANEL_HDR) + / PT_CMD_RET_PANEL_ELMNT_SZ_MAX; + pt_debug(dev, DL_INFO, "%s: Will get %d element\n", + __func__, dad->heatmap.num_element); + } + +exit: + mutex_unlock(&dad->debugfs_lock); + pt_debug(dev, DL_INFO, "%s: return count=%zu\n", + __func__, count); + return count; +} + +/******************************************************************************* + * FUNCTION: tthe_get_panel_data_debugfs_open + * + * SUMMARY: Open the get_panel_data debugfs node to initialize. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *inode - pointer to inode structure + * *filp - pointer to file structure + ******************************************************************************/ +static int tthe_get_panel_data_debugfs_open(struct inode *inode, + struct file *filp) +{ + struct pt_device_access_data *dad = inode->i_private; + + mutex_lock(&dad->debugfs_lock); + + if (dad->tthe_get_panel_data_is_open) { + mutex_unlock(&dad->debugfs_lock); + return -EBUSY; + } + + filp->private_data = inode->i_private; + + dad->tthe_get_panel_data_is_open = 1; + mutex_unlock(&dad->debugfs_lock); + return 0; +} + +/******************************************************************************* + * FUNCTION: tthe_get_panel_data_debugfs_close + * + * SUMMARY: Close the get_panel_data debugfs node to free pointer. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *inode - pointer to inode structure + * *filp - pointer to file structure + ******************************************************************************/ +static int tthe_get_panel_data_debugfs_close(struct inode *inode, + struct file *filp) +{ + struct pt_device_access_data *dad = filp->private_data; + + mutex_lock(&dad->debugfs_lock); + filp->private_data = NULL; + dad->tthe_get_panel_data_is_open = 0; + mutex_unlock(&dad->debugfs_lock); + + return 0; +} + +static const struct file_operations tthe_get_panel_data_fops = { + .open = tthe_get_panel_data_debugfs_open, + .release = tthe_get_panel_data_debugfs_close, + .read = tthe_get_panel_data_debugfs_read, + .write = tthe_get_panel_data_debugfs_write, +}; +#endif + +/******************************************************************************* + * FUNCTION: pt_setup_sysfs + * + * SUMMARY: Creates all device test dependent sysfs nodes owned by the + * device access file. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int pt_setup_sysfs(struct device *dev) +{ + struct pt_device_access_data *dad + = pt_get_device_access_data(dev); + int rc = 0; + + pt_debug(dev, DL_INFO, "Entering %s\n", __func__); + + dad->base_dentry = debugfs_create_dir(dev_name(dev), NULL); + if (IS_ERR_OR_NULL(dad->base_dentry)) { + pt_debug(dev, DL_ERROR, + "%s: Error, could not create base directory\n", + __func__); + goto exit; + } + + dad->mfg_test_dentry = debugfs_create_dir("mfg_test", + dad->base_dentry); + if (IS_ERR_OR_NULL(dad->mfg_test_dentry)) { + pt_debug(dev, DL_ERROR, + "%s: Error, could not create mfg_test directory\n", + __func__); + goto unregister_base_dir; + } + + dad->panel_scan_debugfs = debugfs_create_file( + "panel_scan", 0644, dad->mfg_test_dentry, dad, + &panel_scan_fops); + if (IS_ERR_OR_NULL(dad->panel_scan_debugfs)) { + pt_debug(dev, DL_ERROR, + "%s: Error, could not create panel_scan\n", + __func__); + goto unregister_base_dir; + } + + if (IS_ERR_OR_NULL(debugfs_create_file("get_idac", 0600, + dad->mfg_test_dentry, dad, &get_idac_debugfs_fops))) { + pt_debug(dev, DL_ERROR, + "%s: Error, could not create get_idac\n", + __func__); + goto unregister_base_dir; + } + + if (IS_ERR_OR_NULL(debugfs_create_file("auto_shorts", 0400, + dad->mfg_test_dentry, dad, + &auto_shorts_debugfs_fops))) { + pt_debug(dev, DL_ERROR, + "%s: Error, could not create auto_shorts\n", + __func__); + goto unregister_base_dir; + } + + if (IS_ERR_OR_NULL(debugfs_create_file("opens", 0400, + dad->mfg_test_dentry, dad, &opens_debugfs_fops))) { + pt_debug(dev, DL_ERROR, + "%s: Error, could not create opens\n", + __func__); + goto unregister_base_dir; + } + + if (IS_ERR_OR_NULL(debugfs_create_file("calibrate_ext", + 0600, dad->mfg_test_dentry, + dad, &calibrate_ext_debugfs_fops))) { + pt_debug(dev, DL_ERROR, + "%s: Error, could not create calibrate_ext\n", + __func__); + goto unregister_base_dir; + } + + if (IS_ERR_OR_NULL(debugfs_create_file("calibrate", 0600, + dad->mfg_test_dentry, dad, &calibrate_debugfs_fops))) { + pt_debug(dev, DL_ERROR, + "%s: Error, could not create calibrate\n", + __func__); + goto unregister_base_dir; + } + + if (IS_ERR_OR_NULL(debugfs_create_file("baseline", 0600, + dad->mfg_test_dentry, dad, &baseline_debugfs_fops))) { + pt_debug(dev, DL_ERROR, + "%s: Error, could not create baseline\n", + __func__); + goto unregister_base_dir; + } + + if (IS_ERR_OR_NULL(debugfs_create_file("cm_panel", 0400, + dad->mfg_test_dentry, dad, &cm_panel_debugfs_fops))) { + pt_debug(dev, DL_ERROR, + "%s: Error, could not create cm_panel\n", + __func__); + goto unregister_base_dir; + } + + if (IS_ERR_OR_NULL(debugfs_create_file("cp_panel", 0400, + dad->mfg_test_dentry, dad, &cp_panel_debugfs_fops))) { + pt_debug(dev, DL_ERROR, + "%s: Error, could not create cp_panel\n", + __func__); + goto unregister_base_dir; + } + + if (IS_ERR_OR_NULL(debugfs_create_file("cm_button", 0400, + dad->mfg_test_dentry, dad, &cm_button_debugfs_fops))) { + pt_debug(dev, DL_ERROR, + "%s: Error, could not create cm_button\n", + __func__); + goto unregister_base_dir; + } + + if (IS_ERR_OR_NULL(debugfs_create_file("cp_button", 0400, + dad->mfg_test_dentry, dad, &cp_button_debugfs_fops))) { + pt_debug(dev, DL_ERROR, + "%s: Error, could not create cp_button\n", + __func__); + goto unregister_base_dir; + } + + if (IS_ERR_OR_NULL(debugfs_create_file("fw_self_test", 0600, + dad->mfg_test_dentry, dad, &fw_self_test_debugfs_fops))) { + pt_debug(dev, DL_ERROR, + "%s: Error, could not create fw_self_test\n", + __func__); + goto unregister_base_dir; + } + + dad->cmcp_results_debugfs = debugfs_create_file("cmcp_results", 0644, + dad->mfg_test_dentry, dad, &cmcp_results_debugfs_fops); + if (IS_ERR_OR_NULL(dad->cmcp_results_debugfs)) { + pt_debug(dev, DL_ERROR, + "%s: Error, could not create cmcp_results\n", + __func__); + dad->cmcp_results_debugfs = NULL; + goto unregister_base_dir; + } + +#ifdef TTHE_TUNER_SUPPORT + dad->tthe_get_panel_data_debugfs = debugfs_create_file( + PT_TTHE_TUNER_GET_PANEL_DATA_FILE_NAME, + 0644, NULL, dad, &tthe_get_panel_data_fops); + if (IS_ERR_OR_NULL(dad->tthe_get_panel_data_debugfs)) { + pt_debug(dev, DL_ERROR, + "%s: Error, could not create get_panel_data\n", + __func__); + dad->tthe_get_panel_data_debugfs = NULL; + goto unregister_base_dir; + } +#endif + + rc = device_create_file(dev, &dev_attr_cmcp_test); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Error, could not create cmcp_test\n", + __func__); + goto unregister_base_dir; + } + + rc = device_create_file(dev, &dev_attr_cmcp_threshold_loading); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Error, could not create cmcp_threshold_loading\n", + __func__); + goto unregister_cmcp_test; + } + + rc = device_create_bin_file(dev, &bin_attr_cmcp_threshold_data); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Error, could not create cmcp_threshold_data\n", + __func__); + goto unregister_cmcp_thresold_loading; + } + + dad->sysfs_nodes_created = true; + return rc; + +unregister_cmcp_thresold_loading: + device_remove_file(dev, &dev_attr_cmcp_threshold_loading); +unregister_cmcp_test: + device_remove_file(dev, &dev_attr_cmcp_test); +unregister_base_dir: + debugfs_remove_recursive(dad->base_dentry); +exit: + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_setup_cmcp_attention + * + * SUMMARY: Function to be registered to TTDL attention list to setup sysfs and + * parse threshold file for device test. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int pt_setup_cmcp_attention(struct device *dev) +{ + struct pt_device_access_data *dad + = pt_get_device_access_data(dev); + int rc = 0; + + dad->si = cmd->request_sysinfo(dev); + if (!dad->si) + return -EINVAL; + + rc = pt_setup_sysfs(dev); + schedule_work(&dad->cmcp_threshold_update); + + cmd->unsubscribe_attention(dev, PT_ATTEN_STARTUP, + PT_DEVICE_ACCESS_NAME, pt_setup_cmcp_attention, + 0); + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_cmcp_parse_threshold_file + * + * SUMMARY: Parse cmcp threshold file and store it to the structure. + * + * PARAMETERS: + * *fw - pointer to firmware structure + * *context - expected read length of the response + ******************************************************************************/ +static void pt_cmcp_parse_threshold_file(const struct firmware *fw, + void *context) +{ + struct device *dev = context; + struct pt_device_access_data *dad = + pt_get_device_access_data(dev); + + if (!fw) { + pt_debug(dev, DL_WARN, + "%s: No builtin cmcp threshold file\n", + __func__); + goto exit; + } + + if (!fw->data || !fw->size) { + pt_debug(dev, DL_ERROR, + "%s: Invalid builtin cmcp threshold file\n", + __func__); + goto exit; + } + + pt_debug(dev, DL_WARN, "%s: Found cmcp threshold file.\n", + __func__); + + pt_parse_cmcp_threshold_file_common(dev, &fw->data[0], fw->size); + + dad->builtin_cmcp_threshold_status = 0; + release_firmware(fw); + return; + +exit: + if (fw) + release_firmware(fw); + + dad->builtin_cmcp_threshold_status = -EINVAL; +} + +/******************************************************************************* + * FUNCTION: pt_device_access_user_command + * + * SUMMARY: Wrapper function to call pt_cmcp_parse_threshold_file() by firmware + * class function. + * + * PARAMETERS: + * *cmcp_threshold_update - pointer to work_struct structure + ******************************************************************************/ +static void pt_parse_cmcp_threshold_builtin( + struct work_struct *cmcp_threshold_update) +{ + struct pt_device_access_data *dad = + container_of(cmcp_threshold_update, + struct pt_device_access_data, + cmcp_threshold_update); + struct device *dev = dad->dev; + struct pt_core_data *cd = dev_get_drvdata(dev); + const struct firmware *fw_entry = NULL; + int retval; + + dad->si = cmd->request_sysinfo(dev); + if (!dad->si) { + pt_debug(dev, DL_ERROR, + "%s: Fail get sysinfo pointer from core\n", + __func__); + return; + } + + pt_debug(dev, DL_INFO, + "%s: Enabling cmcp threshold class loader built-in\n", + __func__); + + /* Open threshold file */ + mutex_lock(&cd->firmware_class_lock); +#if (KERNEL_VERSION(3, 13, 0) > LINUX_VERSION_CODE) + retval = request_firmware(&fw_entry, CMCP_THRESHOLD_FILE_NAME, dev); +#else + retval = request_firmware_direct(&fw_entry, + CMCP_THRESHOLD_FILE_NAME, dev); +#endif + if (retval < 0) { + pt_debug(dev, DL_WARN, + "%s: Failed loading cmcp threshold file, attempting legacy file\n", + __func__); + /* Try legacy file name */ +#if (KERNEL_VERSION(3, 13, 0) > LINUX_VERSION_CODE) + retval = request_firmware(&fw_entry, + PT_CMCP_THRESHOLD_FILE_NAME, dev); +#else + retval = request_firmware_direct(&fw_entry, + PT_CMCP_THRESHOLD_FILE_NAME, dev); +#endif + if (retval < 0) { + mutex_unlock(&cd->firmware_class_lock); + dad->builtin_cmcp_threshold_status = -EINVAL; + pt_debug(dev, DL_WARN, + "%s: Fail request cmcp threshold class file load\n", + __func__); + goto exit; + } + } + pt_cmcp_parse_threshold_file(fw_entry, dev); + + mutex_unlock(&cd->firmware_class_lock); + +exit: + return; +} + +/******************************************************************************* + * FUNCTION: pt_device_access_probe + * + * SUMMARY: The probe function for the device access + * + * PARAMETERS: + * *dev - pointer to device structure + * **data - double pointer to pt_device_access_data data to be created here + ******************************************************************************/ +static int pt_device_access_probe(struct device *dev, void **data) +{ + struct pt_device_access_data *dad; + struct configuration *configurations; + struct cmcp_data *cmcp_info; + struct result *result; + + int tx_num = MAX_TX_SENSORS; + int rx_num = MAX_RX_SENSORS; + int btn_num = MAX_BUTTONS; + + struct test_case_field *test_case_field_array; + struct test_case_search *test_case_search_array; + int rc = 0; + + dad = kzalloc(sizeof(*dad), GFP_KERNEL); + if (!dad) { + rc = -ENOMEM; + goto pt_device_access_probe_data_failed; + } + + configurations = + kzalloc(sizeof(*configurations), GFP_KERNEL); + if (!configurations) { + rc = -ENOMEM; + goto pt_device_access_probe_configs_failed; + } + dad->configs = configurations; + + cmcp_info = kzalloc(sizeof(*cmcp_info), GFP_KERNEL); + if (!cmcp_info) { + rc = -ENOMEM; + goto pt_device_access_probe_cmcp_info_failed; + } + dad->cmcp_info = cmcp_info; + + cmcp_info->tx_num = tx_num; + cmcp_info->rx_num = rx_num; + cmcp_info->btn_num = btn_num; + + result = kzalloc(sizeof(*result), GFP_KERNEL); + if (!result) { + rc = -ENOMEM; + goto pt_device_access_probe_result_failed; + } + dad->result = result; + + test_case_field_array = + kzalloc(sizeof(*test_case_field_array) * MAX_CASE_NUM, + GFP_KERNEL); + if (!test_case_field_array) { + rc = -ENOMEM; + goto pt_device_access_probe_field_array_failed; + } + + test_case_search_array = + kzalloc(sizeof(*test_case_search_array) * MAX_CASE_NUM, + GFP_KERNEL); + if (!test_case_search_array) { + rc = -ENOMEM; + goto pt_device_access_probe_search_array_failed; + } + + cmcp_info->gd_sensor_col = (struct gd_sensor *) + kzalloc(tx_num * sizeof(struct gd_sensor), GFP_KERNEL); + if (cmcp_info->gd_sensor_col == NULL) + goto pt_device_access_probe_gd_sensor_col_failed; + + cmcp_info->gd_sensor_row = (struct gd_sensor *) + kzalloc(rx_num * sizeof(struct gd_sensor), GFP_KERNEL); + if (cmcp_info->gd_sensor_row == NULL) + goto pt_device_access_probe_gd_sensor_row_failed; + + cmcp_info->cm_data_panel = + kzalloc((tx_num * rx_num + 1) * sizeof(int32_t), GFP_KERNEL); + if (cmcp_info->cm_data_panel == NULL) + goto pt_device_access_probe_cm_data_panel_failed; + + cmcp_info->cp_tx_data_panel = + kzalloc(tx_num * sizeof(int32_t), GFP_KERNEL); + if (cmcp_info->cp_tx_data_panel == NULL) + goto pt_device_access_probe_cp_tx_data_panel_failed; + + cmcp_info->cp_tx_cal_data_panel = + kzalloc(tx_num * sizeof(int32_t), GFP_KERNEL); + if (cmcp_info->cp_tx_cal_data_panel == NULL) + goto pt_device_access_probe_cp_tx_cal_data_panel_failed; + + cmcp_info->cp_rx_data_panel = + kzalloc(rx_num * sizeof(int32_t), GFP_KERNEL); + if (cmcp_info->cp_rx_data_panel == NULL) + goto pt_device_access_probe_cp_rx_data_panel_failed; + + cmcp_info->cp_rx_cal_data_panel = + kzalloc(rx_num * sizeof(int32_t), GFP_KERNEL); + if (cmcp_info->cp_rx_cal_data_panel == NULL) + goto pt_device_access_probe_cp_rx_cal_data_panel_failed; + + cmcp_info->cm_btn_data = kcalloc(btn_num, sizeof(int32_t), GFP_KERNEL); + if (cmcp_info->cm_btn_data == NULL) + goto pt_device_access_probe_cm_btn_data_failed; + + cmcp_info->cp_btn_data = kcalloc(btn_num, sizeof(int32_t), GFP_KERNEL); + if (cmcp_info->cp_btn_data == NULL) + goto pt_device_access_probe_cp_btn_data_failed; + + cmcp_info->cm_sensor_column_delta = + kzalloc(rx_num * tx_num * sizeof(int32_t), GFP_KERNEL); + if (cmcp_info->cm_sensor_column_delta == NULL) + goto pt_device_access_probe_cm_sensor_column_delta_failed; + + cmcp_info->cm_sensor_row_delta = + kzalloc(tx_num * rx_num * sizeof(int32_t), GFP_KERNEL); + if (cmcp_info->cm_sensor_row_delta == NULL) + goto pt_device_access_probe_cm_sensor_row_delta_failed; + + mutex_init(&dad->sysfs_lock); + mutex_init(&dad->cmcp_threshold_lock); + dad->dev = dev; +#ifdef TTHE_TUNER_SUPPORT + mutex_init(&dad->debugfs_lock); + dad->heatmap.num_element = 200; +#endif + *data = dad; + + dad->test_field_array = test_case_field_array; + dad->test_search_array = test_case_search_array; + dad->test_executed = 0; + + dad->cal_ext_data.mode = PT_CAL_EXT_MODE_UNDEFINED; + dad->panel_scan_retrieve_id = 0; + dad->panel_scan_type_id = 0; + + INIT_WORK(&dad->cmcp_threshold_update, pt_parse_cmcp_threshold_builtin); + + /* get sysinfo */ + dad->si = cmd->request_sysinfo(dev); + if (dad->si) { + rc = pt_setup_sysfs(dev); + if (rc) + goto pt_device_access_setup_sysfs_failed; + } else { + pt_debug(dev, DL_ERROR, + "%s: Fail get sysinfo pointer from core p=%p\n", + __func__, dad->si); + cmd->subscribe_attention(dev, PT_ATTEN_STARTUP, + PT_DEVICE_ACCESS_NAME, + pt_setup_cmcp_attention, 0); + } + + schedule_work(&dad->cmcp_threshold_update); + + return 0; + +pt_device_access_setup_sysfs_failed: + kfree(cmcp_info->cm_sensor_row_delta); +pt_device_access_probe_cm_sensor_row_delta_failed: + kfree(cmcp_info->cm_sensor_column_delta); +pt_device_access_probe_cm_sensor_column_delta_failed: + kfree(cmcp_info->cp_btn_data); +pt_device_access_probe_cp_btn_data_failed: + kfree(cmcp_info->cm_btn_data); +pt_device_access_probe_cm_btn_data_failed: + kfree(cmcp_info->cp_rx_cal_data_panel); +pt_device_access_probe_cp_rx_cal_data_panel_failed: + kfree(cmcp_info->cp_rx_data_panel); +pt_device_access_probe_cp_rx_data_panel_failed: + kfree(cmcp_info->cp_tx_cal_data_panel); +pt_device_access_probe_cp_tx_cal_data_panel_failed: + kfree(cmcp_info->cp_tx_data_panel); +pt_device_access_probe_cp_tx_data_panel_failed: + kfree(cmcp_info->cm_data_panel); +pt_device_access_probe_cm_data_panel_failed: + kfree(cmcp_info->gd_sensor_row); +pt_device_access_probe_gd_sensor_row_failed: + kfree(cmcp_info->gd_sensor_col); +pt_device_access_probe_gd_sensor_col_failed: + kfree(test_case_search_array); +pt_device_access_probe_search_array_failed: + kfree(test_case_field_array); +pt_device_access_probe_field_array_failed: + kfree(result); +pt_device_access_probe_result_failed: + kfree(cmcp_info); +pt_device_access_probe_cmcp_info_failed: + kfree(configurations); +pt_device_access_probe_configs_failed: + kfree(dad); +pt_device_access_probe_data_failed: + pt_debug(dev, DL_ERROR, "%s failed.\n", __func__); + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_device_access_release + * + * SUMMARY: Remove function for device_access module that does following + * cleanup: + * - Unsubscibe all registered attention tasks + * - Removes all created sysfs nodes + * - Frees all pointers + * + * PARAMETERS: + * *dev - pointer to device structure + * *data - pointer to the device_access data + ******************************************************************************/ +static void pt_device_access_release(struct device *dev, void *data) +{ + struct pt_device_access_data *dad = data; + + if (dad->sysfs_nodes_created) { + debugfs_remove(dad->cmcp_results_debugfs); + debugfs_remove_recursive(dad->base_dentry); +#ifdef TTHE_TUNER_SUPPORT + debugfs_remove(dad->tthe_get_panel_data_debugfs); +#endif + device_remove_file(dev, &dev_attr_cmcp_test); + device_remove_file(dev, &dev_attr_cmcp_threshold_loading); + device_remove_bin_file(dev, &bin_attr_cmcp_threshold_data); + kfree(dad->cmcp_threshold_data); + } else { + cmd->unsubscribe_attention(dev, PT_ATTEN_STARTUP, + PT_DEVICE_ACCESS_NAME, + pt_setup_cmcp_attention, 0); + } + + kfree(dad->test_search_array); + kfree(dad->test_field_array); + kfree(dad->configs); + pt_free_cmcp_buf(dad->cmcp_info); + kfree(dad->cmcp_info); + kfree(dad->result); + kfree(dad); +} + +static struct pt_module device_access_module = { + .name = PT_DEVICE_ACCESS_NAME, + .probe = pt_device_access_probe, + .release = pt_device_access_release, +}; + +/******************************************************************************* + * FUNCTION: pt_device_access_init + * + * SUMMARY: Initialize function for device access module which to register + * device_access_module into TTDL module list. + * + * RETURN: + * 0 = success + * !0 = failure + ******************************************************************************/ +static int __init pt_device_access_init(void) +{ + int rc; + + cmd = pt_get_commands(); + if (!cmd) + return -EINVAL; + + rc = pt_register_module(&device_access_module); + if (rc) { + pr_err("%s: Error, failed registering module\n", + __func__); + return rc; + } + + pr_info("%s: Parade TTSP Device Access Driver (Built %s) rc = %d\n", + __func__, PT_DRIVER_VERSION, rc); + return 0; +} +module_init(pt_device_access_init); + +/******************************************************************************* + * FUNCTION: pt_device_access_exit + * + * SUMMARY: Exit function for device access module which to unregister + * device_access_module from TTDL module list. + * + ******************************************************************************/ +static void __exit pt_device_access_exit(void) +{ + pt_unregister_module(&device_access_module); +} +module_exit(pt_device_access_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Parade TrueTouch(R) Standard Product Device Access Driver"); +MODULE_AUTHOR("Parade Technologies "); diff --git a/qcom/opensource/touch-drivers/pt/pt_devtree.c b/qcom/opensource/touch-drivers/pt/pt_devtree.c new file mode 100644 index 0000000000..5482c1d224 --- /dev/null +++ b/qcom/opensource/touch-drivers/pt/pt_devtree.c @@ -0,0 +1,1128 @@ +/* + * pt_devtree.c + * Parade TrueTouch(TM) Standard Product Device Tree Support Module. + * For use with Parade touchscreen controllers. + * Supported parts include: + * TMA5XX + * TMA448 + * TMA445A + * TT21XXX + * TT31XXX + * TT4XXXX + * TT7XXX + * TC3XXX + * + * Copyright (C) 2015-2020 Parade Technologies + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * 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. + * + * Contact Parade Technologies at www.paradetech.com + */ + +#include +#include +#include +#include +#include "pt_platform.h" +#include "pt_regs.h" + +#define MAX_NAME_LENGTH 64 + +static bool is_create_and_get_pdata; + +enum pt_device_type { + DEVICE_MT, + DEVICE_BTN, + DEVICE_PROXIMITY, + DEVICE_TYPE_MAX, +}; + +struct pt_device_pdata_func { + void * (*create_and_get_pdata)(struct device_node *dn); + void (*free_pdata)(void *ptr); +}; + +struct pt_pdata_ptr { + void **pdata; +}; + +#ifdef ENABLE_VIRTUAL_KEYS +static struct kobject *board_properties_kobj; + +struct pt_virtual_keys { + struct kobj_attribute kobj_attr; + u16 *data; + int size; +}; +#endif + +struct pt_extended_mt_platform_data { + struct pt_mt_platform_data pdata; +#ifdef ENABLE_VIRTUAL_KEYS + struct pt_virtual_keys vkeys; +#endif +}; + +/******************************************************************************* + * FUNCTION: get_inp_dev_name + * + * SUMMARY: Get the name of input device from dts. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev_node - pointer to device_node structure + * **inp_dev_name - double pointer to the name of input device + ******************************************************************************/ +static inline int get_inp_dev_name(struct device_node *dev_node, + const char **inp_dev_name) +{ + return of_property_read_string(dev_node, "parade,inp_dev_name", + inp_dev_name); +} + +/******************************************************************************* + * FUNCTION: create_and_get_u16_array + * + * SUMMARY: Create and get u16 array from dts. + * + * RETURN: + * success: the pointer of the created array + * fail : error code with type of error pointer + * + * PARAMETERS: + * *dev_node - pointer to device_node structure + * *name - name of device node + * size - number of u16 array elements + ******************************************************************************/ +static s16 *create_and_get_u16_array(struct device_node *dev_node, + const char *name, int *size) +{ + const __be32 *values; + s16 *val_array; + int len; + int sz; + int rc; + int i; + + values = of_get_property(dev_node, name, &len); + if (values == NULL) + return NULL; + + sz = len / sizeof(u32); + pr_debug("%s: %s size:%d\n", __func__, name, sz); + + val_array = kcalloc(sz, sizeof(s16), GFP_KERNEL); + if (!val_array) { + rc = -ENOMEM; + goto fail; + } + + for (i = 0; i < sz; i++) + val_array[i] = (s16)be32_to_cpup(values++); + + *size = sz; + + return val_array; + +fail: + return ERR_PTR(rc); +} + +/******************************************************************************* + * FUNCTION: create_and_get_touch_framework + * + * SUMMARY: Create and get touch framework structure from dts. + * + * RETURN: + * success: the pointer of the touch framework data + * fail : error code with type of error pointer + * + * PARAMETERS: + * *dev_node - pointer to device_node structure + ******************************************************************************/ +static struct touch_framework *create_and_get_touch_framework( + struct device_node *dev_node) +{ + struct touch_framework *frmwrk; + s16 *abs; + int size; + int rc; + + abs = create_and_get_u16_array(dev_node, "parade,abs", &size); + if (IS_ERR_OR_NULL(abs)) + return (void *)abs; + + /* Check for valid abs size */ + if (size % PT_NUM_ABS_SET) { + rc = -EINVAL; + goto fail_free_abs; + } + + frmwrk = kzalloc(sizeof(*frmwrk), GFP_KERNEL); + if (!frmwrk) { + rc = -ENOMEM; + goto fail_free_abs; + } + + frmwrk->abs = abs; + frmwrk->size = size; + + return frmwrk; + +fail_free_abs: + kfree(abs); + + return ERR_PTR(rc); +} + +/******************************************************************************* + * FUNCTION: free_touch_framework + * + * SUMMARY: Free all the pointer of touch framework structure. + * + * PARAMETERS: + * *frmwrk - pointer to touch framework structure + ******************************************************************************/ +static void free_touch_framework(struct touch_framework *frmwrk) +{ + kfree(frmwrk->abs); + kfree(frmwrk); +} + +#ifdef ENABLE_VIRTUAL_KEYS +#define VIRTUAL_KEY_ELEMENT_SIZE 5 +/******************************************************************************* + * FUNCTION: virtual_keys_show + * + * SUMMARY: Show method for the board_properties sysfs node that will show the + * information for all virtual keys + * + * RETURN: size of data written to sysfs node + * + * PARAMETERS: + * *kobj - pointer to kobject structure + * *attr - pointer to kobject attributes + * *buf - pointer to print output buffer + ******************************************************************************/ +static ssize_t virtual_keys_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct pt_virtual_keys *vkeys = container_of(attr, + struct pt_virtual_keys, kobj_attr); + u16 *data = vkeys->data; + int size = vkeys->size; + int index; + int i; + + index = 0; + for (i = 0; i < size; i += VIRTUAL_KEY_ELEMENT_SIZE) + index += scnprintf(buf + index, PT_MAX_PRBUF_SIZE - index, + "0x01:%d:%d:%d:%d:%d\n", + data[i], data[i+1], data[i+2], data[i+3], data[i+4]); + + return index; +} + +/******************************************************************************* + * FUNCTION: setup_virtual_keys + * + * SUMMARY: Create virtual key data array from dts and set up dynamic sysfs for + * board_properties node. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev_node - pointer to device_node structure + * **inp_dev_name - double pointer to the name of input device + * *vkeys - pointer to virtual key structure + ******************************************************************************/ +static int setup_virtual_keys(struct device_node *dev_node, + const char *inp_dev_name, struct pt_virtual_keys *vkeys) +{ + char *name; + u16 *data; + int size; + int rc; + + data = create_and_get_u16_array(dev_node, "parade,virtual_keys", &size); + if (data == NULL) + return 0; + else if (IS_ERR(data)) { + rc = PTR_ERR(data); + goto fail; + } + + /* Check for valid virtual keys size */ + if (size % VIRTUAL_KEY_ELEMENT_SIZE) { + rc = -EINVAL; + goto fail_free_data; + } + + name = kzalloc(MAX_NAME_LENGTH, GFP_KERNEL); + if (!name) { + rc = -ENOMEM; + goto fail_free_data; + } + + snprintf(name, MAX_NAME_LENGTH, "virtualkeys.%s", inp_dev_name); + + vkeys->data = data; + vkeys->size = size; + + /* TODO: Instantiate in board file and export it */ + if (board_properties_kobj == NULL) + board_properties_kobj = + kobject_create_and_add("board_properties", NULL); + if (board_properties_kobj == NULL) { + pr_err("%s: Cannot get board_properties kobject!\n", __func__); + rc = -EINVAL; + goto fail_free_name; + } + + /* Initialize dynamic SysFs attribute */ + sysfs_attr_init(&vkeys->kobj_attr.attr); + vkeys->kobj_attr.attr.name = name; + vkeys->kobj_attr.attr.mode = 0444; + vkeys->kobj_attr.show = virtual_keys_show; + + rc = sysfs_create_file(board_properties_kobj, &vkeys->kobj_attr.attr); + if (rc) + goto fail_del_kobj; + + return 0; + +fail_del_kobj: + kobject_del(board_properties_kobj); +fail_free_name: + kfree(name); + vkeys->kobj_attr.attr.name = NULL; +fail_free_data: + kfree(data); + vkeys->data = NULL; +fail: + return rc; +} + +/******************************************************************************* + * FUNCTION: free_virtual_keys + * + * SUMMARY: Remove board_properties node and free all pointers. + * + * PARAMETERS: + * *vkeys - pointer to virtual key structure + ******************************************************************************/ +static void free_virtual_keys(struct pt_virtual_keys *vkeys) +{ + if (board_properties_kobj) + sysfs_remove_file(board_properties_kobj, + &vkeys->kobj_attr.attr); + + + kobject_del(board_properties_kobj); + board_properties_kobj = NULL; + + kfree(vkeys->data); + kfree(vkeys->kobj_attr.attr.name); +} +#endif + +/******************************************************************************* + * FUNCTION: create_and_get_mt_pdata + * + * SUMMARY: Create and get touch platform data from dts.Touch framework and + * virtual keys are set up in this function. + * + * RETURN: + * success: the pointer of the platform data + * fail : error code with type of error pointer + * + * PARAMETERS: + * *dev_node - pointer to device_node structure + ******************************************************************************/ +static void *create_and_get_mt_pdata(struct device_node *dev_node) +{ + struct pt_extended_mt_platform_data *ext_pdata; + struct pt_mt_platform_data *pdata; + u32 value; + int rc; + + ext_pdata = kzalloc(sizeof(*ext_pdata), GFP_KERNEL); + if (!ext_pdata) { + rc = -ENOMEM; + goto fail; + } + + pdata = &ext_pdata->pdata; + + rc = get_inp_dev_name(dev_node, &pdata->inp_dev_name); + if (rc) + goto fail_free_pdata; + + /* Optional fields */ + rc = of_property_read_u32(dev_node, "parade,flags", &value); + if (!rc) + pdata->flags = value; + + rc = of_property_read_u32(dev_node, "parade,vkeys_x", &value); + if (!rc) + pdata->vkeys_x = value; + + rc = of_property_read_u32(dev_node, "parade,vkeys_y", &value); + if (!rc) + pdata->vkeys_y = value; + + /* Required fields */ + pdata->frmwrk = create_and_get_touch_framework(dev_node); + if (pdata->frmwrk == NULL) { + rc = -EINVAL; + goto fail_free_pdata; + } else if (IS_ERR(pdata->frmwrk)) { + rc = PTR_ERR(pdata->frmwrk); + goto fail_free_pdata; + } +#ifdef ENABLE_VIRTUAL_KEYS + rc = setup_virtual_keys(dev_node, pdata->inp_dev_name, + &ext_pdata->vkeys); + if (rc) { + pr_err("%s: Cannot setup virtual keys!\n", __func__); + goto fail_free_pdata; + } +#endif + return pdata; + +fail_free_pdata: + kfree(ext_pdata); +fail: + return ERR_PTR(rc); +} + +/******************************************************************************* + * FUNCTION: free_mt_pdata + * + * SUMMARY: Free all pointers that include touch framework, virtual keys and + * touch data. + * + * PARAMETERS: + * *vkeys - pointer to virtual key structure + ******************************************************************************/ +static void free_mt_pdata(void *pdata) +{ + struct pt_mt_platform_data *mt_pdata = + (struct pt_mt_platform_data *)pdata; + struct pt_extended_mt_platform_data *ext_mt_pdata = + container_of(mt_pdata, + struct pt_extended_mt_platform_data, pdata); + + free_touch_framework(mt_pdata->frmwrk); +#ifdef ENABLE_VIRTUAL_KEYS + free_virtual_keys(&ext_mt_pdata->vkeys); +#endif + kfree(ext_mt_pdata); +} + +/******************************************************************************* + * FUNCTION: create_and_get_btn_pdata + * + * SUMMARY: Create and get button platform data from dts. + * + * RETURN: + * success: the pointer of the platform data + * fail : error code with type of error pointer + * + * PARAMETERS: + * *dev_node - pointer to device_node structure + ******************************************************************************/ +static void *create_and_get_btn_pdata(struct device_node *dev_node) +{ + struct pt_btn_platform_data *pdata; + int rc; + + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + rc = -ENOMEM; + goto fail; + } + + rc = get_inp_dev_name(dev_node, &pdata->inp_dev_name); + if (rc) + goto fail_free_pdata; + + return pdata; + +fail_free_pdata: + kfree(pdata); +fail: + return ERR_PTR(rc); +} + +/******************************************************************************* + * FUNCTION: free_btn_pdata + * + * SUMMARY: Free all pointers for button platform data. + * + * PARAMETERS: + * *vkeys - pointer to virtual key structure + ******************************************************************************/ +static void free_btn_pdata(void *pdata) +{ + struct pt_btn_platform_data *btn_pdata = + (struct pt_btn_platform_data *)pdata; + + kfree(btn_pdata); +} + +/******************************************************************************* + * FUNCTION: create_and_get_proximity_pdata + * + * SUMMARY: Create and get proximity platform data from dts. + * + * RETURN: + * success: the pointer of the proximity platform data + * fail : error code with type of error pointer + * + * PARAMETERS: + * *dev_node - pointer to device_node structure + ******************************************************************************/ +static void *create_and_get_proximity_pdata(struct device_node *dev_node) +{ + struct pt_proximity_platform_data *pdata; + int rc; + + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + rc = -ENOMEM; + goto fail; + } + + rc = get_inp_dev_name(dev_node, &pdata->inp_dev_name); + if (rc) + goto fail_free_pdata; + + pdata->frmwrk = create_and_get_touch_framework(dev_node); + if (pdata->frmwrk == NULL) { + rc = -EINVAL; + goto fail_free_pdata; + } else if (IS_ERR(pdata->frmwrk)) { + rc = PTR_ERR(pdata->frmwrk); + goto fail_free_pdata; + } + + return pdata; + +fail_free_pdata: + kfree(pdata); +fail: + return ERR_PTR(rc); +} + +/******************************************************************************* + * FUNCTION: free_proximity_pdata + * + * SUMMARY: Free all pointers for proximity platform data. + * + * PARAMETERS: + * *vkeys - pointer to virtual key structure + ******************************************************************************/ +static void free_proximity_pdata(void *pdata) +{ + struct pt_proximity_platform_data *proximity_pdata = + (struct pt_proximity_platform_data *)pdata; + + free_touch_framework(proximity_pdata->frmwrk); + + kfree(proximity_pdata); +} + +static struct pt_device_pdata_func device_pdata_funcs[DEVICE_TYPE_MAX] = { + [DEVICE_MT] = { + .create_and_get_pdata = create_and_get_mt_pdata, + .free_pdata = free_mt_pdata, + }, + [DEVICE_BTN] = { + .create_and_get_pdata = create_and_get_btn_pdata, + .free_pdata = free_btn_pdata, + }, + [DEVICE_PROXIMITY] = { + .create_and_get_pdata = create_and_get_proximity_pdata, + .free_pdata = free_proximity_pdata, + }, +}; + +static struct pt_pdata_ptr pdata_ptr[DEVICE_TYPE_MAX]; + +static const char *device_names[DEVICE_TYPE_MAX] = { + [DEVICE_MT] = "parade,mt", + [DEVICE_BTN] = "parade,btn", + [DEVICE_PROXIMITY] = "parade,proximity", +}; + +/******************************************************************************* + * FUNCTION: set_pdata_ptr + * + * SUMMARY: Set platform data pointer for touch, button, proximity module. + * + * PARAMETERS: + * *pdata - pointer to platform data structure + ******************************************************************************/ +static void set_pdata_ptr(struct pt_platform_data *pdata) +{ + pdata_ptr[DEVICE_MT].pdata = (void **)&pdata->mt_pdata; + pdata_ptr[DEVICE_BTN].pdata = (void **)&pdata->btn_pdata; + pdata_ptr[DEVICE_PROXIMITY].pdata = (void **)&pdata->prox_pdata; +} + +/******************************************************************************* + * FUNCTION: get_device_type + * + * SUMMARY: Determine the device type[mt,btn,proximity] from dts. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev_node - pointer to device_node structure + * *type - pointer to store the device type + ******************************************************************************/ +static int get_device_type(struct device_node *dev_node, + enum pt_device_type *type) +{ + const char *name; + enum pt_device_type t; + int rc; + + rc = of_property_read_string(dev_node, "name", &name); + if (rc) + return rc; + + for (t = 0; t < DEVICE_TYPE_MAX; t++) + if (!strncmp(name, device_names[t], MAX_NAME_LENGTH)) { + *type = t; + return 0; + } + + return -EINVAL; +} + +/******************************************************************************* + * FUNCTION: create_and_get_device_pdata + * + * SUMMARY: Create platform data for mt, btn, proximity module. + * + * RETURN: + * success: the pointer of the platform data + * fail : error code with type of error pointer + * + * PARAMETERS: + * *dev_node - pointer to device_node structure + * type - determine the device type + ******************************************************************************/ +static inline void *create_and_get_device_pdata(struct device_node *dev_node, + enum pt_device_type type) +{ + return device_pdata_funcs[type].create_and_get_pdata(dev_node); +} + +/******************************************************************************* + * FUNCTION: free_device_pdata + * + * SUMMARY: Free platform data for mt, btn, proximity module. + * + * PARAMETERS: + * type - determine the device type + ******************************************************************************/ +static inline void free_device_pdata(enum pt_device_type type) +{ + device_pdata_funcs[type].free_pdata(*pdata_ptr[type].pdata); +} + +/******************************************************************************* + * FUNCTION: create_and_get_touch_setting + * + * SUMMARY: Create and get touch settings from dts. + * + * RETURN: + * success: the pointer of touch settings + * fail : error code with type of error pointer + * + * PARAMETERS: + * *core_node - pointer to device_node structure + * name - name of touch setting + ******************************************************************************/ +static struct touch_settings *create_and_get_touch_setting( + struct device_node *core_node, const char *name) +{ + struct touch_settings *setting; + char *tag_name; + u32 tag_value; + u16 *data; + int size; + int rc; + + data = create_and_get_u16_array(core_node, name, &size); + if (IS_ERR_OR_NULL(data)) + return (void *)data; + + pr_debug("%s: Touch setting:'%s' size:%d\n", __func__, name, size); + + setting = kzalloc(sizeof(*setting), GFP_KERNEL); + if (!setting) { + rc = -ENOMEM; + goto fail_free_data; + } + + setting->data = (u8 *)data; + setting->size = size; + + tag_name = kzalloc(MAX_NAME_LENGTH, GFP_KERNEL); + if (!tag_name) { + rc = -ENOMEM; + goto fail_free_setting; + } + + snprintf(tag_name, MAX_NAME_LENGTH, "%s-tag", name); + + rc = of_property_read_u32(core_node, tag_name, &tag_value); + if (!rc) + setting->tag = tag_value; + + kfree(tag_name); + + return setting; + +fail_free_setting: + kfree(setting); +fail_free_data: + kfree(data); + + return ERR_PTR(rc); +} + +/******************************************************************************* + * FUNCTION: free_touch_setting + * + * SUMMARY: Free touch setting data. + * + * PARAMETERS: + * setting - pointer to touch setting + ******************************************************************************/ +static void free_touch_setting(struct touch_settings *setting) +{ + if (setting) { + kfree(setting->data); + kfree(setting); + } +} + +static char *touch_setting_names[PT_IC_GRPNUM_NUM] = { + NULL, /* PT_IC_GRPNUM_RESERVED */ + "parade,cmd_regs", /* PT_IC_GRPNUM_CMD_REGS */ + "parade,tch_rep", /* PT_IC_GRPNUM_TCH_REP */ + "parade,data_rec", /* PT_IC_GRPNUM_DATA_REC */ + "parade,test_rec", /* PT_IC_GRPNUM_TEST_REC */ + "parade,pcfg_rec", /* PT_IC_GRPNUM_PCFG_REC */ + "parade,tch_parm_val", /* PT_IC_GRPNUM_TCH_PARM_VAL */ + "parade,tch_parm_size", /* PT_IC_GRPNUM_TCH_PARM_SIZE */ + NULL, /* PT_IC_GRPNUM_RESERVED1 */ + NULL, /* PT_IC_GRPNUM_RESERVED2 */ + "parade,opcfg_rec", /* PT_IC_GRPNUM_OPCFG_REC */ + "parade,ddata_rec", /* PT_IC_GRPNUM_DDATA_REC */ + "parade,mdata_rec", /* PT_IC_GRPNUM_MDATA_REC */ + "parade,test_regs", /* PT_IC_GRPNUM_TEST_REGS */ + "parade,btn_keys", /* PT_IC_GRPNUM_BTN_KEYS */ + NULL, /* PT_IC_GRPNUM_TTHE_REGS */ +}; + +/******************************************************************************* + * FUNCTION: pt_check_dsi_panel_dt + * + * SUMMARY: Get the DSI active panel information from dtsi + * + * RETURN: + * 0 = success + * !0 = fail + * + * PARAMETERS: + * np - pointer to device_node structure + * active_panel - name of active DSI panel + ******************************************************************************/ + + +static int pt_check_dsi_panel_dt(struct device_node *np, struct drm_panel **active_panel) +{ + int i; + int count; + struct device_node *node; + struct drm_panel *panel; + + count = of_count_phandle_with_args(np, "panel", NULL); + pr_info("%s: Active panel count: %d\n", __func__, count); + if (count <= 0) + return 0; + + for (i = 0; i < count; i++) { + node = of_parse_phandle(np, "panel", i); + if (node != NULL) + pr_info("%s: Node handle successfully parsed !\n", __func__); + panel = of_drm_find_panel(node); + of_node_put(node); + if (!IS_ERR(panel)) { + pr_info("%s: Active panel selected !\n", __func__); + *active_panel = panel; + return 0; + } + } + pr_err("%s: Active panel NOT selected !\n", __func__); + return PTR_ERR(panel); +} + +/******************************************************************************* + * FUNCTION: create_and_get_core_pdata + * + * SUMMARY: Create and get core module platform data from dts. + * + * RETURN: + * success: the pointer of core platform data + * fail : error code with type of error pointer + * + * PARAMETERS: + * *core_node - pointer to device_node structure + ******************************************************************************/ +static struct pt_core_platform_data *create_and_get_core_pdata( + struct device_node *core_node) +{ + struct pt_core_platform_data *pdata; + u32 value; + int rc; + int i; + + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + rc = -ENOMEM; + goto fail; + } + + /* Required fields */ + value = of_get_named_gpio_flags(core_node, "parade,irq_gpio", 0, &pdata->irq_gpio_flags); + pdata->irq_gpio = value; + + rc = of_property_read_u32(core_node, "parade,hid_desc_register", + &value); + if (rc) + goto fail_free; + pdata->hid_desc_register = value; + + /* Optional fields */ + /* rst_gpio is optional since a platform may use + * power cycling instead of using the XRES pin + */ + value = of_get_named_gpio_flags(core_node, "parade,rst_gpio", 0, &pdata->rst_gpio_flags); + pdata->rst_gpio = value; + + rc = of_property_read_u32(core_node, "parade,ddi_rst_gpio", &value); + if (!rc) + pdata->ddi_rst_gpio = value; + + rc = of_property_read_u32(core_node, "parade,vddi_gpio", &value); + if (!rc) + pdata->vddi_gpio = value; + + rc = of_property_read_u32(core_node, "parade,vcc_gpio", &value); + if (!rc) + pdata->vcc_gpio = value; + + rc = of_property_read_u32(core_node, "parade,avdd_gpio", &value); + if (!rc) + pdata->avdd_gpio = value; + + rc = of_property_read_u32(core_node, "parade,avee_gpio", &value); + if (!rc) + pdata->avee_gpio = value; + + rc = of_property_read_u32(core_node, "parade,level_irq_udelay", &value); + if (!rc) + pdata->level_irq_udelay = value; + + rc = of_property_read_u32(core_node, "parade,vendor_id", &value); + if (!rc) + pdata->vendor_id = value; + + rc = of_property_read_u32(core_node, "parade,product_id", &value); + if (!rc) + pdata->product_id = value; + + rc = of_property_read_u32(core_node, "parade,flags", &value); + if (!rc) + pdata->flags = value; + + rc = of_property_read_u32(core_node, "parade,easy_wakeup_gesture", + &value); + if (!rc) + pdata->easy_wakeup_gesture = (u8)value; + + rc = of_property_read_u32(core_node, "parade,config_dut_generation", + &value); + if (!rc) + pdata->config_dut_generation = (u8)value; + else { + pr_err("%s: dut_generation is not configured, set default: DUT_PIP2_CAPABLE!\n", + __func__); + pdata->config_dut_generation = CONFIG_DUT_PIP2_CAPABLE; + } + + rc = of_property_read_u32(core_node, "parade,watchdog_force_stop", + &value); + if (!rc) { + if (value) + pdata->watchdog_force_stop = true; + else + pdata->watchdog_force_stop = false; + } else { + pr_err("%s: watchdog_force_stop is not configured, set default: false!\n", + __func__); + pdata->watchdog_force_stop = false; + } + + rc = of_property_read_u32(core_node, "parade,panel_id_support", + &value); + if (!rc) { + pdata->panel_id_support = (u8)value; + } else { + pr_err("%s: panel_id_support is not configured, set default: PT_PANEL_ID_DISABLE\n", + __func__); + pdata->panel_id_support = PT_PANEL_ID_DISABLE; + } + + for (i = 0; (unsigned int)i < ARRAY_SIZE(touch_setting_names); i++) { + if (touch_setting_names[i] == NULL) + continue; + + pdata->sett[i] = create_and_get_touch_setting(core_node, + touch_setting_names[i]); + if (IS_ERR(pdata->sett[i])) { + rc = PTR_ERR(pdata->sett[i]); + goto fail_free_sett; + } else if (pdata->sett[i] == NULL) + pr_debug("%s: No data for setting '%s'\n", __func__, + touch_setting_names[i]); + } + + pr_debug("%s: irq_gpio:%d rst_gpio:%d\n" + "hid_desc_reg:%d level_irq_udelay:%d vendor_id:%d prod_id:%d\n" + "flags:%d easy_wakeup_gesture:%d\n", __func__, + pdata->irq_gpio, pdata->rst_gpio, + pdata->hid_desc_register, + pdata->level_irq_udelay, pdata->vendor_id, pdata->product_id, + pdata->flags, pdata->easy_wakeup_gesture); + + pdata->xres = pt_xres; + pdata->init = pt_init; + pdata->power = pt_power; + pdata->detect = pt_detect; + pdata->irq_stat = pt_irq_stat; + pdata->setup_power = pt_setup_power; + pdata->setup_irq = pt_setup_irq; + + return pdata; + +fail_free_sett: + for (i--; i >= 0; i--) + free_touch_setting(pdata->sett[i]); +fail_free: + kfree(pdata); +fail: + return ERR_PTR(rc); +} + +/******************************************************************************* + * FUNCTION: free_core_pdata + * + * SUMMARY: Free the core module platform data and touch settings data. + * + * RETURN: + * success: the pointer of core platform data + * fail : error code with type of error pointer + * + * PARAMETERS: + * *core_node - pointer to device_node structure + ******************************************************************************/ +static void free_core_pdata(void *pdata) +{ + struct pt_core_platform_data *core_pdata = pdata; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(touch_setting_names); i++) + free_touch_setting(core_pdata->sett[i]); + kfree(core_pdata); +} + +/******************************************************************************* + * FUNCTION: pt_devtree_create_and_get_pdata + * + * SUMMARY: Parse dts and set up platform data for core module, multi-touch(mt) + * module, button(btn) module, proximity module.And Assign platform data + * pointer for loader module. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *adap_dev - pointer to device structure + ******************************************************************************/ +int pt_devtree_create_and_get_pdata(struct device *adap_dev) +{ + struct pt_platform_data *pdata; + struct device_node *core_node, *dev_node, *dev_node_fail; + struct drm_panel *active_panel = NULL; + enum pt_device_type type; + int count = 0; + int rc = 0; + + pr_info("%s: Start of fetch dtsi..\n", __func__); + + if (is_create_and_get_pdata == true) + return 0; + + if (!adap_dev->of_node) + return 0; + + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + adap_dev->platform_data = pdata; + set_pdata_ptr(pdata); + + /* There should be only one core node */ + for_each_child_of_node(adap_dev->of_node, core_node) { + const char *name; + + rc = of_property_read_string(core_node, "name", &name); + if (!rc) + pr_debug("%s: name:%s\n", __func__, name); + + rc = pt_check_dsi_panel_dt(core_node, &active_panel); + if (rc) { + pr_err("%s: Panel not selected, rc=%d\n", __func__, rc); + if (rc == -EPROBE_DEFER) { + pr_err("%s: Probe defer selected, rc=%d\n", __func__, rc); + kfree(pdata); + return rc; + } + } + + pdata->core_pdata = create_and_get_core_pdata(core_node); + if (IS_ERR(pdata->core_pdata)) { + pr_err("%s: Error in fetch dtsi..\n", __func__); + rc = PTR_ERR(pdata->core_pdata); + break; + } + + pr_info("%s: End of fetch dtsi..\n", __func__); + pdata->core_pdata->active_panel = active_panel; + pr_info("%s: Successful insert of active panel in core data\n", + __func__); + + /* Increment reference count */ + of_node_get(core_node); + + for_each_child_of_node(core_node, dev_node) { + count++; + rc = get_device_type(dev_node, &type); + if (rc) + break; + *pdata_ptr[type].pdata + = create_and_get_device_pdata(dev_node, type); + if (IS_ERR(*pdata_ptr[type].pdata)) + rc = PTR_ERR(*pdata_ptr[type].pdata); + if (rc) + break; + /* Increment reference count */ + of_node_get(dev_node); + } + + if (rc) { + free_core_pdata(pdata->core_pdata); + of_node_put(core_node); + for_each_child_of_node(core_node, dev_node_fail) { + if (dev_node == dev_node_fail) + break; + rc = get_device_type(dev_node, &type); + if (rc) + break; + free_device_pdata(type); + of_node_put(dev_node); + } + break; + } + pdata->loader_pdata = &_pt_loader_platform_data; + } + is_create_and_get_pdata = true; + pr_debug("%s: %d child node(s) found\n", __func__, count); + + return rc; +} +EXPORT_SYMBOL_GPL(pt_devtree_create_and_get_pdata); + +/******************************************************************************* + * FUNCTION: pt_devtree_clean_pdata + * + * SUMMARY: Free all platform data for core module, multi-touch(mt) module, + * button(btn) module, proximity module. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *adap_dev - pointer to device structure + ******************************************************************************/ +int pt_devtree_clean_pdata(struct device *adap_dev) +{ + struct pt_platform_data *pdata; + struct device_node *core_node, *dev_node; + enum pt_device_type type; + int rc = 0; + + if (is_create_and_get_pdata == false) + return 0; + + if (!adap_dev->of_node) + return 0; + + pdata = dev_get_platdata(adap_dev); + set_pdata_ptr(pdata); + free_core_pdata(pdata->core_pdata); + for_each_child_of_node(adap_dev->of_node, core_node) { + of_node_put(core_node); + for_each_child_of_node(core_node, dev_node) { + rc = get_device_type(dev_node, &type); + if (rc) + break; + free_device_pdata(type); + of_node_put(dev_node); + } + } + is_create_and_get_pdata = false; + + return rc; +} +EXPORT_SYMBOL_GPL(pt_devtree_clean_pdata); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Parade TrueTouch(R) Standard Product DeviceTree Driver"); +MODULE_AUTHOR("Parade Technologies "); diff --git a/qcom/opensource/touch-drivers/pt/pt_i2c.c b/qcom/opensource/touch-drivers/pt/pt_i2c.c new file mode 100644 index 0000000000..6e70fc1148 --- /dev/null +++ b/qcom/opensource/touch-drivers/pt/pt_i2c.c @@ -0,0 +1,328 @@ +/* + * pt_i2c.c + * Parade TrueTouch(TM) Standard Product I2C Module. + * For use with Parade touchscreen controllers. + * Supported parts include: + * TMA5XX + * TMA448 + * TMA445A + * TT21XXX + * TT31XXX + * TT4XXXX + * TT7XXX + * TC3XXX + * + * Copyright (C) 2015-2020 Parade Technologies + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * 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. + * + * Contact Parade Technologies at www.paradetech.com + */ + +#include "pt_regs.h" + +#include +#include + +#define PT_I2C_DATA_SIZE (2 * 256) +#define PT_I2C_NAME "pt_i2c_adapter" + +/******************************************************************************* + * FUNCTION: pt_i2c_read_default + * + * SUMMARY: Read a certain number of bytes from the I2C bus + * + * PARAMETERS: + * *dev - pointer to Device structure + * *buf - pointer to buffer where the data read will be stored + * size - size to be read + ******************************************************************************/ +static int pt_i2c_read_default(struct device *dev, void *buf, int size) +{ + struct i2c_client *client = to_i2c_client(dev); + int rc; + int read_size = size; + + if (!buf || !size || size > PT_I2C_DATA_SIZE) + return -EINVAL; + + rc = i2c_master_recv(client, buf, read_size); + + return (rc < 0) ? rc : rc != read_size ? -EIO : 0; +} + +/******************************************************************************* + * FUNCTION: pt_i2c_read_default_nosize + * + * SUMMARY: Read from the I2C bus in two transactions first reading the HID + * packet size (2 bytes) followed by reading the rest of the packet based + * on the size initially read. + * NOTE: The empty buffer 'size' was redefined in PIP version 1.7. + * + * PARAMETERS: + * *dev - pointer to Device structure + * *buf - pointer to buffer where the data read will be stored + * max - max size that can be read + ******************************************************************************/ +static int pt_i2c_read_default_nosize(struct device *dev, u8 *buf, u32 max) +{ + struct i2c_client *client = to_i2c_client(dev); + struct i2c_msg msgs[2]; + u8 msg_count = 1; + int rc; + u32 size; + + if (!buf) + return -EINVAL; + + msgs[0].addr = client->addr; + msgs[0].flags = (client->flags & I2C_M_TEN) | I2C_M_RD; + msgs[0].len = 2; + msgs[0].buf = buf; + rc = i2c_transfer(client->adapter, msgs, msg_count); + if (rc < 0 || rc != msg_count) + return (rc < 0) ? rc : -EIO; + + size = get_unaligned_le16(&buf[0]); + if (!size || size == 2 || size >= PT_PIP_1P7_EMPTY_BUF) + /* + * Before PIP 1.7, empty buffer is 0x0002; + * From PIP 1.7, empty buffer is 0xFFXX + */ + return 0; + + if (size > max) + return -EINVAL; + + rc = i2c_master_recv(client, buf, size); + return (rc < 0) ? rc : rc != (int)size ? -EIO : 0; +} + +/******************************************************************************* + * FUNCTION: pt_i2c_write_read_specific + * + * SUMMARY: Write the contents of write_buf to the I2C device and then read + * the response using pt_i2c_read_default_nosize() + * + * PARAMETERS: + * *dev - pointer to Device structure + * write_len - length of data buffer write_buf + * *write_buf - pointer to buffer to write + * *read_buf - pointer to buffer to read response into + ******************************************************************************/ +static int pt_i2c_write_read_specific(struct device *dev, u16 write_len, + u8 *write_buf, u8 *read_buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct i2c_msg msgs[2]; + u8 msg_count = 1; + int rc; + + /* Ensure no packet larger than what the PIP spec allows */ + if (write_len > PT_MAX_PIP2_MSG_SIZE) + return -EINVAL; + + if (!write_buf || !write_len) { + if (!write_buf) + pt_debug(dev, DL_ERROR, + "%s write_buf is NULL", __func__); + if (!write_len) + pt_debug(dev, DL_ERROR, + "%s write_len is NULL", __func__); + return -EINVAL; + } + + msgs[0].addr = client->addr; + msgs[0].flags = client->flags & I2C_M_TEN; + msgs[0].len = write_len; + msgs[0].buf = write_buf; + rc = i2c_transfer(client->adapter, msgs, msg_count); + + if (rc < 0 || rc != msg_count) + return (rc < 0) ? rc : -EIO; + + rc = 0; + + if (read_buf) { + rc = pt_i2c_read_default_nosize(dev, read_buf, + PT_I2C_DATA_SIZE); + } + + return rc; +} + +static struct pt_bus_ops pt_i2c_bus_ops = { + .bustype = BUS_I2C, + .read_default = pt_i2c_read_default, + .read_default_nosize = pt_i2c_read_default_nosize, + .write_read_specific = pt_i2c_write_read_specific, +}; + +#ifdef CONFIG_TOUCHSCREEN_PARADE_DEVICETREE_SUPPORT +static const struct of_device_id pt_i2c_of_match[] = { + { .compatible = "parade,pt_i2c_adapter", }, + { } +}; +MODULE_DEVICE_TABLE(of, pt_i2c_of_match); +#endif + + +/******************************************************************************* + * FUNCTION: pt_i2c_probe + * + * SUMMARY: Probe functon for the I2C module + * + * PARAMETERS: + * *client - pointer to i2c client structure + * *i2c_id - pointer to i2c device structure + ******************************************************************************/ +static int pt_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *i2c_id) +{ + struct device *dev = &client->dev; +#ifdef CONFIG_TOUCHSCREEN_PARADE_DEVICETREE_SUPPORT + const struct of_device_id *match; +#endif + int rc; + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + pt_debug(dev, DL_ERROR, "I2C functionality not Supported\n"); + return -EIO; + } +#ifdef CONFIG_TOUCHSCREEN_PARADE_DEVICETREE_SUPPORT + match = of_match_device(of_match_ptr(pt_i2c_of_match), dev); + if (match) { + rc = pt_devtree_create_and_get_pdata(dev); + if (rc < 0) + return rc; + } +#endif + + rc = pt_probe(&pt_i2c_bus_ops, &client->dev, client->irq, + PT_I2C_DATA_SIZE); + +#ifdef CONFIG_TOUCHSCREEN_PARADE_DEVICETREE_SUPPORT + if (rc && match) + pt_devtree_clean_pdata(dev); +#endif + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_i2c_remove + * + * SUMMARY: Remove functon for the I2C module + * + * PARAMETERS: + * *client - pointer to i2c client structure + ******************************************************************************/ +static int pt_i2c_remove(struct i2c_client *client) +{ +#ifdef CONFIG_TOUCHSCREEN_PARADE_DEVICETREE_SUPPORT + const struct of_device_id *match; +#endif + struct device *dev = &client->dev; + struct pt_core_data *cd = i2c_get_clientdata(client); + + pt_release(cd); + +#ifdef CONFIG_TOUCHSCREEN_PARADE_DEVICETREE_SUPPORT + match = of_match_device(of_match_ptr(pt_i2c_of_match), dev); + if (match) + pt_devtree_clean_pdata(dev); +#endif + + return 0; +} +/******************************************************************************* + * FUNCTION: pt_i2c_shutdown + * + * SUMMARY: Shutdown functon for the I2C module + * + * PARAMETERS: + * *client - pointer to i2c client structure + ******************************************************************************/ +void pt_i2c_shutdown(struct i2c_client *client) +{ +#ifdef CONFIG_TOUCHSCREEN_PARADE_DEVICETREE_SUPPORT + const struct of_device_id *match; +#endif + struct device *dev = &client->dev; + struct pt_core_data *cd = i2c_get_clientdata(client); + + pt_release(cd); + +#ifdef CONFIG_TOUCHSCREEN_PARADE_DEVICETREE_SUPPORT + match = of_match_device(of_match_ptr(pt_i2c_of_match), dev); + if (match) + pt_devtree_clean_pdata(dev); +#endif + +} + +static const struct i2c_device_id pt_i2c_id[] = { + { PT_I2C_NAME, 0, }, + { } +}; +MODULE_DEVICE_TABLE(i2c, pt_i2c_id); + +static struct i2c_driver pt_i2c_driver = { + .driver = { + .name = PT_I2C_NAME, + .owner = THIS_MODULE, + .pm = &pt_pm_ops, +#ifdef CONFIG_TOUCHSCREEN_PARADE_DEVICETREE_SUPPORT + .of_match_table = pt_i2c_of_match, +#endif + }, + .probe = pt_i2c_probe, + .remove = pt_i2c_remove, + .shutdown = pt_i2c_shutdown, + .id_table = pt_i2c_id, +}; + +#if (KERNEL_VERSION(3, 3, 0) <= LINUX_VERSION_CODE) +module_i2c_driver(pt_i2c_driver); +#else +/******************************************************************************* + * FUNCTION: pt_i2c_init + * + * SUMMARY: Initialize function to register i2c module to kernel. + * + * RETURN: + * 0 = success + * !0 = failure + ******************************************************************************/ +static int __init pt_i2c_init(void) +{ + int rc = i2c_add_driver(&pt_i2c_driver); + + pr_info("%s: Parade TTDL I2C Driver (Build %s) rc=%d\n", + __func__, PT_DRIVER_VERSION, rc); + return rc; +} +module_init(pt_i2c_init); + +/******************************************************************************* + * FUNCTION: pt_i2c_exit + * + * SUMMARY: Exit function to unregister i2c module from kernel. + * + ******************************************************************************/ +static void __exit pt_i2c_exit(void) +{ + i2c_del_driver(&pt_i2c_driver); +} +module_exit(pt_i2c_exit); +#endif + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Parade TrueTouch(R) Standard Product I2C driver"); +MODULE_AUTHOR("Parade Technologies "); diff --git a/qcom/opensource/touch-drivers/pt/pt_loader.c b/qcom/opensource/touch-drivers/pt/pt_loader.c new file mode 100644 index 0000000000..ca53aa904b --- /dev/null +++ b/qcom/opensource/touch-drivers/pt/pt_loader.c @@ -0,0 +1,5437 @@ +/* + * pt_loader.c + * Parade TrueTouch(TM) Standard Product FW Loader Module. + * For use with Parade touchscreen controllers. + * Supported parts include: + * TMA5XX + * TMA448 + * TMA445A + * TT21XXX + * TT31XXX + * TT4XXXX + * TT7XXX + * TC3XXX + * + * Copyright (C) 2015-2020 Parade Technologies + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * 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. + * + * Contact Parade Technologies at www.paradetech.com + */ + +#include "pt_regs.h" +#include + +#define PT_LOADER_NAME "pt_loader" + + +#define PT_AUTO_LOAD_FOR_CORRUPTED_FW 1 +#define PT_LOADER_FW_UPGRADE_RETRY_COUNT 3 + +#define PT_FW_UPGRADE \ + (defined(CONFIG_TOUCHSCREEN_PARADE_PLATFORM_FW_UPGRADE) \ + || defined(CONFIG_TOUCHSCREEN_PARADE_BINARY_FW_UPGRADE)) + +#define PT_TTCONFIG_UPGRADE \ + (defined(CONFIG_TOUCHSCREEN_PARADE_PLATFORM_TTCONFIG_UPGRADE) \ + || defined(CONFIG_TOUCHSCREEN_PARADE_MANUAL_TTCONFIG_UPGRADE)) + +/* Timeout values in ms. */ +#define PT_LDR_REQUEST_EXCLUSIVE_TIMEOUT 3000 +#define PT_LDR_SWITCH_TO_APP_MODE_TIMEOUT 300 + +#define PT_MAX_STATUS_SIZE 32 + +#define PT_DATA_MAX_ROW_SIZE 256 +#define PT_DATA_ROW_SIZE 128 + +#define PT_ARRAY_ID_OFFSET 0 +#define PT_ROW_NUM_OFFSET 1 +#define PT_ROW_SIZE_OFFSET 3 +#define PT_ROW_DATA_OFFSET 5 + +#define PT_POST_TT_CFG_CRC_MASK 0x2 +static inline struct pt_loader_data *pt_get_loader_data( + struct device *dev); + +static struct pt_core_commands *cmd; + +#define PIP2_LAUNCH_APP_DELAY 400 + +struct pip2_file_read { + s8 para_num; + u8 file_handle; + int file_offset; + int file_max_size; + int file_read_size; + int file_print_size; + u8 *file_print_buf; + int file_print_left; +}; + +#ifdef TTDL_DIAGNOSTICS +struct pip2_file_crc { + s8 para_num; + u8 file_handle; + int file_offset; + int file_max_size; + int file_read_size; +}; +#endif + +struct pip2_loader_data { + struct device *dev; + struct completion pip2_fw_upgrade_complete; /* mutex for loader */ + u8 pip2_file_handle; +}; + +struct pt_loader_data { + struct device *dev; + struct pt_sysinfo *si; + u8 status_buf[PT_MAX_STATUS_SIZE]; + struct completion int_running; + struct completion calibration_complete; +#ifdef CONFIG_TOUCHSCREEN_PARADE_BINARY_FW_UPGRADE + int builtin_bin_fw_status; + bool is_manual_upgrade_enabled; +#endif + struct work_struct fw_and_config_upgrade; + struct work_struct calibration_work; + struct pt_loader_platform_data *loader_pdata; +#ifdef CONFIG_TOUCHSCREEN_PARADE_MANUAL_TTCONFIG_UPGRADE + struct mutex config_lock; + u8 *config_data; + int config_size; + bool config_loading; +#endif + struct pip2_loader_data *pip2_data; + u8 pip2_load_file_no; + bool pip2_load_builtin; + u8 pip2_file_erase_file_no; + struct pip2_file_read pip2_file_data; +#ifdef TTDL_DIAGNOSTICS + struct pip2_file_crc pip2_fcrc; +#endif +#ifdef CONFIG_TOUCHSCREEN_PARADE_BINARY_FW_UPGRADE + struct work_struct bl_from_file; + struct work_struct pip2_bl_from_file; +#endif +}; + +static u8 update_fw_status; +static int pip2_erase_status; +static int pip2_erase_rc; + +/* PIP2 update fw status codes. 1-99% are "active" */ +enum UPDATE_FW_STATUS { + UPDATE_FW_IDLE = 0, + UPDATE_FW_ACTIVE_10 = 10, + UPDATE_FW_ACTIVE_90 = 90, + UPDATE_FW_ACTIVE_99 = 99, + UPDATE_FW_COMPLETE = 100, + UPDATE_FW_GENERAL_ERROR = 200, + UPDATE_FW_PIP_VERSION_ERROR = 201, + UPDATE_FW_VERSION_ERROR = 202, + UPDATE_FW_ERASE_ERROR = 203, + UPDATE_FW_FILE_CLOSE_ERROR = 204, + UPDATE_FW_WRITE_ERROR = 205, + UPDATE_FW_EXECUTE_ERROR = 206, + UPDATE_FW_RESET_ERROR = 207, + UPDATE_FW_MODE_ERROR = 208, + UPDATE_FW_ENTER_BL_ERROR = 209, + UPDATE_FW_FILE_OPEN_ERROR = 210, + UPDATE_FW_SENTINEL_NOT_SEEN = 211, + UPDATE_FW_EXCLUSIVE_ACCESS_ERROR = 212, + UPDATE_FW_NO_FW_PROVIDED = 213, + UPDATE_FW_INVALID_FW_IMAGE = 214, + UPDATE_FW_MISALIGN_FW_IMAGE = 230, + UPDATE_FW_SYSTEM_NOMEM = 231, + UPDATE_FW_INIT_BL_ERROR = 232, + UPDATE_FW_PARSE_ROW_ERROR = 233, + UPDATE_FW_PROGRAM_ROW_ERROR = 234, + UPDATE_FW_EXIT_BL_ERROR = 235, + UPDATE_FW_CHECK_SUM_ERROR = 236, + UPDATE_FW_NO_PLATFORM_DATA = 237, + UPDATE_FW_UNDEFINED_ERROR = 255, +}; + +enum PIP2_FILE_ERASE_STATUS { + PIP2_FILE_ERASE_STATUS_DUT_BUSY = 101, + PIP2_FILE_ERASE_STATUS_ENTER_BL_ERROR = 102, +}; + +struct pt_dev_id { + u32 silicon_id; + u8 rev_id; + u32 bl_ver; +}; + +struct pt_hex_image { + u8 array_id; + u16 row_num; + u16 row_size; + u8 row_data[PT_DATA_ROW_SIZE]; +} __packed; + +#ifdef CONFIG_TOUCHSCREEN_PARADE_BINARY_FW_UPGRADE +static int pt_pip2_upgrade_firmware_from_builtin(struct device *dev); +#endif + +typedef int (*PIP2_SEND_CMD)(struct device *, int, + u8, u8 *, u16, u8 *, u16 *); +static struct pt_module loader_module; + +/******************************************************************************* + * FUNCTION: pt_get_loader_data + * + * SUMMARY: Inline function to get pt_loader_data pointer from loader module. + * + * RETURN: + * pointer to pt_loader_data structure + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static inline struct pt_loader_data *pt_get_loader_data( + struct device *dev) +{ + return pt_get_module_data(dev, &loader_module); +} + +#if PT_FW_UPGRADE || PT_TTCONFIG_UPGRADE +/******************************************************************************* + * FUNCTION: pt_calibrate_idacs + * + * SUMMARY: Calibrate idac for mutual-cap,buttons,self-cap by called functions. + * It needs stop panel scan during calibration. + * + * PARAMETERS: + * *calibration_work - pointer to work_struct structure + ******************************************************************************/ +static void pt_calibrate_idacs(struct work_struct *calibration_work) +{ + struct pt_loader_data *ld = container_of(calibration_work, + struct pt_loader_data, calibration_work); + struct device *dev = ld->dev; + struct pt_cal_ext_data cal_data = {0}; + u8 dut_gen = cmd->request_dut_generation(dev); + u8 mode; + u8 status; + int rc; + + pt_debug(dev, DL_INFO, "Entering %s\n", __func__); + rc = cmd->request_exclusive(dev, PT_LDR_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) + goto exit; + + rc = cmd->nonhid_cmd->suspend_scanning(dev, 0); + if (rc < 0) + goto release; + + if (dut_gen == DUT_PIP1_ONLY) { + for (mode = 0; mode < 3; mode++) { + rc = cmd->nonhid_cmd->calibrate_idacs(dev, 0, mode, + &status); + if (rc < 0) { + pt_debug(dev, DL_ERROR, + "%s: calibrate idac error, rc = %d\n", + __func__, rc); + break; + } + } + } else { + /* + * Manual calibration is not need after fw 3.4.932110. + * Therefore, PT_LOADER_FLAG_NONE should be used to avoid + * manual calibration if the FW is newer than 3.4.932110. + */ + memset(&cal_data, 0, sizeof(struct pt_cal_ext_data)); + rc = cmd->nonhid_cmd->calibrate_ext(dev, 0, &cal_data, + &status); + if (rc < 0) { + pt_debug(dev, DL_ERROR, + "%s: extended calibrate error, rc = %d\n", + __func__, rc); + } + } + + rc = cmd->nonhid_cmd->resume_scanning(dev, 0); + if (rc < 0) + goto release; + + pt_debug(dev, DL_WARN, "%s: Calibration Done\n", __func__); + +release: + cmd->release_exclusive(dev); +exit: + complete(&ld->calibration_complete); +} + +/******************************************************************************* + * FUNCTION: pt_calibration_attention + * + * SUMMARY: Wrapper function to schedule calibration work used to subscribe into + * TTDL attention list.Once called will unsubscribe from attention list. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int pt_calibration_attention(struct device *dev) +{ + struct pt_loader_data *ld = pt_get_loader_data(dev); + int rc = 0; + + schedule_work(&ld->calibration_work); + + cmd->unsubscribe_attention(dev, PT_ATTEN_STARTUP, PT_LOADER_NAME, + pt_calibration_attention, 0); + + return rc; +} +#endif /* PT_FW_UPGRADE || PT_TTCONFIG_UPGRADE */ + +#if PT_FW_UPGRADE \ + || defined(CONFIG_TOUCHSCREEN_PARADE_PLATFORM_TTCONFIG_UPGRADE) + +/******************************************************************************* + * FUNCTION: pt_get_panel_id + * + * SUMMARY: Get panel id from core data. + * + * RETURN: + * panel id + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static u8 pt_get_panel_id(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + return cd->pid_for_loader; +} +#endif + + +#if (PT_FW_UPGRADE || PT_TTCONFIG_UPGRADE) +/******************************************************************************* + * FUNCTION: pt_check_firmware_version + * + * SUMMARY: Compare fw's version and revision control number with fw image's. + * + * RETURN: + * -1: Do not upgrade firmware + * 0: Version info same, let caller decide + * 1: Do a firmware upgrade + * + * PARAMETERS: + * *dev - pointer to device structure + * fw_ver_new - firmware version + * fw_revctrl_new - firmware revision control number + ******************************************************************************/ +static int pt_check_firmware_version(struct device *dev, + u32 fw_ver_new, u32 fw_revctrl_new) +{ + struct pt_loader_data *ld = pt_get_loader_data(dev); + u32 fw_ver_img; + u32 fw_revctrl_img; + + fw_ver_img = ld->si->ttdata.fw_ver_major << 8; + fw_ver_img += ld->si->ttdata.fw_ver_minor; + + pt_debug(dev, DL_WARN, + "%s: Current FW ver:0x%04X New FW ver:0x%04X\n", __func__, + fw_ver_img, fw_ver_new); + + if (fw_ver_new > fw_ver_img) { + pt_debug(dev, DL_WARN, + "%s: Image is newer, will upgrade\n", __func__); + return 1; + } + + if (fw_ver_new < fw_ver_img) { + pt_debug(dev, DL_WARN, + "%s: Image is older, will NOT upgrade\n", __func__); + return -EPERM; + } + + fw_revctrl_img = ld->si->ttdata.revctrl; + + pt_debug(dev, DL_WARN, + "%s: Current FW rev:%d New FW rev:%d\n", + __func__, fw_revctrl_img, fw_revctrl_new); + + if (fw_revctrl_new > fw_revctrl_img) { + pt_debug(dev, DL_WARN, + "%s: Image is newer, will upgrade\n", __func__); + return 1; + } + + if (fw_revctrl_new < fw_revctrl_img) { + pt_debug(dev, DL_WARN, + "%s: Image is older, will NOT upgrade\n", __func__); + return -EPERM; + } + + return 0; +} + +#endif /* PT_FW_UPGRADE || PT_TTCONFIG_UPGRADE */ + +#if PT_FW_UPGRADE +/******************************************************************************* + * FUNCTION: pt_get_row_ + * + * SUMMARY: Copy the image data to the "row_buf". + * + * RETURN: + * pointer to image buffer plus copy size + * + * PARAMETERS: + * *dev - pointer to device structure + * *row_buf - pointer to written buffer to program chip + * *image_buf - pointer to image buffer + * size - size of written data + ******************************************************************************/ +static u8 *pt_get_row_(struct device *dev, u8 *row_buf, + u8 *image_buf, int size) +{ + memcpy(row_buf, image_buf, size); + return image_buf + size; +} + +/******************************************************************************* + * FUNCTION: pt_ldr_enter_ + * + * SUMMARY: Enter bootloader state and update device id(silicon id, rev id, + * bootloader version). + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *dev_id - pointer to device id + ******************************************************************************/ +static int pt_ldr_enter_(struct device *dev, struct pt_dev_id *dev_id) +{ + int rc; + u8 return_data[8]; + u8 mode = PT_MODE_OPERATIONAL; + + dev_id->silicon_id = 0; + dev_id->rev_id = 0; + dev_id->bl_ver = 0; + + /* + * Reset to get to a known state, sleep to allow sentinel processing. + * + * NOTE: This msleep MUST be greater than the auto launch timeout + * that is built into the FW. + */ + cmd->request_reset(dev, PT_CORE_CMD_UNPROTECTED); + msleep(20); + + rc = cmd->request_get_mode(dev, 0, &mode); + pt_debug(dev, DL_INFO, "%s:request_get_mode rc=%d mode=%d\n", + __func__, rc, mode); + if (rc) + return rc; + + if (mode == PT_MODE_UNKNOWN) + return -EINVAL; + + if (mode == PT_MODE_OPERATIONAL) { + rc = cmd->nonhid_cmd->start_bl(dev, PT_CORE_CMD_UNPROTECTED); + pt_debug(dev, DL_INFO, "%s:start_bl rc=%d\n", __func__, rc); + if (rc) + return rc; + rc = cmd->request_get_mode(dev, 0, &mode); + pt_debug(dev, DL_INFO, "%s:request_get_mode rc=%d mode=%d\n", + __func__, rc, mode); + if (rc) + return rc; + if (mode != PT_MODE_BOOTLOADER) + return -EINVAL; + } + + rc = cmd->nonhid_cmd->get_bl_info(dev, + PT_CORE_CMD_UNPROTECTED, return_data); + pt_debug(dev, DL_INFO, "%s:get_bl_info rc=%d\n", __func__, rc); + if (rc) + return rc; + + dev_id->silicon_id = get_unaligned_le32(&return_data[0]); + dev_id->rev_id = return_data[4]; + dev_id->bl_ver = return_data[5] + (return_data[6] << 8) + + (return_data[7] << 16); + + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_ldr_init_ + * + * SUMMARY: Erase the entire TrueTouch application, Configuration Data block, + * and Design Data block in flash and enables the host to execute the Program + * and Verify Row command to bootload the application image and data. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *pt_hex_image - pointer to hex image structure + ******************************************************************************/ +static int pt_ldr_init_(struct device *dev, + struct pt_hex_image *row_image) +{ + return cmd->nonhid_cmd->initiate_bl(dev, 0, 8, + (u8 *)pt_data_block_security_key, row_image->row_size, + row_image->row_data); +} + +/******************************************************************************* + * FUNCTION: pt_ldr_parse_row_ + * + * SUMMARY: Parse and copy the row buffer data to hex image structure. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *row_buf - pointer to row buffer + * *row_image - pointer to hex image structure + ******************************************************************************/ +static int pt_ldr_parse_row_(struct device *dev, u8 *row_buf, + struct pt_hex_image *row_image) +{ + int rc = 0; + + row_image->array_id = row_buf[PT_ARRAY_ID_OFFSET]; + row_image->row_num = get_unaligned_be16(&row_buf[PT_ROW_NUM_OFFSET]); + row_image->row_size = get_unaligned_be16(&row_buf[PT_ROW_SIZE_OFFSET]); + + if (row_image->row_size > ARRAY_SIZE(row_image->row_data)) { + pt_debug(dev, DL_ERROR, + "%s: row data buffer overflow\n", __func__); + rc = -EOVERFLOW; + goto pt_ldr_parse_row_exit; + } + + memcpy(row_image->row_data, &row_buf[PT_ROW_DATA_OFFSET], + row_image->row_size); +pt_ldr_parse_row_exit: + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_ldr_prog_row_ + * + * SUMMARY: Program one row that the hex image structure data to the chip. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *row_image - pointer to hex image structure + ******************************************************************************/ +static int pt_ldr_prog_row_(struct device *dev, + struct pt_hex_image *row_image) +{ + int rc = 0; + u16 length = row_image->row_size + 3; + u8 *data = NULL; + u8 offset = 0; + + if (length > PT_DATA_MAX_ROW_SIZE) + return -EINVAL; + + data = kzalloc(length, GFP_KERNEL); + if (!data) + return -ENOMEM; + + data[offset++] = row_image->array_id; + data[offset++] = LOW_BYTE(row_image->row_num); + data[offset++] = HI_BYTE(row_image->row_num); + memcpy(data + 3, row_image->row_data, row_image->row_size); + rc = cmd->nonhid_cmd->prog_and_verify(dev, 0, length, data); + kfree(data); + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_ldr_verify_chksum_ + * + * SUMMARY: Perform a full verification of the application integrity by + * calculating the CRC of the TrueTouch application image in flash and + * comparing it to the expected CRC stored in the TrueTouch application CRC + * value stored in the Metadata row. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int pt_ldr_verify_chksum_(struct device *dev) +{ + u8 result; + int rc; + + rc = cmd->nonhid_cmd->verify_app_integrity(dev, 0, &result); + if (rc) + return rc; + + /* fail */ + if (result == 0) + return -EINVAL; + + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_ldr_exit_ + * + * SUMMARY: Launch the application from bootloader. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int pt_ldr_exit_(struct device *dev) +{ + return cmd->nonhid_cmd->launch_app(dev, 0); +} + +#define PT_NO_INC 0 +/******************************************************************************* + * FUNCTION: _pt_pip2_update_bl_status + * + * SUMMARY: Update the value in the bl_status global by either setting to a + * specific value or incrementing it ensuring we don't go out of bounds. + * + * PARAMETERS: + * *dev - pointer to device structure + * value - Value to force, if 0 then ignore. + * inc - Value to increment, if 0 then ignore. + ******************************************************************************/ +static u8 _pt_pip2_update_bl_status(struct device *dev, u8 value, u8 inc) +{ + pt_debug(dev, DL_DEBUG, + "%s: fw_status = %d, request val=%d, request inc=%d\n", + __func__, update_fw_status, value, inc); + + /* Reset status if both value and inc are 0 */ + if (value == UPDATE_FW_IDLE && inc == 0) { + update_fw_status = value; + pt_debug(dev, DL_WARN, "%s: ATM - Reset BL Status to %d\n", + __func__, value); + return update_fw_status; + } + + /* Set to value if valid */ + if (value > UPDATE_FW_IDLE && value < UPDATE_FW_UNDEFINED_ERROR) { + if (value <= UPDATE_FW_COMPLETE && value > update_fw_status) { + update_fw_status = value; + } else if (value >= UPDATE_FW_GENERAL_ERROR) { + update_fw_status = value; + pt_debug(dev, DL_WARN, + "%s: BL Status set to error code %d\n", + __func__, value); + } + return update_fw_status; + } + + if (inc > 0 && update_fw_status + inc <= UPDATE_FW_COMPLETE) + update_fw_status += inc; + else + pt_debug(dev, DL_ERROR, + "%s: Inc Request out of bounds: status=%d inc=%d\n", + __func__, update_fw_status, inc); + return update_fw_status; +} + +/******************************************************************************* + * FUNCTION: pt_load_app_ + * + * SUMMARY: Program the firmware image to the chip. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *fw - pointer to the firmware + * fw_size - size of firmware + * update_status - store the update status of firmware + ******************************************************************************/ +static int pt_load_app_(struct device *dev, const u8 *fw, int fw_size, + u8 *update_status) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_dev_id *dev_id; + struct pt_hex_image *row_image; + u8 *row_buf; + size_t image_rec_size; + size_t row_buf_size = PT_DATA_MAX_ROW_SIZE; + int row_count = 0; + u8 *p; + u8 *last_row; + int rc; + int rc_tmp; + int percent_cmplt; + int total_row_count; + + if (update_status == NULL) { + rc = -EINVAL; + pt_debug(dev, DL_ERROR, + "%s: update_status is NULL pointer %d\n", + __func__, rc); + return rc; + } else if (*update_status > UPDATE_FW_ACTIVE_10) { + pt_debug(dev, DL_WARN, + "%s: update_status is illegal = %d, fixed to %d\n", + __func__, *update_status, UPDATE_FW_ACTIVE_10); + *update_status = UPDATE_FW_ACTIVE_10; + } + + image_rec_size = sizeof(struct pt_hex_image); + if (fw_size % image_rec_size != 0) { + pt_debug(dev, DL_ERROR, + "%s: Firmware image is misaligned\n", __func__); + *update_status = UPDATE_FW_MISALIGN_FW_IMAGE; + rc = -EINVAL; + goto _pt_load_app_error; + } + *update_status += 1; + total_row_count = fw_size / image_rec_size - 1; + + pt_debug(dev, DL_INFO, "%s: start load app\n", __func__); +#ifdef TTHE_TUNER_SUPPORT + cmd->request_tthe_print(dev, NULL, 0, "start load app"); +#endif + + row_buf = kzalloc(row_buf_size, GFP_KERNEL); + row_image = kzalloc(sizeof(struct pt_hex_image), GFP_KERNEL); + dev_id = kzalloc(sizeof(struct pt_dev_id), GFP_KERNEL); + if (!row_buf || !row_image || !dev_id) { + *update_status = UPDATE_FW_SYSTEM_NOMEM; + rc = -ENOMEM; + goto _pt_load_app_exit; + } + *update_status += 1; + + cmd->request_stop_wd(dev); + + pt_debug(dev, DL_INFO, "%s: Send BL Loader Enter\n", __func__); +#ifdef TTHE_TUNER_SUPPORT + cmd->request_tthe_print(dev, NULL, 0, "Send BL Loader Enter"); +#endif + rc = pt_ldr_enter_(dev, dev_id); + if (rc) { + *update_status = UPDATE_FW_ENTER_BL_ERROR; + pt_debug(dev, DL_ERROR, "%s: Error cannot start Loader (ret=%d)\n", + __func__, rc); + goto _pt_load_app_exit; + } + pt_debug(dev, DL_INFO, + "%s: dev: silicon id=%08X rev=%02X bl=%08X\n", __func__, + dev_id->silicon_id, dev_id->rev_id, dev_id->bl_ver); + *update_status += 1; + + /* + * since start loader is successful, firmware mode can be assumed + * at bl mode directly. Updating this variable is helpful to detect + * firmware entered application mode. + */ + cd->mode = PT_MODE_BOOTLOADER; + + /* get last row */ + last_row = (u8 *)fw + fw_size - image_rec_size; + pt_get_row_(dev, row_buf, last_row, image_rec_size); + pt_ldr_parse_row_(dev, row_buf, row_image); + + /* initialise bootloader */ + rc = pt_ldr_init_(dev, row_image); + if (rc) { + *update_status = UPDATE_FW_ERASE_ERROR; + pt_debug(dev, DL_ERROR, "%s: Error cannot init Loader (ret=%d)\n", + __func__, rc); + goto _pt_load_app_exit; + } + *update_status += 5; + + pt_debug(dev, DL_INFO, + "%s: Send BL Loader Blocks\n", __func__); +#ifdef TTHE_TUNER_SUPPORT + cmd->request_tthe_print(dev, NULL, 0, "Send BL Loader Blocks"); +#endif + p = (u8 *)fw; + while (p < last_row) { + /* Get row */ + row_count += 1; + pt_debug(dev, DL_INFO, "%s: read row=%d\n", + __func__, row_count); + memset(row_buf, 0, row_buf_size); + p = pt_get_row_(dev, row_buf, p, image_rec_size); + + /* Don't update BL status on every pass */ + if (row_count / 8 * 8 == row_count) { + /* Calculate % complete for update_status sysfs */ + percent_cmplt = row_count * 100 / total_row_count; + if (percent_cmplt > UPDATE_FW_ACTIVE_99) + percent_cmplt = UPDATE_FW_ACTIVE_99; + *update_status = (percent_cmplt > *update_status) ? + percent_cmplt : *update_status; +#ifdef TTDL_DIAGNOSTICS + pt_debug(dev, DL_INFO, + "Wrote row num %d of total %d\n", + row_count, total_row_count); +#endif + } + + /* Parse row */ + pt_debug(dev, DL_INFO, "%s: p=%p buf=%p buf[0]=%02X\n", + __func__, p, row_buf, row_buf[0]); + rc = pt_ldr_parse_row_(dev, row_buf, row_image); + pt_debug(dev, DL_INFO, + "%s: array_id=%02X row_num=%04X(%d) row_size=%04X(%d)\n", + __func__, row_image->array_id, + row_image->row_num, row_image->row_num, + row_image->row_size, row_image->row_size); + if (rc) { + *update_status = UPDATE_FW_PARSE_ROW_ERROR; + pt_debug(dev, DL_ERROR, + "%s: Parse Row Error (a=%d r=%d ret=%d\n", + __func__, row_image->array_id, + row_image->row_num, rc); + goto _pt_load_app_exit; + } else { + pt_debug(dev, DL_INFO, + "%s: Parse Row (a=%d r=%d ret=%d\n", + __func__, row_image->array_id, + row_image->row_num, rc); + } + + /* program row */ + rc = pt_ldr_prog_row_(dev, row_image); + if (rc) { + *update_status = UPDATE_FW_PROGRAM_ROW_ERROR; + pt_debug(dev, DL_ERROR, + "%s: Prog Row Error (array=%d row=%d ret=%d)\n", + __func__, row_image->array_id, + row_image->row_num, rc); + goto _pt_load_app_exit; + } + + pt_debug(dev, DL_INFO, + "%s: array=%d row_cnt=%d row_num=%04X\n", + __func__, row_image->array_id, row_count, + row_image->row_num); + } + + /* exit loader */ + pt_debug(dev, DL_INFO, "%s: Send BL Loader Terminate\n", __func__); +#ifdef TTHE_TUNER_SUPPORT + cmd->request_tthe_print(dev, NULL, 0, "Send BL Loader Terminate"); +#endif + rc = pt_ldr_exit_(dev); + if (rc) { + *update_status = UPDATE_FW_EXIT_BL_ERROR; + pt_debug(dev, DL_ERROR, "%s: Error on exit Loader (ret=%d)\n", + __func__, rc); + + /* verify app checksum */ + rc_tmp = pt_ldr_verify_chksum_(dev); + if (rc_tmp) { + *update_status = UPDATE_FW_CHECK_SUM_ERROR; + pt_debug(dev, DL_ERROR, + "%s: ldr_verify_chksum fail r=%d\n", + __func__, rc_tmp); + } else + pt_debug(dev, DL_INFO, + "%s: APP Checksum Verified\n", __func__); + } else + *update_status = UPDATE_FW_ACTIVE_99; + +_pt_load_app_exit: + kfree(row_buf); + kfree(row_image); + kfree(dev_id); +_pt_load_app_error: + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_upgrade_firmware + * + * SUMMARY: Program the firmware image and set call back for start up. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *fw_img - pointer to the firmware + * fw_size - size of firmware + * *update_status - store the update status of firmware + ******************************************************************************/ +static int pt_upgrade_firmware(struct device *dev, const u8 *fw_img, + int fw_size, u8 *update_status) +{ + struct pt_loader_data *ld = pt_get_loader_data(dev); + struct pt_core_data *cd = dev_get_drvdata(dev); + int retry = PT_LOADER_FW_UPGRADE_RETRY_COUNT; + bool wait_for_calibration_complete = false; + int rc; + int t; + + _pt_pip2_update_bl_status(dev, UPDATE_FW_IDLE, PT_NO_INC); + + if (update_status == NULL) { + rc = -EINVAL; + pt_debug(dev, DL_ERROR, + "%s: update_status is NULL pointer %d\n", + __func__, rc); + return rc; + } else if (*update_status > UPDATE_FW_ACTIVE_10) { + pt_debug(dev, DL_WARN, + "%s: update_status is illegal = %d, fixed to %d\n", + __func__, *update_status, UPDATE_FW_ACTIVE_10); + *update_status = UPDATE_FW_ACTIVE_10; + } + + /* Ensure no enum task is pending */ + pt_debug(dev, DL_WARN, "%s: Cancel enum work thread\n", __func__); + cancel_work_sync(&cd->enum_work); + + pm_runtime_get_sync(dev); + *update_status += 1; + + rc = cmd->request_exclusive(dev, PT_LDR_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + *update_status = UPDATE_FW_EXCLUSIVE_ACCESS_ERROR; + goto exit; + } + *update_status += 1; + + t = *update_status; + while (retry--) { + /* Restore the update_status */ + *update_status = t; + rc = pt_load_app_(dev, fw_img, fw_size, update_status); + if (rc < 0) + pt_debug(dev, DL_ERROR, + "%s: Firmware update failed rc=%d, retry:%d\n", + __func__, rc, retry); + else + break; + msleep(20); + } + if (rc < 0) { + pt_debug(dev, DL_ERROR, + "%s: Firmware update failed with error code %d\n", + __func__, rc); + } else if (ld->loader_pdata && + (ld->loader_pdata->flags + & PT_LOADER_FLAG_CALIBRATE_AFTER_FW_UPGRADE)) { +#if (KERNEL_VERSION(3, 13, 0) <= LINUX_VERSION_CODE) + reinit_completion(&ld->calibration_complete); +#else + INIT_COMPLETION(ld->calibration_complete); +#endif + /* set up call back for startup */ + pt_debug(dev, DL_INFO, + "%s: Adding callback for calibration\n", __func__); + rc = cmd->subscribe_attention(dev, PT_ATTEN_STARTUP, + PT_LOADER_NAME, pt_calibration_attention, 0); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Failed adding callback for calibration\n", + __func__); + pt_debug(dev, DL_ERROR, + "%s: No calibration will be performed\n", + __func__); + rc = 0; + } else + wait_for_calibration_complete = true; + } + + cmd->release_exclusive(dev); + +exit: + if (!rc) { + cmd->request_enum(dev, true); + + /* + * Wait for FW reset sentinel from reset or execute for up + * to 500ms + */ + t = wait_event_timeout(cd->wait_q, + (cd->startup_status >= + STARTUP_STATUS_FW_RESET_SENTINEL) && + (cd->mode == PT_MODE_OPERATIONAL), + msecs_to_jiffies(PT_BL_WAIT_FOR_SENTINEL)); + if (IS_TMO(t)) { + pt_debug(dev, DL_WARN, + "%s: 0x%04X Timeout waiting for FW sentinel", + __func__, cd->startup_status); + } + + /* Double verify DUT is alive and well in Application mode */ + if (!(cd->startup_status & STARTUP_STATUS_FW_RESET_SENTINEL)) { + pt_debug(dev, DL_ERROR, + "%s FW sentinel not seen\n", __func__); + *update_status = UPDATE_FW_SENTINEL_NOT_SEEN; + } else if (cd->mode != PT_MODE_OPERATIONAL) { + *update_status = UPDATE_FW_MODE_ERROR; + pt_debug(dev, DL_ERROR, + "%s ERROR: Not in App mode as expected\n", + __func__); + } else { + *update_status = UPDATE_FW_COMPLETE; + pt_debug(dev, DL_INFO, + "%s == PIP1 FW upgrade finished ==\n", + __func__); + } + } else if (*update_status < UPDATE_FW_COMPLETE) { + *update_status = UPDATE_FW_UNDEFINED_ERROR; + pt_debug(dev, DL_ERROR, "%s undefined error!\n", __func__); + } + + pm_runtime_put_sync(dev); + + if (wait_for_calibration_complete) + wait_for_completion(&ld->calibration_complete); + + return rc; +} + +#endif /* PT_FW_UPGRADE */ + +#ifdef CONFIG_TOUCHSCREEN_PARADE_PLATFORM_FW_UPGRADE +/******************************************************************************* + * FUNCTION: pt_check_firmware_version_platform + * + * SUMMARY: The caller of function pt_check_firmware_version() to determine + * whether to load firmware from touch_firmware structure. + * + * RETURN: + * 0: Don't upgrade + * 1: Upgrade + * + * PARAMETERS: + * *dev - pointer to device structure + * *fw - pointer to the touch_firmware structure + ******************************************************************************/ +static int pt_check_firmware_version_platform(struct device *dev, + struct pt_touch_firmware *fw) +{ + struct pt_loader_data *ld = pt_get_loader_data(dev); + u32 fw_ver_new; + u32 fw_revctrl_new; + int upgrade; + + if (!ld->si) { + pt_debug(dev, DL_WARN, + "%s: No firmware info found, DUT FW may be corrupted\n", + __func__); + return PT_AUTO_LOAD_FOR_CORRUPTED_FW; + } + + fw_ver_new = get_unaligned_be16(fw->ver + 2); + /* 4 middle bytes are not used */ + fw_revctrl_new = get_unaligned_be32(fw->ver + 8); + pt_debug(dev, DL_WARN, "%s: Built-in FW version 0x%04x rev %d\n", + __func__, fw_ver_new, fw_revctrl_new); + + upgrade = pt_check_firmware_version(dev, fw_ver_new, + fw_revctrl_new); + + if (upgrade > 0) + return 1; + + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_get_platform_firmware + * + * SUMMARY: To get the pointer of right touch_firmware structure by panel id. + * + * RETURN: + * pointer to touch_firmware structure or null pointer if fail + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static struct pt_touch_firmware *pt_get_platform_firmware( + struct device *dev) +{ + struct pt_loader_data *ld = pt_get_loader_data(dev); + struct pt_touch_firmware **fws; + struct pt_touch_firmware *fw; + u8 panel_id; + + panel_id = pt_get_panel_id(dev); + if (panel_id == PANEL_ID_NOT_ENABLED) { + pt_debug(dev, DL_WARN, + "%s: Panel ID not enabled, using legacy firmware\n", + __func__); + return ld->loader_pdata->fw; + } + + fws = ld->loader_pdata->fws; + if (!fws) { + pt_debug(dev, DL_ERROR, + "%s: No firmwares provided\n", __func__); + return NULL; + } + + /* Find FW according to the Panel ID */ + while ((fw = *fws++)) { + if (fw->panel_id == panel_id) { + pt_debug(dev, DL_WARN, + "%s: Found matching fw:%p with Panel ID: 0x%02X\n", + __func__, fw, fw->panel_id); + return fw; + } + pt_debug(dev, DL_WARN, + "%s: Found mismatching fw:%p with Panel ID: 0x%02X\n", + __func__, fw, fw->panel_id); + } + + return NULL; +} + +/******************************************************************************* + * FUNCTION: upgrade_firmware_from_platform + * + * SUMMARY: Get touch_firmware structure and perform upgrade if pass the + * firmware version check. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * forced - flag to force upgrade(1:force to upgrade) + ******************************************************************************/ +static int upgrade_firmware_from_platform(struct device *dev, + bool forced) +{ + struct pt_loader_data *ld = pt_get_loader_data(dev); + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_touch_firmware *fw; + int rc = -ENODEV; + int upgrade; + int retry = 3; + +retry_bl: + if (!ld->loader_pdata) { + _pt_pip2_update_bl_status(dev, UPDATE_FW_NO_PLATFORM_DATA, + PT_NO_INC); + pt_debug(dev, DL_ERROR, + "%s: No loader platform data\n", __func__); + return rc; + } + + fw = pt_get_platform_firmware(dev); + if (!fw || !fw->img || !fw->size) { + _pt_pip2_update_bl_status(dev, UPDATE_FW_NO_FW_PROVIDED, + PT_NO_INC); + pt_debug(dev, DL_ERROR, + "%s: No platform firmware\n", __func__); + return rc; + } + + if (!fw->ver || !fw->vsize) { + _pt_pip2_update_bl_status(dev, UPDATE_FW_INVALID_FW_IMAGE, + PT_NO_INC); + pt_debug(dev, DL_ERROR, "%s: No platform firmware version\n", + __func__); + return rc; + } + + if (forced) + upgrade = forced; + else + upgrade = pt_check_firmware_version_platform(dev, fw); + + if (upgrade) { + rc = pt_upgrade_firmware(dev, + fw->img, fw->size, &update_fw_status); + + /* An extra BL may be needed if default PID was wrong choice */ + if ((cd->panel_id_support & PT_PANEL_ID_BY_SYS_INFO) && + !rc && !ld->si && retry--) { + pt_debug(dev, DL_WARN, "%s: ATM - An extra BL may be needed\n", + __func__); + /* Panel_ID coming from sysinfo, ensure we have it */ + ld->si = cmd->request_sysinfo(dev); + goto retry_bl; + } + } + + return rc; +} +#endif /* CONFIG_TOUCHSCREEN_PARADE_PLATFORM_FW_UPGRADE */ + +#ifdef CONFIG_TOUCHSCREEN_PARADE_BINARY_FW_UPGRADE +/******************************************************************************* + * FUNCTION: _pt_pip1_bl_from_file + * + * SUMMARY: Wrapper function for _pt_firmware_cont() to perform bl with an + * image file from userspace. + * + * PARAMETERS: + * *fw - pointer to firmware structure + * forced - flag to force upgrade(1:force to upgrade) + ******************************************************************************/ +static int _pt_pip1_bl_from_file(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + int fw_size = 0; + int rc = 0; + u8 *fw_img = NULL; + u8 header_size = 0; + + _pt_pip2_update_bl_status(dev, UPDATE_FW_IDLE, PT_NO_INC); + + fw_img = kzalloc(PT_PIP2_MAX_FILE_SIZE, GFP_KERNEL); + if (!fw_img) { + _pt_pip2_update_bl_status(dev, UPDATE_FW_SYSTEM_NOMEM, + PT_NO_INC); + rc = -ENOMEM; + goto exit; + } + + rc = cmd->nonhid_cmd->read_us_file(dev, cd->pip2_us_file_path, + fw_img, &fw_size); + if (rc) { + pt_debug(dev, DL_ERROR, "%s: No firmware provided to load\n", + __func__); + _pt_pip2_update_bl_status(dev, UPDATE_FW_NO_FW_PROVIDED, + PT_NO_INC); + goto exit; + } + + if (!fw_img || !fw_size) { + pt_debug(dev, DL_ERROR, + "%s: No firmware received\n", __func__); + _pt_pip2_update_bl_status(dev, UPDATE_FW_INVALID_FW_IMAGE, + PT_NO_INC); + goto exit; + } + + header_size = fw_img[0]; + if (header_size >= (fw_size + 1)) { + pt_debug(dev, DL_ERROR, + "%s: Firmware format is invalid\n", __func__); + _pt_pip2_update_bl_status(dev, UPDATE_FW_INVALID_FW_IMAGE, + PT_NO_INC); + goto exit; + } + + pt_upgrade_firmware(dev, &(fw_img[header_size + 1]), + fw_size - (header_size + 1), &update_fw_status); +exit: + kfree(fw_img); + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_firmware_cont + * + * SUMMARY: Firmware upgrade continue function that verifies the firmware size + * in the firmware class and then upgrades the firmware. + * + * PARAMETERS: + * *fw - pointer to firmware structure + * forced - flag to force upgrade(1:force to upgrade) + ******************************************************************************/ +static void _pt_firmware_cont(const struct firmware *fw, void *context) +{ + struct device *dev = context; + struct pt_loader_data *ld = pt_get_loader_data(dev); + u8 header_size = 0; + + _pt_pip2_update_bl_status(dev, UPDATE_FW_IDLE, PT_NO_INC); + if (!fw) { + pt_debug(dev, DL_ERROR, "%s: No firmware\n", __func__); + _pt_pip2_update_bl_status(dev, UPDATE_FW_NO_FW_PROVIDED, + PT_NO_INC); + goto pt_firmware_cont_exit; + } + + if (!fw->data || !fw->size) { + pt_debug(dev, DL_ERROR, + "%s: No firmware received\n", __func__); + _pt_pip2_update_bl_status(dev, UPDATE_FW_INVALID_FW_IMAGE, + PT_NO_INC); + goto pt_firmware_cont_release_exit; + } + + header_size = fw->data[0]; + if (header_size >= (fw->size + 1)) { + pt_debug(dev, DL_ERROR, + "%s: Firmware format is invalid\n", __func__); + _pt_pip2_update_bl_status(dev, UPDATE_FW_INVALID_FW_IMAGE, + PT_NO_INC); + goto pt_firmware_cont_release_exit; + } + + pt_upgrade_firmware(dev, &(fw->data[header_size + 1]), + fw->size - (header_size + 1), &update_fw_status); + +pt_firmware_cont_release_exit: + if (fw) + release_firmware(fw); + +pt_firmware_cont_exit: + ld->is_manual_upgrade_enabled = 0; +} + +/******************************************************************************* + * FUNCTION: pt_check_firmware_config_version + * + * SUMMARY: Compare fw's config version with fw image's. If they are differnt + * report a FW upgrade is needed. + * + * RETURN: + * -1: Do not upgrade firmware + * 0: Version info same, let caller decide + * 1: Do a firmware upgrade + * + * PARAMETERS: + * *dev - pointer to device structure + * image_config_ver - Image's firmware config version + ******************************************************************************/ +static int pt_check_firmware_config_version(struct device *dev, + u16 image_config_ver) +{ + struct pt_loader_data *ld = pt_get_loader_data(dev); + u16 fw_config_ver; + + fw_config_ver = ld->si->ttdata.fw_ver_conf; + + pt_debug(dev, DL_WARN, + "%s: Current config ver:0x%04X New config ver:0x%04X\n", + __func__, fw_config_ver, image_config_ver); + + if (image_config_ver != fw_config_ver) { + pt_debug(dev, DL_WARN, + "%s: Image config ver is different, will upgrade\n", + __func__); + return 1; + } + + if (image_config_ver == fw_config_ver) { + pt_debug(dev, DL_WARN, + "%s: Image config ver is the same, will NOT upgrade\n", + __func__); + return 0; + } + + return -EPERM; +} + +/******************************************************************************* + * FUNCTION: pt_check_firmware_version_builtin + * + * SUMMARY: The caller of function pt_check_firmware_version() to determine + * whether to load built-in firmware. + * + * RETURN: + * 0: Don't upgrade + * 1: Upgrade + * + * PARAMETERS: + * *dev - pointer to device structure + * *fw - pointer to the firmware structure + ******************************************************************************/ +static int pt_check_firmware_version_builtin(struct device *dev, + const struct firmware *fw) +{ + struct pt_loader_data *ld = pt_get_loader_data(dev); + u16 fw_config_ver_new; + u32 fw_ver_new; + u32 fw_revctrl_new; + int upgrade; + + if (!ld->si) { + pt_debug(dev, DL_WARN, + "%s: No firmware info found, DUT FW may be corrupted\n", + __func__); + return PT_AUTO_LOAD_FOR_CORRUPTED_FW; + } + + fw_ver_new = get_unaligned_be16(fw->data + 3); + /* 4 middle bytes are not used */ + fw_revctrl_new = get_unaligned_be32(fw->data + 9); + /* Offset 17,18 is the TT Config version*/ + fw_config_ver_new = get_unaligned_be16(fw->data + 17); + + pt_debug(dev, DL_WARN, + "%s: Built-in FW version=0x%04x rev=%d config=0x%04X\n", + __func__, fw_ver_new, fw_revctrl_new, fw_config_ver_new); + + upgrade = pt_check_firmware_version(dev, fw_ver_new, + fw_revctrl_new); + + /* Only check config version if FW version was an exact match */ + if (upgrade == 0) + upgrade = pt_check_firmware_config_version(dev, + fw_config_ver_new); + + if (upgrade > 0) + return 1; + + return 0; +} + +/******************************************************************************* + * FUNCTION: _pt_firmware_cont_builtin + * + * SUMMARY: Perform upgrade if pass the firmware version check. + * + * PARAMETERS: + * *dev - pointer to device structure + * forced - flag to force upgrade(1:force to upgrade) + ******************************************************************************/ +static void _pt_firmware_cont_builtin(const struct firmware *fw, + void *context) +{ + struct device *dev = context; + struct pt_loader_data *ld = pt_get_loader_data(dev); + int upgrade; + + if (!fw) { + pt_debug(dev, DL_INFO, + "%s: No builtin firmware\n", __func__); + goto _pt_firmware_cont_builtin_exit; + } + + if (!fw->data || !fw->size) { + pt_debug(dev, DL_ERROR, + "%s: Invalid builtin firmware\n", __func__); + goto _pt_firmware_cont_builtin_exit; + } + + pt_debug(dev, DL_WARN, "%s: Found firmware\n", __func__); + + upgrade = pt_check_firmware_version_builtin(dev, fw); + if (upgrade) { + _pt_firmware_cont(fw, dev); + ld->builtin_bin_fw_status = 0; + return; + } + +_pt_firmware_cont_builtin_exit: + if (fw) + release_firmware(fw); + + ld->builtin_bin_fw_status = -EINVAL; +} + +/******************************************************************************* + * FUNCTION: upgrade_firmware_from_class + * + * SUMMARY: Create the firmware class but don't actually load any FW to the + * DUT. This creates all the sysfs nodes needed for a user to bootload + * the DUT with their own bin file. + * + * RETURN: + * 0 = success + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int upgrade_firmware_from_class(struct device *dev) +{ + int retval; + + pt_debug(dev, DL_INFO, + "%s: Enabling firmware class loader\n", __func__); + + retval = request_firmware_nowait(THIS_MODULE, FW_ACTION_NOHOTPLUG, + dev_name(dev), dev, GFP_KERNEL, dev, + _pt_firmware_cont); + if (retval < 0) { + pt_debug(dev, DL_ERROR, + "%s: Fail request firmware class file load\n", + __func__); + return retval; + } + + return 0; +} + +#define FILENAME_LEN_MAX 64 +/******************************************************************************* + * FUNCTION: generate_firmware_filename + * + * SUMMARY: Generate firmware file name by panel id. Generates binary FW + * filename as following: + * - Panel ID not enabled: tt_fw.bin + * - Panel ID enabled: tt_fw_pidXX.bin + * + * RETURN: + * pointer to file name or null pointer if fail + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static char *generate_firmware_filename(struct device *dev) +{ + char *filename; + u8 panel_id; + + filename = kzalloc(FILENAME_LEN_MAX, GFP_KERNEL); + if (!filename) + return NULL; + + panel_id = pt_get_panel_id(dev); + if (panel_id == PANEL_ID_NOT_ENABLED) + snprintf(filename, FILENAME_LEN_MAX, "%s", PT_FW_FILE_NAME); + else + snprintf(filename, FILENAME_LEN_MAX, "%s_pid%02X%s", + PT_FW_FILE_PREFIX, panel_id, PT_FW_FILE_SUFFIX); + + pt_debug(dev, DL_INFO, "%s: Filename: %s\n", + __func__, filename); + + return filename; +} + +/******************************************************************************* + * FUNCTION: generate_silicon_id_firmware_filename + * + * SUMMARY: Generate firmware file name with the HW version prefix followed by + * panel id. Generates binary FW filename as following (where XXXX is the + * silicon ID): + * - Panel ID not enabled: XXXX_tt_fw.bin + * - Panel ID enabled: XXXX_tt_fw_pidXX.bin + * + * RETURN: + * pointer to file name or null pointer if fail + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static char *generate_silicon_id_firmware_filename(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + char *filename; + char si_id[5] = "DEAD"; + u8 panel_id; + + filename = kzalloc(FILENAME_LEN_MAX, GFP_KERNEL); + if (!filename) + return NULL; + + panel_id = pt_get_panel_id(dev); + memcpy(si_id, cd->hw_version, 4); + + if (panel_id == PANEL_ID_NOT_ENABLED) + snprintf(filename, FILENAME_LEN_MAX, "%s_%s", si_id, + PT_FW_FILE_NAME); + else + snprintf(filename, FILENAME_LEN_MAX, "%s_%s_pid%02X%s", + si_id, PT_FW_FILE_PREFIX, panel_id, PT_FW_FILE_SUFFIX); + + pt_debug(dev, DL_INFO, "%s: Filename: %s\n", __func__, filename); + + return filename; +} + +#define MAX_FILE_NAMES 2 +/******************************************************************************* + * FUNCTION: upgrade_firmware_from_builtin + * + * SUMMARY: Create the firmware class load FW by searching the name of built-in + * file. Then perform upgrade after getting the file. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int upgrade_firmware_from_builtin(struct device *dev) +{ + struct pt_loader_data *ld = pt_get_loader_data(dev); + struct pt_core_data *cd = dev_get_drvdata(dev); + char *filename[MAX_FILE_NAMES]; + int index = 0; + int retval; + const struct firmware *fw_entry = NULL; + int retry = 3; + +retry_bl: + pt_debug(dev, DL_WARN, + "%s: Enabling firmware class loader built-in\n", + __func__); + + /* Load the supported file names in the search order */ + + /* 0 = Flash file name with optional PID "pt_fw<_PIDX>.bin" */ + filename[0] = generate_firmware_filename(dev); + if (!filename[0]) { + pt_debug(dev, DL_ERROR, + "%s: ERROR - Could not generate filename\n", __func__); + return -ENOMEM; + } + + /* + * 1 = Flash file name with Silicon ID prefix and optional PID + * "XXXX_pt_fw<_PIDX>.bin" + */ + filename[1] = generate_silicon_id_firmware_filename(dev); + if (!filename[1]) { + pt_debug(dev, DL_ERROR, + "%s: ERROR - Could not generate filename\n", __func__); + return -ENOMEM; + } + + mutex_lock(&cd->firmware_class_lock); + while (index < MAX_FILE_NAMES) { + pt_debug(dev, DL_WARN, "%s: Request FW file %s\n", __func__, + filename[index]); +#if (KERNEL_VERSION(3, 13, 0) > LINUX_VERSION_CODE) + retval = request_firmware(&fw_entry, filename[index], dev); +#else + retval = request_firmware_direct(&fw_entry, + filename[index], dev); +#endif + if (retval < 0) { + pt_debug(dev, DL_WARN, "%s: Fail request FW %s load\n", + __func__, filename[index]); + } else { + pt_debug(dev, DL_WARN, "%s: FW %s class file loading\n", + __func__, filename[index]); + break; + } + index++; + } + + /* Proceed with the BL if a matching file was found */ + if (index != MAX_FILE_NAMES) { + _pt_firmware_cont_builtin(fw_entry, dev); + /* An extra BL may be needed if default PID was wrong choice */ + if ((cd->panel_id_support & PT_PANEL_ID_BY_SYS_INFO) && + !ld->si && retry--) { + pt_debug(dev, DL_WARN, "%s: ATM - An extra BL may be needed\n", + __func__); + /* Free allocated memory */ + index = 0; + while (index < MAX_FILE_NAMES) + kfree(filename[index++]); + /* Reset index to 0 */ + index = 0; + mutex_unlock(&cd->firmware_class_lock); + + /* Panel_ID coming from sysinfo, ensure we have it */ + ld->si = cmd->request_sysinfo(dev); + goto retry_bl; + } + retval = ld->builtin_bin_fw_status; + } + + index = 0; + while (index < MAX_FILE_NAMES) + kfree(filename[index++]); + + mutex_unlock(&cd->firmware_class_lock); + return retval; +} +#endif /* CONFIG_TOUCHSCREEN_PARADE_BINARY_FW_UPGRADE */ + +#if PT_TTCONFIG_UPGRADE +/******************************************************************************* + * FUNCTION: pt_write_config_row_ + * + * SUMMARY: Allow to program the data block area that includes configuration + * data, manufacturing data, design data. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * ebid - block id to determine the block name(configuration,etc) + * row_number - row number if written block + * row_size - row size of written data + * *data - pointer to the data to write + ******************************************************************************/ +static int pt_write_config_row_(struct device *dev, u8 ebid, + u16 row_number, u16 row_size, u8 *data) +{ + int rc; + u16 actual_write_len; + + rc = cmd->nonhid_cmd->write_data_block(dev, row_number, + row_size, ebid, data, (u8 *)pt_data_block_security_key, + &actual_write_len); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Fail Put EBID=%d row=%d cmd fail r=%d\n", + __func__, ebid, row_number, rc); + return rc; + } + + if (actual_write_len != row_size) { + pt_debug(dev, DL_ERROR, + "%s: Fail Put EBID=%d row=%d wrong write size=%d\n", + __func__, ebid, row_number, actual_write_len); + rc = -EINVAL; + } + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_upgrade_ttconfig + * + * SUMMARY: Program ttconfig_data with following steps: + * 1) Suspend scanning + * 2) Write data to the data block + * 3) Verify the crc for data block + * 4) Resume scanning + * 5) Set up call back for calibration if required + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *ttconfig_data - pointer to the config data to write to data block + * ttconfig_size - size of config data to write + ******************************************************************************/ +static int pt_upgrade_ttconfig(struct device *dev, + const u8 *ttconfig_data, int ttconfig_size) +{ + struct pt_loader_data *ld = pt_get_loader_data(dev); + bool wait_for_calibration_complete = false; + u8 ebid = PT_TCH_PARM_EBID; + u16 row_size = PT_DATA_ROW_SIZE; + u16 table_size; + u16 row_count; + u16 residue; + u8 *row_buf; + u8 verify_crc_status; + u16 calculated_crc; + u16 stored_crc; + int rc = 0; + int i; + + table_size = ttconfig_size; + row_count = table_size / row_size; + row_buf = (u8 *)ttconfig_data; + pt_debug(dev, DL_INFO, "%s: size:%d row_size=%d row_count=%d\n", + __func__, table_size, row_size, row_count); + + pm_runtime_get_sync(dev); + + rc = cmd->request_exclusive(dev, PT_LDR_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) + goto exit; + + rc = cmd->nonhid_cmd->suspend_scanning(dev, 0); + if (rc < 0) + goto release; + + for (i = 0; i < row_count; i++) { + pt_debug(dev, DL_INFO, "%s: row=%d size=%d\n", + __func__, i, row_size); + rc = pt_write_config_row_(dev, ebid, i, row_size, + row_buf); + if (rc) { + pt_debug(dev, DL_ERROR, "%s: Fail put row=%d r=%d\n", + __func__, i, rc); + break; + } + row_buf += row_size; + } + if (!rc) { + residue = table_size % row_size; + pt_debug(dev, DL_WARN, "%s: row=%d size=%d\n", + __func__, i, residue); + rc = pt_write_config_row_(dev, ebid, i, residue, + row_buf); + row_count++; + if (rc) + pt_debug(dev, DL_ERROR, "%s: Fail put row=%d r=%d\n", + __func__, i, rc); + } + + if (!rc) + pt_debug(dev, DL_WARN, + "%s: TT_CFG updated: rows:%d bytes:%d\n", + __func__, row_count, table_size); + + rc = cmd->nonhid_cmd->verify_cfg_block_crc(dev, 0, ebid, + &verify_crc_status, &calculated_crc, &stored_crc); + if (rc || verify_crc_status) + pt_debug(dev, DL_ERROR, + "%s: CRC Failed, ebid=%d, status=%d, scrc=%X ccrc=%X\n", + __func__, ebid, verify_crc_status, + calculated_crc, stored_crc); + else + pt_debug(dev, DL_INFO, + "%s: CRC PASS, ebid=%d, status=%d, scrc=%X ccrc=%X\n", + __func__, ebid, verify_crc_status, + calculated_crc, stored_crc); + + rc = cmd->nonhid_cmd->resume_scanning(dev, 0); + if (rc < 0) + goto release; + + if (ld->loader_pdata && + (ld->loader_pdata->flags + & PT_LOADER_FLAG_CALIBRATE_AFTER_TTCONFIG_UPGRADE)) { +#if (KERNEL_VERSION(3, 13, 0) <= LINUX_VERSION_CODE) + reinit_completion(&ld->calibration_complete); +#else + INIT_COMPLETION(ld->calibration_complete); +#endif + /* set up call back for startup */ + pt_debug(dev, DL_INFO, "%s: Adding callback for calibration\n", + __func__); + rc = cmd->subscribe_attention(dev, PT_ATTEN_STARTUP, + PT_LOADER_NAME, pt_calibration_attention, 0); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Failed adding callback for calibration\n", + __func__); + pt_debug(dev, DL_ERROR, + "%s: No calibration will be performed\n", + __func__); + rc = 0; + } else + wait_for_calibration_complete = true; + } + +release: + cmd->release_exclusive(dev); + +exit: + if (!rc) + cmd->request_enum(dev, true); + + pm_runtime_put_sync(dev); + + if (wait_for_calibration_complete) + wait_for_completion(&ld->calibration_complete); + + return rc; +} +#endif /* PT_TTCONFIG_UPGRADE */ + +#ifdef CONFIG_TOUCHSCREEN_PARADE_PLATFORM_TTCONFIG_UPGRADE +/******************************************************************************* + * FUNCTION: pt_get_ttconfig_crc + * + * SUMMARY: Get crc from ttconfig data. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *ttconfig_data - pointer to the config data + * ttconfig_size - size of config data + * *crc - pointer to the crc of configure to be stored + ******************************************************************************/ +static int pt_get_ttconfig_crc(struct device *dev, + const u8 *ttconfig_data, int ttconfig_size, u16 *crc) +{ + u16 crc_loc; + + crc_loc = get_unaligned_le16(&ttconfig_data[2]); + if (ttconfig_size < crc_loc + 2) + return -EINVAL; + + *crc = get_unaligned_le16(&ttconfig_data[crc_loc]); + + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_get_ttconfig_version + * + * SUMMARY: Get version number from ttconfig data. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *ttconfig_data - pointer to the config data + * ttconfig_size - size of config data + * *version - pointer to the version of configure to be stored + ******************************************************************************/ +static int pt_get_ttconfig_version(struct device *dev, + const u8 *ttconfig_data, int ttconfig_size, u16 *version) +{ + if (ttconfig_size < PT_TTCONFIG_VERSION_OFFSET + + PT_TTCONFIG_VERSION_SIZE) + return -EINVAL; + + *version = get_unaligned_le16( + &ttconfig_data[PT_TTCONFIG_VERSION_OFFSET]); + + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_check_ttconfig_version + * + * SUMMARY: Check the configure version and crc value to determine whether to + * upgrade,the upgrade conditions as following: + * 1) To upgrade if the config version is newer than current config, but + * this check is based on the flag in loader plarform data. + * 2) To upgrade if config CRC is different. + * 3) Don't upgrade when can't match any of above conditions. + * + * RETURN: + * 0: Don't upgrade + * 1: Upgrade + * + * PARAMETERS: + * *dev - pointer to device structure + * *ttconfig_data - pointer to the config data + * ttconfig_size - size of config data + ******************************************************************************/ +static int pt_check_ttconfig_version(struct device *dev, + const u8 *ttconfig_data, int ttconfig_size) +{ + struct pt_loader_data *ld = pt_get_loader_data(dev); + u16 cfg_crc_new; + int rc; + + if (!ld->si) + return 0; + + /* Check for config version */ + if (ld->loader_pdata->flags & + PT_LOADER_FLAG_CHECK_TTCONFIG_VERSION) { + u16 cfg_ver_new; + + rc = pt_get_ttconfig_version(dev, ttconfig_data, + ttconfig_size, &cfg_ver_new); + if (rc) + return 0; + + pt_debug(dev, DL_INFO, "%s: img_ver:0x%04X new_ver:0x%04X\n", + __func__, ld->si->ttdata.fw_ver_conf, cfg_ver_new); + + /* Check if config version is newer */ + if (cfg_ver_new > ld->si->ttdata.fw_ver_conf) { + pt_debug(dev, DL_WARN, + "%s: Config version newer, will upgrade\n", __func__); + return 1; + } + + pt_debug(dev, DL_WARN, + "%s: Config version is identical or older, will NOT upgrade\n", + __func__); + /* Check for config CRC */ + } else { + rc = pt_get_ttconfig_crc(dev, ttconfig_data, + ttconfig_size, &cfg_crc_new); + if (rc) + return 0; + + pt_debug(dev, DL_INFO, "%s: img_crc:0x%04X new_crc:0x%04X\n", + __func__, ld->si->ttconfig.crc, cfg_crc_new); + + if (cfg_crc_new != ld->si->ttconfig.crc) { + pt_debug(dev, DL_WARN, + "%s: Config CRC different, will upgrade\n", + __func__); + return 1; + } + + pt_debug(dev, DL_WARN, + "%s: Config CRC equal, will NOT upgrade\n", __func__); + } + + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_check_ttconfig_version_platform + * + * SUMMARY: To call the function pt_check_ttconfig_version() to determine + * whether to load config if the firmware version match with current firmware. + * + * RETURN: + * 0: Don't upgrade + * 1: Upgrade + * + * PARAMETERS: + * *dev - pointer to device structure + * *ttconfig - pointer to touch_config structure + ******************************************************************************/ +static int pt_check_ttconfig_version_platform(struct device *dev, + struct pt_touch_config *ttconfig) +{ + struct pt_loader_data *ld = pt_get_loader_data(dev); + u32 fw_ver_config; + u32 fw_revctrl_config; + + if (!ld->si) { + pt_debug(dev, DL_INFO, + "%s: No firmware info found, DUT FW may be corrupted\n", + __func__); + return 0; + } + + fw_ver_config = get_unaligned_be16(ttconfig->fw_ver + 2); + /* 4 middle bytes are not used */ + fw_revctrl_config = get_unaligned_be32(ttconfig->fw_ver + 8); + + /* FW versions should match */ + if (pt_check_firmware_version(dev, fw_ver_config, + fw_revctrl_config)) { + pt_debug(dev, DL_ERROR, + "%s: FW versions mismatch\n", __func__); + return 0; + } + + /* Check PowerOn Self Test, TT_CFG CRC bit */ + if ((ld->si->ttdata.post_code & PT_POST_TT_CFG_CRC_MASK) == 0) { + pt_debug(dev, DL_ERROR, + "%s: POST, TT_CFG failed (%X), will upgrade\n", + __func__, ld->si->ttdata.post_code); + return 1; + } + + return pt_check_ttconfig_version(dev, ttconfig->param_regs->data, + ttconfig->param_regs->size); +} + +/******************************************************************************* + * FUNCTION: pt_get_platform_ttconfig + * + * SUMMARY: To get the pointer of right touch_config structure by panel id. + * + * RETURN: + * pointer to touch_config structure or null pointer if fail + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static struct pt_touch_config *pt_get_platform_ttconfig( + struct device *dev) +{ + struct pt_loader_data *ld = pt_get_loader_data(dev); + struct pt_touch_config **ttconfigs; + struct pt_touch_config *ttconfig; + u8 panel_id; + + panel_id = pt_get_panel_id(dev); + if (panel_id == PANEL_ID_NOT_ENABLED) { + /* TODO: Make debug message */ + pt_debug(dev, DL_INFO, + "%s: Panel ID not enabled, using legacy ttconfig\n", + __func__); + return ld->loader_pdata->ttconfig; + } + + ttconfigs = ld->loader_pdata->ttconfigs; + if (!ttconfigs) + return NULL; + + /* Find TT config according to the Panel ID */ + while ((ttconfig = *ttconfigs++)) { + if (ttconfig->panel_id == panel_id) { + /* TODO: Make debug message */ + pt_debug(dev, DL_INFO, + "%s: Found matching ttconfig:%p with Panel ID: 0x%02X\n", + __func__, ttconfig, ttconfig->panel_id); + return ttconfig; + } + pt_debug(dev, DL_ERROR, + "%s: Found mismatching ttconfig:%p with Panel ID: 0x%02X\n", + __func__, ttconfig, ttconfig->panel_id); + } + + return NULL; +} + +/******************************************************************************* + * FUNCTION: upgrade_ttconfig_from_platform + * + * SUMMARY: Get touch_firmware structure and perform upgrade if pass the + * firmware version check. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * forced - flag to force upgrade(1:force to upgrade) + ******************************************************************************/ +static int upgrade_ttconfig_from_platform(struct device *dev) +{ + struct pt_loader_data *ld = pt_get_loader_data(dev); + struct pt_touch_config *ttconfig; + struct touch_settings *param_regs; + + int rc = -ENODEV; + int upgrade; + + if (!ld->loader_pdata) { + pt_debug(dev, DL_ERROR, + "%s: No loader platform data\n", __func__); + return rc; + } + + ttconfig = pt_get_platform_ttconfig(dev); + if (!ttconfig) { + pt_debug(dev, DL_ERROR, "%s: No ttconfig data\n", __func__); + return rc; + } + + param_regs = ttconfig->param_regs; + if (!param_regs) { + pt_debug(dev, DL_ERROR, "%s: No touch parameters\n", + __func__); + return rc; + } + + if (!param_regs->data || !param_regs->size) { + pt_debug(dev, DL_ERROR, + "%s: Invalid touch parameters\n", __func__); + return rc; + } + + if (!ttconfig->fw_ver || !ttconfig->fw_vsize) { + pt_debug(dev, DL_ERROR, + "%s: Invalid FW version for touch parameters\n", + __func__); + return rc; + } + + upgrade = pt_check_ttconfig_version_platform(dev, ttconfig); + if (upgrade) + return pt_upgrade_ttconfig(dev, param_regs->data, + param_regs->size); + + return rc; +} +#endif /* CONFIG_TOUCHSCREEN_PARADE_PLATFORM_TTCONFIG_UPGRADE */ + +#ifdef CONFIG_TOUCHSCREEN_PARADE_MANUAL_TTCONFIG_UPGRADE +/******************************************************************************* + * FUNCTION: pt_config_data_write + * + * SUMMARY: The write method for the config_data sysfs node. The passed + * in data (config file) is written to the config_data buffer. + * + * RETURN: Size of passed in buffer is success + * + * PARAMETERS: + * *filp - pointer to file structure + * *kobj - pointer to kobject structure + * *bin_attr - pointer to bin_attribute structure + * buf - pointer to cmd input buffer + * offset - offset index to store input buffer + * count - size of data in buffer + ******************************************************************************/ +static ssize_t pt_config_data_write(struct file *filp, + struct kobject *kobj, struct bin_attribute *bin_attr, + char *buf, loff_t offset, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct pt_loader_data *data = pt_get_loader_data(dev); + u8 *p; + + pt_debug(dev, DL_INFO, "%s: offset:%lld count:%zu\n", + __func__, offset, count); + + mutex_lock(&data->config_lock); + + if (!data->config_loading) { + mutex_unlock(&data->config_lock); + return -ENODEV; + } + + p = krealloc(data->config_data, offset + count, GFP_KERNEL); + if (!p) { + kfree(data->config_data); + data->config_data = NULL; + mutex_unlock(&data->config_lock); + return -ENOMEM; + } + data->config_data = p; + + memcpy(&data->config_data[offset], buf, count); + data->config_size += count; + + mutex_unlock(&data->config_lock); + + return count; +} + +static struct bin_attribute bin_attr_config_data = { + .attr = { + .name = "config_data", + .mode = 0200, + }, + .size = 0, + .write = pt_config_data_write, +}; + +/******************************************************************************* + * FUNCTION: pt_verify_ttconfig_binary + * + * SUMMARY: Perform a simple size check if the firmware version match.And + * calculate the start pointer of config data to write and the size to write. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *bin_config_data - pointer to binary config data + * bin_config_size - size of binary config data + * **start - double pointer to config data where to be written + * *len - pointer to the size of config data to store + ******************************************************************************/ +static int pt_verify_ttconfig_binary(struct device *dev, + u8 *bin_config_data, int bin_config_size, u8 **start, int *len) +{ + struct pt_loader_data *ld = pt_get_loader_data(dev); + int header_size; + u16 config_size; + u32 fw_ver_config; + u32 fw_revctrl_config; + + if (!ld->si) { + pt_debug(dev, DL_ERROR, + "%s: No firmware info found, DUT FW may be corrupted\n", + __func__); + return -ENODEV; + } + + /* + * We need 11 bytes for FW version control info and at + * least 6 bytes in config (Length + Max Length + CRC) + */ + header_size = bin_config_data[0] + 1; + if (header_size < 11 || header_size >= bin_config_size - 6) { + pt_debug(dev, DL_ERROR, + "%s: Invalid header size %d\n", __func__, + header_size); + return -EINVAL; + } + + fw_ver_config = get_unaligned_be16(&bin_config_data[1]); + /* 4 middle bytes are not used */ + fw_revctrl_config = get_unaligned_be32(&bin_config_data[7]); + + /* FW versions should match */ + if (pt_check_firmware_version(dev, fw_ver_config, + fw_revctrl_config)) { + pt_debug(dev, DL_ERROR, + "%s: FW versions mismatch\n", __func__); + return -EINVAL; + } + + config_size = get_unaligned_le16(&bin_config_data[header_size]); + /* Perform a simple size check (2 bytes for CRC) */ + if (config_size != bin_config_size - header_size - 2) { + pt_debug(dev, DL_ERROR, + "%s: Config size invalid\n", __func__); + return -EINVAL; + } + + *start = &bin_config_data[header_size]; + *len = bin_config_size - header_size; + + return 0; +} + +/* + * 1: Start loading TT Config + * 0: End loading TT Config and perform upgrade + *-1: Exit loading + */ + +/******************************************************************************* + * FUNCTION: pt_config_loading_store + * + * SUMMARY: The store method for the config_loading sysfs node. The + * passed in value controls if config loading is performed. + * + * RETURN: Size of passed in buffer is success + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to buffer that hold the command parameters + * size - size of buf + ******************************************************************************/ +static ssize_t pt_config_loading_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct pt_loader_data *ld = pt_get_loader_data(dev); + long value; + u8 *start; + int length; + int rc; + + rc = kstrtol(buf, 10, &value); + if (rc < 0 || value < -1 || value > 1) { + pt_debug(dev, DL_ERROR, "%s: Invalid value\n", __func__); + return size; + } + + mutex_lock(&ld->config_lock); + + if (value == 1) + ld->config_loading = true; + else if (value == -1) + ld->config_loading = false; + else if (value == 0 && ld->config_loading) { + ld->config_loading = false; + if (ld->config_size == 0) { + pt_debug(dev, DL_ERROR, + "%s: No config data\n", __func__); + goto exit_free; + } + + rc = pt_verify_ttconfig_binary(dev, + ld->config_data, ld->config_size, + &start, &length); + if (rc) + goto exit_free; + + rc = pt_upgrade_ttconfig(dev, start, length); + } + +exit_free: + kfree(ld->config_data); + ld->config_data = NULL; + ld->config_size = 0; + + mutex_unlock(&ld->config_lock); + + if (rc) + return rc; + + return size; +} + +static DEVICE_ATTR(config_loading, 0200, + NULL, pt_config_loading_store); +#endif /* CONFIG_TOUCHSCREEN_PARADE_MANUAL_TTCONFIG_UPGRADE */ + +#ifdef CONFIG_TOUCHSCREEN_PARADE_PLATFORM_FW_UPGRADE +/******************************************************************************* + * FUNCTION: pt_forced_upgrade_store + * + * SUMMARY: The store method for the forced_upgrade sysfs node. The firmware + * loading is forced to performed with platform upgrade strategy. + * + * RETURN: Size of passed in buffer is success + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to buffer that hold the command parameters + * size - size of buf + ******************************************************************************/ +static ssize_t pt_forced_upgrade_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + int rc = upgrade_firmware_from_platform(dev, true); + + if (rc) + return rc; + return size; +} + +static DEVICE_ATTR(forced_upgrade, 0200, + NULL, pt_forced_upgrade_store); +#endif + +#ifdef CONFIG_TOUCHSCREEN_PARADE_BINARY_FW_UPGRADE +/******************************************************************************* + * FUNCTION: pt_manual_upgrade_store + * + * SUMMARY: The store method for the forced_upgrade sysfs node that it is + * caller for function upgrade_firmware_from_class() to allow upgrade firmware + * manually. + * + * RETURN: Size of passed in buffer is success + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to buffer that hold the command parameters + * size - size of buf + ******************************************************************************/ +static ssize_t pt_manual_upgrade_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct pt_loader_data *ld = pt_get_loader_data(dev); + u32 input_data[2] = {0}; + int length; + int rc = 0; + + length = cmd->parse_sysfs_input(dev, buf, size, input_data, + ARRAY_SIZE(input_data)); + + if (length != 1) { + pt_debug(dev, DL_WARN, "%s: Invalid number of arguments\n", + __func__); + rc = -EINVAL; + goto exit; + } + + if (input_data[0] < 0 || input_data[0] > 1) { + pt_debug(dev, DL_WARN, "%s: Invalid arguments\n", __func__); + rc = -EINVAL; + goto exit; + } + + _pt_pip2_update_bl_status(dev, UPDATE_FW_IDLE, PT_NO_INC); + if (ld->is_manual_upgrade_enabled) { + rc = -EBUSY; + goto exit; + } + + ld->is_manual_upgrade_enabled = 1; + + rc = upgrade_firmware_from_class(ld->dev); + if (rc < 0) + ld->is_manual_upgrade_enabled = 0; + +exit: + if (rc) + return rc; + return size; +} + +static DEVICE_ATTR(manual_upgrade, 0200, NULL, pt_manual_upgrade_store); +#endif /* CONFIG_TOUCHSCREEN_PARADE_BINARY_FW_UPGRADE */ + +/******************************************************************************* + * FUNCTION: pt_fw_and_config_upgrade + * + * SUMMARY: Perform all methods for firmware upgrade and config upgrade + * according to the definition of macro. + * + * PARAMETERS: + * *work_struct - pointer to work_struct structure + ******************************************************************************/ +static void pt_fw_and_config_upgrade( + struct work_struct *fw_and_config_upgrade) +{ + struct pt_loader_data *ld = container_of(fw_and_config_upgrade, + struct pt_loader_data, fw_and_config_upgrade); + struct device *dev = ld->dev; + struct pt_core_data *cd = dev_get_drvdata(dev); + int retry = 200; +#if PT_FW_UPGRADE \ + || defined(CONFIG_TOUCHSCREEN_PARADE_PLATFORM_TTCONFIG_UPGRADE) + u8 dut_gen = cmd->request_dut_generation(dev); +#endif + + /* + * Since the resume_scan command in pt_probe_complete() has + * no protection,it may cause problem for the commands in fw + * upgrade process during probe. Waiting for the probe to + * complete before performing fw upgrade can avoid this failure. + */ + while (!cd->core_probe_complete && retry--) + msleep(20); + + ld->si = cmd->request_sysinfo(dev); + if (!ld->si) + pt_debug(dev, DL_ERROR, + "%s: Fail get sysinfo pointer from core\n", + __func__); +#if !PT_FW_UPGRADE + pt_debug(dev, DL_INFO, + "%s: No FW upgrade method selected!\n", __func__); +#endif + +#ifdef CONFIG_TOUCHSCREEN_PARADE_PLATFORM_FW_UPGRADE + if (dut_gen == DUT_PIP1_ONLY) { + if (!upgrade_firmware_from_platform(dev, false)) + return; + } +#endif + +#ifdef CONFIG_TOUCHSCREEN_PARADE_BINARY_FW_UPGRADE + if (dut_gen == DUT_PIP2_CAPABLE) { + if (!pt_pip2_upgrade_firmware_from_builtin(dev)) + return; + pt_debug(dev, DL_WARN, "%s: Builtin FW upgrade failed\n", + __func__); + } else { + if (!upgrade_firmware_from_builtin(dev)) + return; + } +#endif + +#ifdef CONFIG_TOUCHSCREEN_PARADE_PLATFORM_TTCONFIG_UPGRADE + if (dut_gen == DUT_PIP1_ONLY) { + if (!upgrade_ttconfig_from_platform(dev)) + return; + } +#endif +} + +#ifdef CONFIG_TOUCHSCREEN_PARADE_BINARY_FW_UPGRADE +#ifdef TTDL_DIAGNOSTICS +/******************************************************************************* + * FUNCTION: _pt_pip2_get_flash_info + * + * SUMMARY: Sends a FLASH_INFO command to the DUT logging the results to kmsg + * + * PARAMETERS: + * *dev - pointer to device structure + * *read_buf - pointer to the read buffer array to store the response + ******************************************************************************/ +static void _pt_pip2_get_flash_info(struct device *dev, + u8 *read_buf) +{ + u16 actual_read_len; + int ret; + + /* Get flash info for debugging information */ + ret = cmd->nonhid_cmd->pip2_send_cmd(dev, + PT_CORE_CMD_UNPROTECTED, PIP2_CMD_ID_FLASH_INFO, + NULL, 0, read_buf, &actual_read_len); + if (!ret) { + pt_debug(dev, DL_DEBUG, + "%s --- FLASH Information ---\n", __func__); + pt_debug(dev, DL_DEBUG, + "%s Manufacturer ID: 0x%02x\n", + __func__, read_buf[PIP2_RESP_BODY_OFFSET]); + pt_debug(dev, DL_DEBUG, + "%s Memory Type : 0x%02x\n", + __func__, read_buf[PIP2_RESP_BODY_OFFSET + 1]); + pt_debug(dev, DL_DEBUG, + "%s Num Sectors : 0x%02x%02x%02x%02x\n", + __func__, read_buf[PIP2_RESP_BODY_OFFSET + 2], + read_buf[PIP2_RESP_BODY_OFFSET + 3], + read_buf[PIP2_RESP_BODY_OFFSET + 4], + read_buf[PIP2_RESP_BODY_OFFSET + 5]); + pt_debug(dev, DL_DEBUG, + "%s Sectors Size : 0x%02x%02x%02x%02x\n", + __func__, read_buf[PIP2_RESP_BODY_OFFSET + 6], + read_buf[PIP2_RESP_BODY_OFFSET + 7], + read_buf[PIP2_RESP_BODY_OFFSET + 8], + read_buf[PIP2_RESP_BODY_OFFSET + 9]); + pt_debug(dev, DL_DEBUG, + "%s Page Size : 0x%02x%02x%02x%02x\n", + __func__, read_buf[PIP2_RESP_BODY_OFFSET + 10], + read_buf[PIP2_RESP_BODY_OFFSET + 11], + read_buf[PIP2_RESP_BODY_OFFSET + 12], + read_buf[PIP2_RESP_BODY_OFFSET + 13]); + if (actual_read_len > 21) { + pt_debug(dev, DL_DEBUG, + "%s Status Reg1 : 0x%02x\n", + __func__, + read_buf[PIP2_RESP_BODY_OFFSET + 14]); + pt_debug(dev, DL_DEBUG, + "%s Status Reg2 : 0x%02x\n", + __func__, + read_buf[PIP2_RESP_BODY_OFFSET + 15]); + } + } +} +#endif /* TTDL_DIAGNOSTICS */ + +/******************************************************************************* + * FUNCTION: _pt_pip2_log_last_error + * + * SUMMARY: Sends a STATUS command to the DUT logging the results until all + * errors are cleared. Also sends a GET_LAST_ERRNO to get any Boot errors. + * This must be sent after the STATUS flush in order not to have this + * command cause another error. + * + * NOTE: This function support No Interrupt Solution and switch "pip2_send_cmd" + * function according to the global variable "bl_with_no_int". + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *read_buf - pointer to the read buffer array to store the response + ******************************************************************************/ +static int _pt_pip2_log_last_error(struct device *dev, + u8 *read_buf) +{ + u16 actual_read_len; + u8 loop = 5; + u8 info = 0xFF; + u8 error = 0xFF; + int ret; + PIP2_SEND_CMD pip2_send_cmd; + struct pt_core_data *cd = dev_get_drvdata(dev); + + if (cd->bl_with_no_int) + pip2_send_cmd = cmd->nonhid_cmd->pip2_send_cmd_no_int; + else + pip2_send_cmd = cmd->nonhid_cmd->pip2_send_cmd; + /* + * Send the STATUS command until no errors are found. + * The BL will store an error code for each layer of the stack, + * and each read will return one error. + */ + while (loop > 0 && error) { + + ret = pip2_send_cmd(dev, + PT_CORE_CMD_UNPROTECTED, PIP2_CMD_ID_STATUS, + NULL, 0, read_buf, &actual_read_len); + + if (!ret) { + info = (u8)read_buf[PIP2_RESP_BODY_OFFSET]; + error = (u8)read_buf[PIP2_RESP_BODY_OFFSET + 1]; + + pt_debug(dev, DL_ERROR, + "%s: STATUS: Status=0x%02X BOOT=%d BUSY=%d INT=%d ERR_PHY=%d ERR_REG=%d ERROR=0x%02X", + __func__, + (u8)read_buf[PIP2_RESP_STATUS_OFFSET], + info & 0x01, + (info & 0x02) >> 1, + (info & 0x04) >> 2, + (info & 0x18) >> 3, + (info & 0xE0) >> 5, + error); + } + loop--; + } + + /* Send the GET_LAST_ERROR command to get the last BL startup error */ + ret = pip2_send_cmd(dev, + PT_CORE_CMD_UNPROTECTED, PIP2_CMD_ID_GET_LAST_ERRNO, + NULL, 0, read_buf, &actual_read_len); + if (!ret) { + pt_debug(dev, DL_ERROR, + "%s: GET_LAST_ERR: Status=0x%02X ERRNO=0x%02X BOOTMODE=%d\n", + __func__, + (u8)read_buf[PIP2_RESP_STATUS_OFFSET], + (u8)read_buf[PIP2_RESP_BODY_OFFSET], + (u8)read_buf[PIP2_RESP_BODY_OFFSET + 1]); + } + + return ret; +} + +/******************************************************************************* + * FUNCTION: _pt_pip2_check_fw_ver + * + * SUMMARY: Compare the FW version in the bin file to the current FW. If the + * FW version in the bin file is greater an upgrade should be done. + * + * RETURN: + * -1: Do not upgrade firmware + * 0: Version info same, let caller decide + * 1: Do a firmware upgrade + * + * PARAMETERS: + * *dev - pointer to device structure + * *fw - pointer to the new FW image to load + * *hdr - pointer to the PIP2 bin file hdr structure + ******************************************************************************/ +static int _pt_pip2_check_fw_ver(struct device *dev, + const struct firmware *fw, struct pt_bin_file_hdr *hdr) +{ + u8 img_app_major_ver = fw->data[3]; + u8 img_app_minor_ver = fw->data[4]; + u32 img_app_rev_ctrl = fw->data[9]<<24 | fw->data[10]<<16 | + fw->data[11]<<8 | fw->data[12]; + + pt_debug(dev, DL_WARN, + "%s ATM - BL Image Version: %02x.%02x.%d\n", + __func__, img_app_major_ver, img_app_minor_ver, + img_app_rev_ctrl); + + pt_debug(dev, DL_WARN, + "%s ATM - Current FW Version: %02X.%02X.%d\n", + __func__, hdr->fw_major, hdr->fw_minor, hdr->fw_rev_ctrl); + + if ((256 * img_app_major_ver + img_app_minor_ver) > + (256 * hdr->fw_major + hdr->fw_minor)) { + pt_debug(dev, DL_WARN, + "ATM - bin file version > FW, will upgrade FW"); + return 1; + } + + if ((256 * img_app_major_ver + img_app_minor_ver) < + (256 * hdr->fw_major + hdr->fw_minor)) { + pt_debug(dev, DL_WARN, + "ATM - bin file version < FW, will NOT upgrade FW"); + return -EPERM; + } + + if (img_app_rev_ctrl > hdr->fw_rev_ctrl) { + pt_debug(dev, DL_WARN, + "bin file rev ctrl > FW, will upgrade FW"); + return 1; + } + + if (img_app_rev_ctrl < hdr->fw_rev_ctrl) { + pt_debug(dev, DL_WARN, + "bin file rev ctrl > FW, will NOT upgrade FW"); + return -EPERM; + } + + return 0; +} + +/******************************************************************************* + * FUNCTION: _pt_pip2_check_config_ver + * + * SUMMARY: Compare the Config version in the bin file to the current FW. If the + * config version in the bin file is greater an upgrade should be done. + * + * RETURN: + * -1: Do not upgrade firmware + * 0: Version info same, let caller decide + * 1: Do a firmware upgrade + * + * PARAMETERS: + * *dev - pointer to device structure + * *fw - pointer to the new FW image to load + * *hdr - pointer to the PIP2 bin file hdr structure + ******************************************************************************/ +static int _pt_pip2_check_config_ver(struct device *dev, + const struct firmware *fw, struct pt_bin_file_hdr *hdr) +{ + u16 fw_config_ver_new; + + /* Offset 17,18 is the TT Config version*/ + fw_config_ver_new = get_unaligned_be16(fw->data + 17); + pt_debug(dev, DL_WARN, + "%s ATM - BL Image Config Version: %d\n", + __func__, fw_config_ver_new); + + pt_debug(dev, DL_WARN, + "%s ATM - Current Config Version: %d\n", + __func__, hdr->config_ver); + + if (fw_config_ver_new > hdr->config_ver) { + pt_debug(dev, DL_WARN, + "ATM - bin file Config version > FW, will upgrade FW"); + return 1; + } else + pt_debug(dev, DL_WARN, + "ATM - bin file Config version <= FW, will NOT upgrade FW"); + + return 0; +} + +/******************************************************************************* + * FUNCTION: _pt_pip2_need_upgrade_due_to_fw_ver + * + * SUMMARY: Compare the FW version in the bin file to the current FW. If the + * FW version in the bin file is greater an upgrade should be done. + * + * PARAMETERS: + * *dev - pointer to device structure + * *fw - pointer to the new FW image to load + ******************************************************************************/ +static u8 _pt_pip2_need_upgrade_due_to_fw_ver(struct device *dev, + const struct firmware *fw) +{ + int ret; + u8 should_upgrade = 0; + struct pt_bin_file_hdr hdr = {0}; + + ret = cmd->request_pip2_bin_hdr(dev, &hdr); + if (ret != 0 || hdr.fw_major == 0xFF) { + pt_debug(dev, DL_WARN, + "App ver info not available, will upgrade FW"); + should_upgrade = 1; + goto exit; + } + + ret = _pt_pip2_check_fw_ver(dev, fw, &hdr); + if (ret == 0) + ret = _pt_pip2_check_config_ver(dev, fw, &hdr); + + if (ret > 0) + should_upgrade = 1; + +exit: + return should_upgrade; +} + +/******************************************************************************* + * FUNCTION: _pt_calibrate_flashless_dut + * + * SUMMARY: On a flashless DUT the FW would need to re-calibrate on every power + * cycle, so to speed up the BL process the FW does the calibraiton on the + * first power up and TTDL will read and store it in RAM to be able to + * restore it on subsequent resets of the DUT. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int _pt_calibrate_flashless_dut(struct device *dev) +{ + u8 rc = 0; + u8 cal_status = 1; + u16 cal_size = 0; + struct pt_cal_ext_data cal_data = {0}; + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_ttdata *ttdata = &cd->sysinfo.ttdata; + unsigned short cache_chip_id = 0; + unsigned short active_chip_id = 0; + + cmd->nonhid_cmd->manage_cal_data(dev, PT_CAL_DATA_INFO, &cal_size, + &cache_chip_id); + pt_debug(dev, DL_WARN, "%s: Stored CAL ID=0x%04X size=%d\n", + __func__, cache_chip_id, cal_size); + + active_chip_id = cmd->nonhid_cmd->calc_crc((u8 *)&ttdata->chip_rev, + 4 + PT_UID_SIZE); + pt_debug(dev, DL_WARN, "%s: Current Chip ID=0x%04X\n", + __func__, active_chip_id); + + if (cal_size == 0 || active_chip_id != cache_chip_id) { + memset(&cal_data, 0, sizeof(struct pt_cal_ext_data)); + + /* Calibrate_ext will also save CAL Data in TTDL cache */ + rc = cmd->nonhid_cmd->calibrate_ext(dev, 0, &cal_data, + &cal_status); + pt_debug(dev, DL_INFO, + "%s: Calibration Finished rc=%d\n", __func__, rc); + + /* Afer successful calibration read the stored size */ + if (!rc && cal_status == 0) { + rc = cmd->nonhid_cmd->manage_cal_data(dev, + PT_CAL_DATA_INFO, &cal_size, &cache_chip_id); + if (!rc) { + pt_debug(dev, DL_WARN, + "%s: First BL, Read %d Bytes of CAL\n", + __func__, cal_size); + } else { + pt_debug(dev, DL_ERROR, + "%s: First BL, Failed to read CAL\n", + __func__); + } + } else { + pt_debug(dev, DL_ERROR, + "%s: First BL, Calibration failed rc=%d\n", + __func__, rc); + } + rc = cmd->nonhid_cmd->resume_scanning(dev, 0); + if (rc) + pt_debug(dev, DL_ERROR, + "%s: First BL, Resume Scan failed rc=%d\n", + __func__, rc); + } else { + rc = cmd->nonhid_cmd->manage_cal_data(dev, PT_CAL_DATA_RESTORE, + &cal_size, &cache_chip_id); + if (!rc) + pt_debug(dev, DL_WARN, "%s: Restored CAL %d Bytes\n", + __func__, cal_size); + else + pt_debug(dev, DL_ERROR, "%s: Failed to restore CAL\n", + __func__); + + rc = cmd->nonhid_cmd->resume_scanning(dev, 0); + if (rc) + pt_debug(dev, DL_ERROR, + "%s: Resume Scan failed rc=%d\n", + __func__, rc); + } + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_pip2_firmware_cont + * + * SUMMARY: Bootload the DUT with a FW image using the PIP2 protocol. This + * includes getting the DUT into BL mode, writing the file to either SRAM + * or FLASH, and launching the application directly in SRAM or by resetting + * the DUT without the hostmode pin asserted. + * + * NOTE: Special care must be taken to support a DUT communicating in + * PIP2.0 where the length field is defined differently. + * NOTE: The write packet len is set so that the overall packet size is + * less than 255. The overhead is 9 bytes: 2 byte address (0101), + * 4 byte header, 1 byte file no. 2 byte CRC + * + * PARAMETERS: + * *fw - pointer to the new FW image to load + * *context - pointer to the device + ******************************************************************************/ +static void _pt_pip2_firmware_cont(const struct firmware *fw, + void *context) +{ + u8 read_buf[PT_MAX_PIP2_MSG_SIZE]; + u8 buf[PT_MAX_PIP2_MSG_SIZE]; + u8 *fw_img = NULL; + u16 write_len; + u8 mode = PT_MODE_UNKNOWN; + u8 retry_packet = 0; + u8 us_fw_used = 0; + u16 actual_read_len; + u16 status = 0; + u16 packet_size; + int fw_size = 0; + int remain_bytes; + int ret = 0; + int percent_cmplt; + int t; + int erase_status; + u32 max_file_size; + bool wait_for_calibration_complete = false; + PIP2_SEND_CMD pip2_send_cmd; + struct device *dev = context; + struct pt_loader_data *ld = pt_get_loader_data(dev); + struct pip2_loader_data *pip2_data = ld->pip2_data; + struct pt_core_data *cd = dev_get_drvdata(dev); + + pt_debug(dev, DL_WARN, "%s: ATM - Begin BL\n", __func__); + _pt_pip2_update_bl_status(dev, UPDATE_FW_IDLE, PT_NO_INC); + + if (cd->bus_ops->bustype == BUS_I2C) + packet_size = PIP2_BL_I2C_FILE_WRITE_LEN_PER_PACKET; + else + packet_size = PIP2_BL_SPI_FILE_WRITE_LEN_PER_PACKET; + + ret = cmd->request_exclusive(dev, PT_LDR_REQUEST_EXCLUSIVE_TIMEOUT); + if (ret < 0) { + pt_debug(dev, DL_ERROR, + "%s: Failed to aquire exclusive access\n", __func__); + update_fw_status = UPDATE_FW_EXCLUSIVE_ACCESS_ERROR; + goto exit; + } + _pt_pip2_update_bl_status(dev, 0, 1); + + if (!fw) { + if (ld->pip2_load_builtin) { + pt_debug(dev, DL_ERROR, + "%s: No builtin firmware\n", __func__); + ld->builtin_bin_fw_status = -EINVAL; + pt_debug(dev, DL_ERROR, "%s: Exit BL\n", __func__); + _pt_pip2_update_bl_status(dev, + UPDATE_FW_NO_FW_PROVIDED, PT_NO_INC); + goto exit; + } else { + fw_img = kzalloc(PT_PIP2_MAX_FILE_SIZE, GFP_KERNEL); + us_fw_used = 1; + ret = cmd->nonhid_cmd->read_us_file(dev, + cd->pip2_us_file_path, fw_img, &fw_size); + if (ret) { + pt_debug(dev, DL_ERROR, + "%s: No firmware provided to load\n", + __func__); + pt_debug(dev, DL_ERROR, "%s: Exit BL\n", + __func__); + _pt_pip2_update_bl_status(dev, + UPDATE_FW_NO_FW_PROVIDED, PT_NO_INC); + goto exit; + } + } + } else { + fw_img = (u8 *)&(fw->data[0]); + fw_size = fw->size; + } + + if (!fw_img || !fw_size) { + pt_debug(dev, DL_ERROR, + "%s: Invalid fw or file size=%d\n", __func__, + (int)fw_size); + _pt_pip2_update_bl_status(dev, UPDATE_FW_INVALID_FW_IMAGE, + PT_NO_INC); + goto exit; + } + if (ld->pip2_load_file_no == PIP2_FW_FILE) { + if (fw_img[0] >= (fw_size + 1)) { + pt_debug(dev, DL_ERROR, + "%s: Firmware format is invalid\n", __func__); + _pt_pip2_update_bl_status(dev, + UPDATE_FW_INVALID_FW_IMAGE, PT_NO_INC); + goto exit; + } + } + + cd->fw_updating = true; + wake_up(&cd->wait_q); + + _pt_pip2_update_bl_status(dev, 0, 1); + pt_debug(dev, DL_INFO, + "%s: Found file of size: %d bytes\n", __func__, (int)fw_size); + pm_runtime_get_sync(dev); + _pt_pip2_update_bl_status(dev, 0, 1); + + /* Wait for completion of FW upgrade thread before continuing */ + if (!ld->pip2_load_builtin) + init_completion(&pip2_data->pip2_fw_upgrade_complete); + + _pt_pip2_update_bl_status(dev, 0, 1); + + cmd->request_stop_wd(dev); + + if (cd->flashless_dut) { + cd->bl_pip_ver_ready = false; + cd->app_pip_ver_ready = false; + } + + /* + * 'mode' is used below, if DUT was already in BL before attempting to + * enter the BL, there was either no FW to run or the FW was corrupt + * so either way force a BL + */ + ret = cmd->request_pip2_enter_bl(dev, &mode, NULL); + if (ret) { + pt_debug(dev, DL_ERROR, "%s: Failed to enter BL\n", + __func__); + _pt_pip2_update_bl_status(dev, UPDATE_FW_ENTER_BL_ERROR, + PT_NO_INC); + goto exit; + } + _pt_pip2_update_bl_status(dev, 0, 1); + + /* Only compare FW ver or previous mode when doing a built-in upgrade */ + if (ld->pip2_load_builtin) { + if (_pt_pip2_need_upgrade_due_to_fw_ver(dev, fw) || + mode == PT_MODE_BOOTLOADER) { + _pt_pip2_update_bl_status(dev, 0, 1); + } else { + _pt_pip2_update_bl_status(dev, + UPDATE_FW_VERSION_ERROR, PT_NO_INC); + goto exit; + } + } + + pt_debug(dev, DL_INFO, "%s OPEN File %d for write\n", + __func__, ld->pip2_load_file_no); + ret = cmd->nonhid_cmd->pip2_file_open(dev, ld->pip2_load_file_no); + if (ret < 0) { + pt_debug(dev, DL_ERROR, "%s Open file %d failed\n", + __func__, ld->pip2_load_file_no); + _pt_pip2_update_bl_status(dev, UPDATE_FW_FILE_OPEN_ERROR, + PT_NO_INC); + goto exit; + } + pip2_data->pip2_file_handle = ret; + _pt_pip2_update_bl_status(dev, 0, 1); + + /* Regarding to TC3315, the size of RAM_FILE is less than fw image */ + if (ld->pip2_load_file_no != PIP2_RAM_FILE) { + ret = cmd->nonhid_cmd->pip2_file_get_stats(dev, + ld->pip2_load_file_no, NULL, &max_file_size); + if (ret) { + pt_debug(dev, DL_ERROR, + "%s: Failed to get_file_state ret=%d\n", + __func__, ret); + goto exit_close_file; + } + if (fw_size > max_file_size) { + pt_debug(dev, DL_ERROR, + "%s: Firmware image(%d) is over size(%d)\n", + __func__, fw_size, max_file_size); + _pt_pip2_update_bl_status(dev, + UPDATE_FW_INVALID_FW_IMAGE, PT_NO_INC); + goto exit_close_file; + } +#ifdef TTDL_DIAGNOSTICS + /* Log the Flash part info */ + if (cd->debug_level >= DL_DEBUG) + _pt_pip2_get_flash_info(dev, read_buf); +#endif + /* Erase file before loading */ + ret = cmd->nonhid_cmd->pip2_file_erase(dev, + ld->pip2_load_file_no, &erase_status); + if (ret < 0) { + pt_debug(dev, DL_ERROR, + "%s: File erase failed rc=%d status=%d\n", + __func__, ret, erase_status); + _pt_pip2_update_bl_status(dev, + UPDATE_FW_ERASE_ERROR, PT_NO_INC); + goto exit_close_file; + } + _pt_pip2_update_bl_status(dev, 0, 5); + } + + remain_bytes = fw_size; + buf[0] = pip2_data->pip2_file_handle; + pt_debug(dev, DL_WARN, + "%s: ATM - Writing %d bytes of firmware data now\n", + __func__, fw_size); + + /* + * No IRQ function is used to BL to reduce BL time due to any IRQ + * latency. + */ + if (cd->bl_with_no_int) { + pip2_send_cmd = cmd->nonhid_cmd->pip2_send_cmd_no_int; + disable_irq_nosync(cd->irq); + } else + pip2_send_cmd = cmd->nonhid_cmd->pip2_send_cmd; + + /* Continue writing while data remains */ + while (remain_bytes > packet_size) { + write_len = packet_size; + + /* Don't update BL status on every pass */ + if (remain_bytes % 2000 < packet_size) { + /* Calculate % complete for update_fw_status sysfs */ + percent_cmplt = (fw_size - remain_bytes) * + 100 / fw_size; + if (percent_cmplt > 0 && + percent_cmplt > UPDATE_FW_ACTIVE_90) + percent_cmplt = UPDATE_FW_ACTIVE_90; + _pt_pip2_update_bl_status(dev, percent_cmplt, + PT_NO_INC); + +#ifdef TTDL_DIAGNOSTICS + pt_debug(dev, DL_INFO, + "Wrote %d bytes with %d bytes remaining\n", + fw_size - remain_bytes - write_len, + remain_bytes); +#endif + } + if (retry_packet > 0) { +#ifdef TTDL_DIAGNOSTICS + cd->bl_retry_packet_count++; + cmd->request_toggle_err_gpio(dev, + PT_ERR_GPIO_BL_RETRY_PACKET); + pt_debug(dev, DL_WARN, + "%s: === Retry Packet #%d ===\n", + __func__, retry_packet); +#endif + /* Get and log the last error(s) */ + _pt_pip2_log_last_error(dev, read_buf); + } + + memcpy(&buf[1], fw_img, write_len); + ret = pip2_send_cmd(dev, + PT_CORE_CMD_UNPROTECTED, PIP2_CMD_ID_FILE_WRITE, + buf, write_len + 1, read_buf, &actual_read_len); + status = read_buf[PIP2_RESP_STATUS_OFFSET]; + + /* Write cmd successful with a fail status */ + if (!ret && status) { + /* + * The last time through the loop when remain_bytes = + * write_len, no partial payload will remain, the last + * successful write (when writing to RAM) will respond + * with EOF status, writing to FLASH will respond with a + * standard success status of 0x00 + */ + if ((ld->pip2_load_file_no == PIP2_RAM_FILE) && + (status == PIP2_RSP_ERR_END_OF_FILE) && + (remain_bytes == write_len)) { + pt_debug(dev, DL_WARN, + "%s Last write, ret = 0x%02x\n", + __func__, status); + /* Drop out of the while loop */ + break; + } + _pt_pip2_update_bl_status(dev, + UPDATE_FW_WRITE_ERROR, PT_NO_INC); + pt_debug(dev, DL_ERROR, + "%s file write failure, status = 0x%02x\n", + __func__, status); + if (retry_packet >= 3) { + /* Tripple retry error - break */ + remain_bytes = 0; + pt_debug(dev, DL_ERROR, + "%s %d - Packet status error - Break\n", + __func__, status); + } else { + retry_packet++; + } + } else if (ret) { + /* Packet write failed - retry 3x */ + if (retry_packet >= 3) { + remain_bytes = 0; + pt_debug(dev, DL_ERROR, + "%s %d - Packet cmd error - Break\n", + __func__, ret); + } + retry_packet++; + } else { + /* Cmd success and status success */ + retry_packet = 0; + } + + if (retry_packet == 0) { + fw_img += write_len; + remain_bytes -= write_len; + } + } + /* Write the remaining bytes if any remain */ + if (remain_bytes > 0 && retry_packet == 0) { + pt_debug(dev, DL_INFO, + "Write last %d bytes to File = 0x%02x\n", + remain_bytes, pip2_data->pip2_file_handle); + memcpy(&buf[1], fw_img, remain_bytes); + while (remain_bytes > 0 && retry_packet <= 3) { + ret = pip2_send_cmd(dev, PT_CORE_CMD_UNPROTECTED, + PIP2_CMD_ID_FILE_WRITE, buf, remain_bytes + 1, + read_buf, &actual_read_len); + status = read_buf[PIP2_RESP_STATUS_OFFSET]; + if (ret || status) { + /* + * Any non zero status when writing to FLASH is + * an error. Only when writing to RAM, the last + * packet must respond with an EOF status + */ + if ((ld->pip2_load_file_no >= PIP2_FW_FILE) || + (ld->pip2_load_file_no == PIP2_RAM_FILE && + status != PIP2_RSP_ERR_END_OF_FILE)) { + _pt_pip2_update_bl_status(dev, + UPDATE_FW_WRITE_ERROR, + PT_NO_INC); + pt_debug(dev, DL_ERROR, + "%s Write Fail-status=0x%02x\n", + __func__, status); + retry_packet++; + } else if (ld->pip2_load_file_no == + PIP2_RAM_FILE && + status == PIP2_RSP_ERR_END_OF_FILE) { + /* Special case EOF writing to SRAM */ + remain_bytes = 0; + status = 0; + } + } else { + remain_bytes = 0; + } + } + } + + if (cd->bl_with_no_int) + enable_irq(cd->irq); + + if (remain_bytes == 0 && retry_packet == 0) + _pt_pip2_update_bl_status(dev, UPDATE_FW_ACTIVE_99, PT_NO_INC); + + if (retry_packet >= 3) { + /* A packet write failure occurred 3x */ + pt_debug(dev, DL_ERROR, + "%s: BL terminated due to consecutive write errors\n", + __func__); + _pt_pip2_update_bl_status(dev, UPDATE_FW_WRITE_ERROR, + PT_NO_INC); + } else if (status) { + ret = status; + pt_debug(dev, DL_ERROR, + "%s: File write failed with status=%d\n", + __func__, status); + _pt_pip2_update_bl_status(dev, UPDATE_FW_WRITE_ERROR, + PT_NO_INC); + } else + pt_debug(dev, DL_INFO, + "%s: BIN file write finished successfully\n", __func__); + + ret = cmd->nonhid_cmd->pip2_file_close(dev, ld->pip2_load_file_no); + if (ret != ld->pip2_load_file_no) { + pt_debug(dev, DL_ERROR, + "%s file close failure\n", __func__); + _pt_pip2_update_bl_status(dev, UPDATE_FW_FILE_CLOSE_ERROR, + PT_NO_INC); + goto exit; + } + + /* When updating non FW files, stay in BL */ + if (ld->pip2_load_file_no >= PIP2_CONFIG_FILE) + goto exit; + + if ((ld->pip2_load_file_no == PIP2_RAM_FILE) && + (update_fw_status < UPDATE_FW_COMPLETE)) { + /* When writing to RAM don't reset, just launch application */ + pt_debug(dev, DL_INFO, + "%s Sending execute command now...\n", __func__); + cd->startup_status = STARTUP_STATUS_START; + ret = cmd->nonhid_cmd->pip2_send_cmd(dev, + PT_CORE_CMD_UNPROTECTED, PIP2_CMD_ID_EXECUTE, + NULL, 0, read_buf, &actual_read_len); + status = read_buf[PIP2_RESP_STATUS_OFFSET]; + if (ret || status) { + pt_debug(dev, DL_ERROR, + "%s Execute command failure\n", __func__); + _pt_pip2_update_bl_status(dev, + UPDATE_FW_EXECUTE_ERROR, PT_NO_INC); + goto exit; + } + } else if (ld->pip2_load_file_no == PIP2_FW_FILE && + update_fw_status < UPDATE_FW_COMPLETE) { + pt_debug(dev, DL_INFO, + "%s Toggle TP_XRES now...\n", __func__); + cmd->request_reset(dev, PT_CORE_CMD_UNPROTECTED); + } + pt_debug(dev, DL_INFO, "%s: APP launched\n", __func__); + + /* If any error occurred simply close the file and exit */ + if (update_fw_status > UPDATE_FW_COMPLETE) + goto exit_close_file; + + /* Wait for FW reset sentinel from reset or execute for up to 500ms */ + t = wait_event_timeout(cd->wait_q, + (cd->startup_status >= STARTUP_STATUS_FW_RESET_SENTINEL), + msecs_to_jiffies(PT_BL_WAIT_FOR_SENTINEL)); + if (IS_TMO(t)) { + pt_debug(dev, DL_WARN, + "%s: 0x%04X Timeout waiting for FW sentinel", + __func__, cd->startup_status); + } + + /* Double verify DUT is alive and well in Application mode */ + if (cd->startup_status & STARTUP_STATUS_FW_RESET_SENTINEL) { + ret = cmd->request_pip2_get_mode_sysmode(dev, + PT_CORE_CMD_UNPROTECTED, &mode, NULL); + pt_debug(dev, DL_WARN, "%s: mode = %d (Expected 2)", + __func__, mode); + if (mode != PT_MODE_OPERATIONAL) { + pt_debug(dev, DL_ERROR, + "%s ERROR: Not in App mode as expected\n", + __func__); + _pt_pip2_update_bl_status(dev, UPDATE_FW_MODE_ERROR, + PT_NO_INC); + goto exit; + } + } else { + pt_debug(dev, DL_ERROR, "%s: FW sentinel not seen 0x%04X\n", + __func__, cd->startup_status); + _pt_pip2_log_last_error(dev, read_buf); + _pt_pip2_update_bl_status(dev, UPDATE_FW_SENTINEL_NOT_SEEN, + PT_NO_INC); + goto exit; + } + + /* On a Flashless DUT save or restore the CAL data */ + if (cd->cal_cache_in_host == PT_FEATURE_ENABLE) + _pt_calibrate_flashless_dut(dev); + + /* Subscribe calibration task if calibration flag is set */ + if (ld->loader_pdata + && (ld->loader_pdata->flags + & PT_LOADER_FLAG_CALIBRATE_AFTER_FW_UPGRADE) + && (cd->cal_cache_in_host == PT_FEATURE_DISABLE)) { +#if (KERNEL_VERSION(3, 13, 0) <= LINUX_VERSION_CODE) + reinit_completion(&ld->calibration_complete); +#else + INIT_COMPLETION(ld->calibration_complete); +#endif + /* set up call back for startup */ + pt_debug(dev, DL_INFO, "%s: Adding callback for calibration\n", + __func__); + ret = cmd->subscribe_attention(dev, PT_ATTEN_STARTUP, + PT_LOADER_NAME, pt_calibration_attention, 0); + if (ret) { + pt_debug(dev, DL_ERROR, + "%s: Failed adding callback for calibration\n", + __func__); + ret = 0; + } else + wait_for_calibration_complete = true; + } + + pt_debug(dev, DL_INFO, "%s: == PIP2 FW upgrade finished ==\n", + __func__); + goto exit; + +exit_close_file: + ret = cmd->nonhid_cmd->pip2_file_close(dev, ld->pip2_load_file_no); + if (ret < 0) { + pt_debug(dev, DL_ERROR, + "%s file close failure\n", __func__); + _pt_pip2_update_bl_status(dev, UPDATE_FW_FILE_CLOSE_ERROR, + PT_NO_INC); + } + +exit: + cd->fw_updating = false; + if (us_fw_used) + kfree(fw_img); + if (ld->pip2_load_file_no > PIP2_FW_FILE) + goto exit_staying_in_bl; + + cmd->release_exclusive(dev); + + pm_runtime_put_sync(dev); + if (fw) + release_firmware(fw); + + /* + * For built-in FW update, it should not warn builtin_bin_fw_status + * for each bootup since update_fw_status would be + * UPDATE_FW_VERSION_ERROR in most situations because firmware + * should have been up to date. + */ + if (ld->pip2_load_builtin) { + if ((update_fw_status == UPDATE_FW_ACTIVE_99) || + (update_fw_status == UPDATE_FW_VERSION_ERROR)) + ld->builtin_bin_fw_status = 0; + else + ld->builtin_bin_fw_status = -EINVAL; + } + + if ((update_fw_status == UPDATE_FW_ACTIVE_99) || + (update_fw_status == UPDATE_FW_VERSION_ERROR)) { + pt_debug(dev, DL_WARN, "%s: Queue ENUM\n", __func__); + cmd->request_enum(dev, true); + } + if (update_fw_status < UPDATE_FW_COMPLETE) + _pt_pip2_update_bl_status(dev, UPDATE_FW_COMPLETE, PT_NO_INC); + + if (wait_for_calibration_complete) + wait_for_completion(&ld->calibration_complete); + + pt_debug(dev, DL_INFO, "%s: Starting watchdog\n", __func__); + ret = cmd->request_start_wd(dev); + + /* + * When in No-Flash mode allow auto BL after any BL. + * There is an issue where setting flashless mode via drv_debug + * can happen in the middle of pt_pip2_enter_bl() which will revert + * the flashless_auto_bl value back to what it was when the function + * started. + */ + if (cd->flashless_dut) + cd->flashless_auto_bl = PT_ALLOW_AUTO_BL; + + return; + +exit_staying_in_bl: + /* When updating a non FW file, a restart is not wanted. Stay in BL */ + _pt_pip2_update_bl_status(dev, UPDATE_FW_COMPLETE, PT_NO_INC); + cmd->release_exclusive(dev); + pm_runtime_put_sync(dev); + if (fw) + release_firmware(fw); +} + +#define PIP2_MAX_FILE_NAMES 3 +/******************************************************************************* + * FUNCTION: pt_pip2_upgrade_firmware_from_builtin + * + * SUMMARY: Bootload the DUT with a built in firmware binary image. + * Load either a SRAM image "ttdl_fw_RAM.bin" or a FLASH image + * "ttdl_fw.bin" with the priority being the SRAM image. + * + * PARAMETERS: + * *dev - pointer to the device structure + ******************************************************************************/ +static int pt_pip2_upgrade_firmware_from_builtin(struct device *dev) +{ + struct pt_loader_data *ld = pt_get_loader_data(dev); + struct pt_core_data *cd = dev_get_drvdata(dev); + const struct firmware *fw_entry = NULL; + int retval; + int rc = 0; + int read_size = 16; + u8 image[16]; + int index = 0; + int file_count = 0; + char *filename[PIP2_MAX_FILE_NAMES]; + + /* + * Load the supported filenames in the correct search order + * 0 - "tt_fw<_PIDX>.bin" + * 1 - "XXXX_tt_fw<_PIDX>.bin" where XXXX = Silicon ID + * 2 - "tt_fw.bin", default FW name + */ + + filename[file_count++] = generate_firmware_filename(dev); + filename[file_count++] = generate_silicon_id_firmware_filename(dev); + if (pt_get_panel_id(dev) != PANEL_ID_NOT_ENABLED) { + filename[file_count] = + kzalloc(sizeof(PT_FW_FILE_NAME), GFP_KERNEL); + memcpy(filename[file_count++], PT_FW_FILE_NAME, + sizeof(PT_FW_FILE_NAME)); + } + + for (index = 0; index < file_count; index++) { + if (!filename[index]) + return -ENOMEM; + } + + if (cd->flashless_dut) { + pt_debug(dev, DL_INFO, + "%s: Proceed to BL flashless DUT\n", __func__); + ld->pip2_load_file_no = PIP2_RAM_FILE; + if (cd->pip2_us_file_path[0] == '\0') { + ld->pip2_load_builtin = true; + pt_debug(dev, DL_WARN, + "%s: US Path not defined, BL from built-in\n", + __func__); + } else { + /* Read a few bytes to see if file exists */ + rc = cmd->nonhid_cmd->read_us_file(dev, + cd->pip2_us_file_path, image, &read_size); + if (!rc) { + ld->pip2_load_builtin = false; + pt_debug(dev, DL_WARN, + "%s: %s Found, BL from US\n", + __func__, cd->pip2_us_file_path); + goto ready; + } else { + ld->pip2_load_builtin = true; + pt_debug(dev, DL_WARN, + "%s: ATM - %s NOT Found, BL from built-in\n", + __func__, cd->pip2_us_file_path); + } + } + } else { + ld->pip2_load_file_no = PIP2_FW_FILE; + ld->pip2_load_builtin = true; + } + + /* Look for any FW file name match and request the FW */ + mutex_lock(&cd->firmware_class_lock); + index = 0; + while (index < file_count) { + pt_debug(dev, DL_INFO, "%s: Request FW class file: %s\n", + __func__, filename[index]); + retval = request_firmware_direct(&fw_entry, + filename[index], dev); + if (retval < 0) { + pt_debug(dev, DL_WARN, "%s: ATM - Fail request FW %s load\n", + __func__, filename[index]); + } else { + pt_debug(dev, DL_INFO, "%s: FW %s class file loading\n", + __func__, filename[index]); + break; + } + index++; + } + + /* No matching file names found */ + if (index == file_count) { + pt_debug(dev, DL_WARN, "%s: No FW is found\n", __func__); + goto exit; + } + +ready: + _pt_pip2_firmware_cont(fw_entry, dev); + retval = ld->builtin_bin_fw_status; +exit: + mutex_unlock(&cd->firmware_class_lock); + index = 0; + while (index < file_count) + kfree(filename[index++]); + + return retval; +} + +/******************************************************************************* + * FUNCTION: pt_pip2_create_fw_class + * + * SUMMARY: Create the firmware class but don't actually laod any FW to the + * DUT. This creates all the sysfs nodes needed for a user to bootload + * the DUT with their own bin file. + * + * PARAMETERS: + * *pip2_data - pointer to the PIP2 loader data structure + ******************************************************************************/ +static int pt_pip2_create_fw_class(struct pip2_loader_data *pip2_data) +{ + + int ret = 0; + struct device *dev = pip2_data->dev; + struct pt_loader_data *ld = pt_get_loader_data(dev); + + /* + * The file name dev_name(dev) is tied with bus name and usually + * it is "x-0024". This name is wanted to keep consistency + * (e.g. /sys/class/firmware/x-0024/) for the path of fw class + * nodes with different kernel release. Also it is an invalid bin + * file name used intentionally because request_firmware_nowait + * will not find the file which is what we want and then simply + * create the fw class nodes. + */ + ld->pip2_load_builtin = false; + pt_debug(dev, DL_INFO, "%s: Request FW Class", __func__); + ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_NOHOTPLUG, + dev_name(dev), dev, GFP_KERNEL, dev, + _pt_pip2_firmware_cont); + if (ret) { + pt_debug(dev, DL_ERROR, + "%s: ERROR requesting firmware class\n", __func__); + } + + return ret; +} + +/******************************************************************************* + * FUNCTION: pt_pip2_bl_from_file_work + * + * SUMMARY: The work function to schedule the BL work for PIP2 only. + * + * PARAMETERS: + * *bl_from_file - pointer to work_struct structure + ******************************************************************************/ +static void pt_pip2_bl_from_file_work(struct work_struct *pip2_bl_from_file) +{ + struct pt_loader_data *ld = container_of(pip2_bl_from_file, + struct pt_loader_data, pip2_bl_from_file); + struct device *dev = ld->dev; + const struct firmware *fw_entry = NULL; + + _pt_pip2_firmware_cont(fw_entry, dev); +} + +/******************************************************************************* + * FUNCTION: pt_bl_from_file_work + * + * SUMMARY: The work function to schedule the BL work for PIP2 or PIP1 + * according to the active_dut_generation in core data. + * + * PARAMETERS: + * *bl_from_file - pointer to work_struct structure + ******************************************************************************/ +static void pt_bl_from_file_work(struct work_struct *bl_from_file) +{ + struct pt_loader_data *ld = container_of(bl_from_file, + struct pt_loader_data, bl_from_file); + struct device *dev = ld->dev; + const struct firmware *fw_entry = NULL; + u8 dut_gen = cmd->request_dut_generation(dev); + + if (dut_gen == DUT_PIP2_CAPABLE) + _pt_pip2_firmware_cont(fw_entry, dev); + else if (dut_gen == DUT_PIP1_ONLY) + _pt_pip1_bl_from_file(dev); +} + +/******************************************************************************* + * FUNCTION: pt_pip2_bl_from_file_show + * + * SUMMARY: The show method for the "pip2_bl_from_file" sysfs node. The + * scheduled work will perform PIP2 BL. + * + * NOTE: Since this function doesn't set pip2_load_file_no, it will use the + * value what has been stored there. + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes structure + * *buf - pointer to print output buffer + ******************************************************************************/ +static ssize_t pt_pip2_bl_from_file_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pt_loader_data *ld = pt_get_loader_data(dev); + struct pt_core_data *cd = dev_get_drvdata(dev); + int rc = 0; + int read_size = 2; + u8 image[2]; + + mutex_lock(&cd->firmware_class_lock); + ld->pip2_load_builtin = false; + mutex_unlock(&cd->firmware_class_lock); + + /* Read a few bytes to see if file exists */ + rc = cmd->nonhid_cmd->read_us_file(dev, + cd->pip2_us_file_path, image, &read_size); + + if (!rc) { + schedule_work(&ld->pip2_bl_from_file); + + return scnprintf(buf, PT_MAX_PRBUF_SIZE, + "Status: %d\n" + "BL File: %s\n", + rc, cd->pip2_us_file_path); + } else { + return scnprintf(buf, PT_MAX_PRBUF_SIZE, + "Status: %d\n" + "BL File: '%s' - Does not exist\n", + rc, cd->pip2_us_file_path); + } +} + +/******************************************************************************* + * FUNCTION: pt_bl_from_file_show + * + * SUMMARY: The show method for the "pt_bl_from_file" sysfs node. The scheduled + * work can perform either PIP1 BL and PIP2 BL according to the + * active_dut_generation of core data. + * + * NOTE: Since this function doesn't set pip2_load_file_no, it will use the + * value what has been stored there. + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes structure + * *buf - pointer to print output buffer + ******************************************************************************/ +static ssize_t pt_bl_from_file_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pt_loader_data *ld = pt_get_loader_data(dev); + struct pt_core_data *cd = dev_get_drvdata(dev); + int rc = 0; + int read_size = 2; + u8 dut_gen = cmd->request_dut_generation(dev); + u8 image[2]; + + mutex_lock(&cd->firmware_class_lock); + ld->pip2_load_builtin = false; + mutex_unlock(&cd->firmware_class_lock); + + /* Read a few bytes to see if file exists */ + rc = cmd->nonhid_cmd->read_us_file(dev, + cd->pip2_us_file_path, image, &read_size); + + if (dut_gen == DUT_UNKNOWN) { + rc = -EINVAL; + return scnprintf(buf, PT_MAX_PRBUF_SIZE, + "Status: %d\n" + "BL File: '%s' - Failed, DUT Generation could not be determined\n", + rc, cd->pip2_us_file_path); + } else if (!rc) { + schedule_work(&ld->bl_from_file); + + return scnprintf(buf, PT_MAX_PRBUF_SIZE, + "Status: %d\n" + "BL File: %s\n", + rc, cd->pip2_us_file_path); + } else { + return scnprintf(buf, PT_MAX_PRBUF_SIZE, + "Status: %d\n" + "BL File: '%s' - Does not exist\n", + rc, cd->pip2_us_file_path); + } +} + +/******************************************************************************* + * FUNCTION: pt_pip2_bl_from_file_store + * + * SUMMARY: The store method for the "pip2_bl_from_file" and "pt_bl_from_file" + * sysfs node. Used to allow any file path[necessary] and file_no[optional] to + * be used to BL, for example: "echo /data/pt_fw 1 > pip2_bl_from_file", and + * do the "cat" will perform FW loader process. + * + * NOTE: the last char of file path in buf which is a '\n' is not copied. + * NOTE: the default file_no is PIP2_RAM_FILE. + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to output buffer + * size - size of data in buffer + ******************************************************************************/ +static ssize_t pt_pip2_bl_from_file_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct pt_loader_data *ld = pt_get_loader_data(dev); + struct pt_core_data *cd = dev_get_drvdata(dev); + const char *deli_space = " ", *deli_comma = ","; + char name_space[PT_MAX_PATH_SIZE]; + char *ptr_left = NULL, *ptr_right = name_space; + u8 file_no = PIP2_RAM_FILE; + u32 input_data[2]; + int length; + bool file_no_set = false; + + memset(name_space, 0, PT_MAX_PATH_SIZE); + memset(cd->pip2_us_file_path, 0, PT_MAX_PATH_SIZE); + if (size <= PT_MAX_PATH_SIZE) { + memcpy(name_space, buf, size); + ptr_left = strsep(&ptr_right, deli_space); + if (ptr_right == NULL) { + ptr_right = name_space; + ptr_left = strsep(&ptr_right, deli_comma); + } + + if (ptr_right != NULL) { + length = cmd->parse_sysfs_input( + dev, ptr_right, strlen(ptr_right), input_data, + ARRAY_SIZE(input_data)); + if (length <= 0) { + pt_debug(dev, DL_ERROR, + "%s: Input format error!\n", __func__); + return -EINVAL; + } + file_no_set = true; + file_no = input_data[0]; + } + + pt_debug(dev, DL_WARN, "%s:Path=%s, File_no=%s(%d)\n", __func__, + ptr_left, ptr_right, file_no); + + if ((file_no_set) && + ((file_no < PIP2_RAM_FILE) || (file_no > PIP2_FILE_MAX))) { + pt_debug(dev, DL_WARN, "%s:Invalid File_no = %d\n", + __func__, file_no); + return -EINVAL; + } + + mutex_lock(&cd->firmware_class_lock); + ld->pip2_load_file_no = file_no; + mutex_unlock(&cd->firmware_class_lock); + + if (ptr_left[strlen(ptr_left) - 1] == '\n') + memcpy(cd->pip2_us_file_path, ptr_left, + strlen(ptr_left) - 1); + else + memcpy(cd->pip2_us_file_path, ptr_left, + strlen(ptr_left)); + } + + return size; +} +static DEVICE_ATTR(pip2_bl_from_file, 0644, + pt_pip2_bl_from_file_show, pt_pip2_bl_from_file_store); + +static DEVICE_ATTR(pt_bl_from_file, 0644, + pt_bl_from_file_show, pt_pip2_bl_from_file_store); + +/******************************************************************************* + * FUNCTION: pt_pip2_manual_upgrade_store + * + * SUMMARY: Store method for the pip2_manual_upgrade sysfs node. Allows + * sysfs control of bootloading a new FW image to FLASH. + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to output buffer + * size - size of data in buffer + ******************************************************************************/ +static ssize_t pt_pip2_manual_upgrade_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct pt_loader_data *ld = pt_get_loader_data(dev); + u32 input_data[2] = {0}; + int length; + int rc = 0; + + length = cmd->parse_sysfs_input(dev, buf, size, input_data, + ARRAY_SIZE(input_data)); + + if (length != 1) { + pt_debug(dev, DL_WARN, "%s: Invalid number of arguments\n", + __func__); + rc = -EINVAL; + goto exit; + } + + if (input_data[0] < 0 || input_data[0] > 1) { + pt_debug(dev, DL_WARN, "%s: Invalid arguments\n", __func__); + rc = -EINVAL; + goto exit; + } + + _pt_pip2_update_bl_status(dev, UPDATE_FW_IDLE, PT_NO_INC); + if (ld->is_manual_upgrade_enabled) { + pt_debug(dev, DL_ERROR, + "%s: ERROR - Manual upgrade busy\n", __func__); + rc = -EBUSY; + goto exit; + } + ld->pip2_load_file_no = PIP2_FW_FILE; + pt_debug(dev, DL_DEBUG, "%s: ATM - File number is %d\n", + __func__, ld->pip2_load_file_no); + + ld->is_manual_upgrade_enabled = 1; + rc = pt_pip2_create_fw_class(ld->pip2_data); + ld->is_manual_upgrade_enabled = 0; + if (rc < 0) + pt_debug(dev, DL_ERROR, + "%s: ERROR - FLASH Upgrade failed\n", __func__); + +exit: + if (rc) + return rc; + return size; +} +static DEVICE_ATTR(pip2_manual_upgrade, 0200, + NULL, pt_pip2_manual_upgrade_store); + +/******************************************************************************* + * FUNCTION: pt_pip2_manual_ram_upgrade_store + * + * SUMMARY: Store method for the pip2_manual_ram_upgrade sysfs node. Allows + * sysfs control of bootloading a new FW image to SRAM. + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to output buffer + * size - size of data in buffer + ******************************************************************************/ +static ssize_t pt_pip2_manual_ram_upgrade_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct pt_loader_data *ld = pt_get_loader_data(dev); + u32 input_data[2] = {0}; + int length; + int rc = 0; + + length = cmd->parse_sysfs_input(dev, buf, size, input_data, + ARRAY_SIZE(input_data)); + + if (length != 1) { + pt_debug(dev, DL_WARN, "%s: Invalid number of arguments\n", + __func__); + rc = -EINVAL; + goto exit; + } + + if (input_data[0] < 0 || input_data[0] > 1) { + pt_debug(dev, DL_WARN, "%s: Invalid arguments\n", __func__); + rc = -EINVAL; + goto exit; + } + + _pt_pip2_update_bl_status(dev, UPDATE_FW_IDLE, PT_NO_INC); + if (ld->is_manual_upgrade_enabled) { + pt_debug(dev, DL_ERROR, + "%s: ERROR - Manual upgrade busy\n", __func__); + rc = -EBUSY; + goto exit; + } + + ld->pip2_load_file_no = PIP2_RAM_FILE; + pt_debug(dev, DL_DEBUG, "%s: ATM - File number is %d\n", + __func__, ld->pip2_load_file_no); + + ld->is_manual_upgrade_enabled = 1; + rc = pt_pip2_create_fw_class(ld->pip2_data); + ld->is_manual_upgrade_enabled = 0; + if (rc < 0) + pt_debug(dev, DL_ERROR, + "%s: ERROR - RAM Upgrade failed\n", __func__); + +exit: + if (rc) + return rc; + return size; +} +static DEVICE_ATTR(pip2_manual_ram_upgrade, 0200, + NULL, pt_pip2_manual_ram_upgrade_store); + +/******************************************************************************* + * FUNCTION: pt_pip2_file_write_store + * + * SUMMARY: Store method for the pip2_file_write sysfs node. Allows + * sysfs control to "load" data into any file. + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to output buffer + * size - size of data in buffer + ******************************************************************************/ +static ssize_t pt_pip2_file_write_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct pt_loader_data *ld = pt_get_loader_data(dev); + int rc; + u32 input_data[3]; + int length; + + if (ld->is_manual_upgrade_enabled) { + pt_debug(dev, DL_ERROR, + "%s: ERROR - Manual upgrade busy\n", __func__); + rc = -EBUSY; + goto exit; + } + + length = cmd->parse_sysfs_input(dev, buf, size, input_data, + ARRAY_SIZE(input_data)); + if (length <= 0 || length > 2) { + pt_debug(dev, DL_ERROR, "%s: Invalid number of arguments\n", + __func__); + ld->pip2_file_data.para_num = 0; + rc = -EINVAL; + goto exit; + } + + if (input_data[0] < PIP2_FW_FILE || input_data[0] > PIP2_FILE_MAX) { + pt_debug(dev, DL_ERROR, "%s: Invalid file handle\n", __func__); + ld->pip2_file_data.para_num = 0; + rc = -EINVAL; + goto exit; + } + + ld->pip2_load_file_no = input_data[0]; + if (length == 2) + ld->pip2_file_data.file_offset = input_data[1]; + + ld->is_manual_upgrade_enabled = 1; + rc = pt_pip2_create_fw_class(ld->pip2_data); + ld->is_manual_upgrade_enabled = 0; + if (rc < 0) + pt_debug(dev, DL_ERROR, + "%s: ERROR - RAM Upgrade failed\n", __func__); +exit: + if (rc) + return rc; + return size; +} +static DEVICE_ATTR(pip2_file_write, 0200, NULL, pt_pip2_file_write_store); + +/******************************************************************************* + * FUNCTION: pt_update_fw_store + * + * SUMMARY: Store method for the update_fw sysfs node. This node is required + * by ChromeOS to first determine if loading is available and then perform + * the loading if required. This function is simply a wrapper to call: + * pt_pip2_manual_upgrade_store - for the TC3XXX or TT7XXX parts + * pt_manual_upgrade_store - for the legacy Gen5/6 devices. + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to output buffer + * size - size of data in buffer + ******************************************************************************/ +static ssize_t pt_update_fw_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + u8 dut_gen = cmd->request_dut_generation(dev); + + if (dut_gen == DUT_PIP2_CAPABLE) + size = pt_pip2_manual_upgrade_store(dev, attr, buf, size); + else if (dut_gen == DUT_PIP1_ONLY) + size = pt_manual_upgrade_store(dev, attr, buf, size); + + return size; +} +static DEVICE_ATTR(update_fw, 0200, NULL, pt_update_fw_store); +#endif /* CONFIG_TOUCHSCREEN_PARADE_BINARY_FW_UPGRADE */ + +#ifdef TTDL_DIAGNOSTICS +/******************************************************************************* + * FUNCTION: pt_pip2_file_read_show + * + * SUMMARY: The read method for the pip2_file_read sysfs node. Allows to + * perform flash read action according to stored value. This function will + * re-enter always until it returns: + * 0: No data to be written to sysfs node + * <0: Error happens + * (For kernel version large than 3.11(not tested), if the function returns + * non-zero value in previous chunk, to return 0 once can not stop re-enter. + * It needs one more time to stop read action by returned value <= 0. But for + * older version, read action will stop when returned value <= 0 once). + * + * NOTE: Up to PIP2_FILE_WRITE_LEN_PER_PACKET(245) bytes of data are read for + * each enter. When last package is read, pip2_file_data.para_num is assigned + * as negatie value(-1), then next enter can return 0 to indicate the read + * method can be finished. + * + * RETURN: Size of data written to sysfs node + * + * PARAMETERS: + * *filp - pointer to file structure + * *kobj - pointer to kobject structure + * *bin_attr - pointer to bin_attribute structure + * buf - pointer to cmd input buffer + * offset - offset index to store input buffer + * count - size of data in buffer + ******************************************************************************/ +static ssize_t pt_pip2_file_read_show(struct file *filp, + struct kobject *kobj, struct bin_attribute *bin_attr, + char *buf, loff_t offset, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct pt_loader_data *ld = pt_get_loader_data(dev); + struct pt_core_data *cd = dev_get_drvdata(dev); + int rc = 0; + u8 file_handle = ld->pip2_file_data.file_handle; + u8 *pr_buf = ld->pip2_file_data.file_print_buf; + int print_idx = 0, read_size = 0, i; + u8 read_buf[PT_MAX_PIP2_MSG_SIZE]; + u8 read_len; + u32 address, file_size; + + if (ld->pip2_file_data.file_print_left) { + pt_debug(dev, DL_INFO, "%s: print left=%d, count=%zu\n", + __func__, ld->pip2_file_data.file_print_left, count); + + print_idx = ld->pip2_file_data.file_print_left; + if (count < print_idx) { + memcpy(buf, pr_buf, count); + ld->pip2_file_data.file_print_left = print_idx - count; + for (i = 0; i < ld->pip2_file_data.file_print_left; i++) + pr_buf[i] = pr_buf[i+count]; + print_idx = count; + } else { + memcpy(buf, pr_buf, print_idx); + ld->pip2_file_data.file_print_left = 0; + } + return print_idx; + } + + if (ld->pip2_file_data.para_num == 0) { + /* + * When offset != 0, it means the extra call for splice out, + * don't need a warning. + */ + if (offset != 0) + return 0; + + print_idx += scnprintf(buf, count, "Status: %d\n" + "No input!\n", -EINVAL); + pt_debug(dev, DL_ERROR, "%s: Invalid para_num = %d!\n", + __func__, ld->pip2_file_data.para_num); + return print_idx; + } else if (ld->pip2_file_data.para_num == -1) { + ld->pip2_file_data.para_num = 0; + pt_debug(dev, DL_INFO, "%s: flash read finish!\n", + __func__); + rc = 0; + goto exit_release; + } else if (ld->pip2_file_data.para_num < -1) { + ld->pip2_file_data.para_num = 0; + pt_debug(dev, DL_ERROR, "%s: Exit directly due to errors!\n", + __func__); + return 0; + } else if (ld->pip2_file_data.para_num > 3) { + ld->pip2_file_data.para_num = 0; + pt_debug(dev, DL_ERROR, + "%s: Exit directly due to invalid parameter!\n", + __func__); + return 0; + } + + if (offset == 0) { + ld->pip2_file_data.file_print_buf = kzalloc(PIPE_BUF, + GFP_KERNEL); + if (!ld->pip2_file_data.file_print_buf) { + rc = -ENOMEM; + goto exit; + } + /* This functionality is only available in the BL */ + if (cd->mode != PT_MODE_BOOTLOADER) { + rc = -EPERM; + goto exit_free; + } + + pr_buf = ld->pip2_file_data.file_print_buf; + + rc = cmd->request_exclusive(dev, + PT_LDR_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Failed to request exclusive rc=%d\n", + __func__, rc); + goto exit_free; + } + + rc = cmd->nonhid_cmd->pip2_file_open(dev, file_handle); + if (rc < 0) { + pt_debug(dev, DL_ERROR, + "%s: Failed to file_open rc=%d\n", + __func__, rc); + goto exit_release; + } + + rc = cmd->nonhid_cmd->pip2_file_get_stats(dev, file_handle, + &address, &file_size); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Failed to get_file_state rc=%d\n", + __func__, rc); + goto exit_file_close; + } + + ld->pip2_file_data.file_print_size = 0; + ld->pip2_file_data.file_max_size = file_size; + ld->pip2_file_data.file_print_left = 0; + print_idx += scnprintf(pr_buf, PIPE_BUF, "ROM_DATA:"); + + if (ld->pip2_file_data.para_num == 1) + ld->pip2_file_data.file_read_size = file_size; + else if (ld->pip2_file_data.para_num == 2) { + if (ld->pip2_file_data.file_offset < file_size) + ld->pip2_file_data.file_read_size = file_size - + ld->pip2_file_data.file_offset; + else { + rc = -EINVAL; + pt_debug(dev, DL_ERROR, + "%s: File read out of bounds rc=%d\n", + __func__, rc); + goto exit_file_close; + } + } else if (ld->pip2_file_data.para_num == 3) { + if ((ld->pip2_file_data.file_read_size + + ld->pip2_file_data.file_offset) > file_size) { + rc = -EINVAL; + pt_debug(dev, DL_ERROR, + "%s: File read out of bounds rc=%d\n", + __func__, rc); + goto exit_file_close; + } + } else { + pt_debug(dev, DL_ERROR, + "%s: Invalid number of parameters!\n", + __func__); + goto exit_file_close; + } + } + + offset = ld->pip2_file_data.file_print_size + + ld->pip2_file_data.file_offset; + if ((offset >= ld->pip2_file_data.file_max_size) || + (ld->pip2_file_data.file_print_size >= + ld->pip2_file_data.file_read_size)) + goto exit_file_close; + else if ((ld->pip2_file_data.file_print_size + + PIP2_FILE_WRITE_LEN_PER_PACKET) >= + ld->pip2_file_data.file_read_size) + read_len = ld->pip2_file_data.file_read_size - + ld->pip2_file_data.file_print_size; + else + read_len = PIP2_FILE_WRITE_LEN_PER_PACKET; + + rc = cmd->nonhid_cmd->pip2_file_seek_offset(dev, + file_handle, offset, 0); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Failed to seek file offset rc=%d\n", + __func__, rc); + goto exit_file_close; + } + + read_size = cmd->nonhid_cmd->pip2_file_read(dev, + file_handle, read_len, read_buf); + if (read_size < 0) { + pt_debug(dev, DL_ERROR, "%s: Failed to read file rc=%d\n", + __func__, read_size); + goto exit_file_close; + } + + for (i = 0; i < read_size; i++) + print_idx += scnprintf(pr_buf + print_idx, + PIPE_BUF - print_idx, + "%02X ", read_buf[i + PIP2_RESP_BODY_OFFSET]); + ld->pip2_file_data.file_print_size += read_size; + if (count < print_idx) { + memcpy(buf, pr_buf, count); + ld->pip2_file_data.file_print_left = print_idx - count; + for (i = 0; i < ld->pip2_file_data.file_print_left; i++) + pr_buf[i] = pr_buf[i+count]; + print_idx = count; + } else { + memcpy(buf, pr_buf, print_idx); + } + goto exit_for_next_read; + +exit_file_close: + if (rc) + print_idx += scnprintf(pr_buf + print_idx, + PIPE_BUF - print_idx, "(READ ERROR)\n"); + else if (ld->pip2_file_data.file_print_size) + print_idx += scnprintf(pr_buf + print_idx, + PIPE_BUF - print_idx, + ":(%d bytes)\n", + ld->pip2_file_data.file_print_size); + else + print_idx += scnprintf(pr_buf + print_idx, + PIPE_BUF - print_idx, "No Data\n"); + if (count < print_idx) { + memcpy(buf, pr_buf, count); + ld->pip2_file_data.file_print_left = print_idx - count; + for (i = 0; i < ld->pip2_file_data.file_print_left; i++) + pr_buf[i] = pr_buf[i+count]; + print_idx = count; + } else { + memcpy(buf, pr_buf, print_idx); + } + rc = cmd->nonhid_cmd->pip2_file_close(dev, file_handle); + if (file_handle != rc) + pt_debug(dev, DL_ERROR, + "%s Failed to close file %d, rc = %d\n", __func__, + file_handle, rc); + /* + * Mark para_num as negative value to finish read method during + * next enter. + */ + ld->pip2_file_data.para_num = -1; + +exit_for_next_read: + pt_debug(dev, DL_INFO, + "%s: %s=%d, %s=%d, %s=%d, %s=%d, %s=%d\n", + __func__, + "para_num", ld->pip2_file_data.para_num, + "handle", ld->pip2_file_data.file_handle, + "offset", ld->pip2_file_data.file_offset, + "read", ld->pip2_file_data.file_read_size, + "print", ld->pip2_file_data.file_print_size); + return print_idx; +exit_release: + cmd->release_exclusive(dev); +exit_free: + kfree(ld->pip2_file_data.file_print_buf); +exit: + if (rc) { + ld->pip2_file_data.para_num = -2; + print_idx = scnprintf(buf, count, "Status: %d\n", rc); + return print_idx; + } else + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_pip2_file_read_store + * + * SUMMARY: The write method for the pip2_file_read node. The passed + * in data is needed by read method. + * + * RETURN: Size of passed in buffer is success + * + * PARAMETERS: + * *filp - pointer to file structure + * *kobj - pointer to kobject structure + * *bin_attr - pointer to bin_attribute structure + * buf - pointer to cmd input buffer + * offset - offset index to store input buffer + * count - size of data in buffer + ******************************************************************************/ +static ssize_t pt_pip2_file_read_store(struct file *filp, + struct kobject *kobj, struct bin_attribute *bin_attr, + char *buf, loff_t offset, size_t count) +{ + int rc = 0; + int length; + struct device *dev = container_of(kobj, struct device, kobj); + struct pt_loader_data *ld = pt_get_loader_data(dev); + int ic_buffer[4] = {0}; + + length = cmd->parse_sysfs_input(dev, buf, count, ic_buffer, + ARRAY_SIZE(ic_buffer)); + if (length <= 0 || length > 3) { + pt_debug(dev, DL_ERROR, "%s: Input format error!\n", + __func__); + ld->pip2_file_data.para_num = 0; + rc = -EINVAL; + goto error; + } + + if (ic_buffer[0] < PIP2_FW_FILE || ic_buffer[0] > PIP2_FILE_7) { + pt_debug(dev, DL_ERROR, "%s: Invalid file handle!\n", + __func__); + ld->pip2_file_data.para_num = 0; + rc = -EINVAL; + goto error; + } + + switch (length) { + case 1: + ld->pip2_file_data.file_handle = ic_buffer[0]; + ld->pip2_file_data.file_offset = 0; + ld->pip2_file_data.file_read_size = 0; + break; + case 2: + ld->pip2_file_data.file_handle = ic_buffer[0]; + ld->pip2_file_data.file_offset = ic_buffer[1]; + ld->pip2_file_data.file_read_size = 0; + break; + case 3: + ld->pip2_file_data.file_handle = ic_buffer[0]; + ld->pip2_file_data.file_offset = ic_buffer[1]; + ld->pip2_file_data.file_read_size = ic_buffer[2]; + break; + default: + break; + } + + ld->pip2_file_data.para_num = length; +error: + pt_debug(dev, DL_INFO, + "%s: %s=%d, %s=%d, %s=%d, %s=%d, %s=%d\n", + __func__, + "para_num", ld->pip2_file_data.para_num, + "handle", ld->pip2_file_data.file_handle, + "offset", ld->pip2_file_data.file_offset, + "read", ld->pip2_file_data.file_read_size, + "print", ld->pip2_file_data.file_print_size); + + if (rc) + return rc; + return count; +} + +static struct bin_attribute bin_attr_pip2_file_read = { + .attr = { + .name = "pip2_file_read", + .mode = (0644), + }, + .read = pt_pip2_file_read_show, + .write = pt_pip2_file_read_store, +}; + +/****************************************************************************** + * FUNCTION: pt_pip2_file_crc_show + * + * SUMMARY: The show method for the "pip2_file_crc" sysfs node. + * Shows the CRC of a file or portion of the file. + * + * NOTE: If function is called when DUT is already in BL mode, the DUT + * will remain in BL mode when function exits, however if DUT is in + * normal mode when function is called, the DUT will be forced into BL + * mode and then returned to normal mode when function exits. + * + * NOTE: This sysfs node only can be used for BL version 1.8 or greater. + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes structure + * *buf - pointer to print output buffer + ******************************************************************************/ +static ssize_t pt_pip2_file_crc_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pt_loader_data *ld = pt_get_loader_data(dev); + struct pt_core_data *cd = dev_get_drvdata(dev); + int rc = 0; + u8 file_handle; + int print_idx = 0; + u8 read_buf[PT_MAX_PIP2_MSG_SIZE]; + u32 address, file_size; + u32 length; + u32 offset; + u16 file_crc; + u16 status; + + if (ld->pip2_fcrc.para_num == 0 || + ld->pip2_fcrc.file_handle == 0 || + ld->pip2_fcrc.file_read_size < 0 || + ld->pip2_fcrc.file_offset < 0) { + pt_debug(dev, DL_ERROR, + "%s: Invalid parameters!\n", + __func__); + print_idx = scnprintf(buf, PT_MAX_PRBUF_SIZE, + "Status: %d\n" + "Invalid parameters!\n", -EINVAL); + return print_idx; + } + + file_handle = ld->pip2_fcrc.file_handle; + offset = ld->pip2_fcrc.file_offset; + length = ld->pip2_fcrc.file_read_size; + + /* This functionality is only available in the BL */ + if (cd->mode != PT_MODE_BOOTLOADER) { + rc = -EPERM; + print_idx = scnprintf(buf, PT_MAX_PRBUF_SIZE, + "Status: %d\n", rc); + goto exit; + } + + rc = cmd->request_exclusive(dev, PT_LDR_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Failed to request exclusive rc=%d\n", + __func__, rc); + print_idx = scnprintf(buf, PT_MAX_PRBUF_SIZE, + "Status: %d\n", rc); + goto exit; + } + + rc = cmd->nonhid_cmd->pip2_file_open(dev, file_handle); + if (rc < 0) { + pt_debug(dev, DL_ERROR, + "%s: Failed to file_open rc=%d\n", + __func__, rc); + print_idx = scnprintf(buf, PT_MAX_PRBUF_SIZE, + "Status: %d\n", rc); + goto exit_release; + } + + rc = cmd->nonhid_cmd->pip2_file_get_stats(dev, file_handle, + &address, &file_size); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Failed to get_file_state rc=%d\n", + __func__, rc); + print_idx = scnprintf(buf, PT_MAX_PRBUF_SIZE, + "Status: %d\n", rc); + goto exit_file_close; + } + + ld->pip2_fcrc.file_max_size = file_size; + + if (ld->pip2_fcrc.file_read_size > file_size + || ld->pip2_fcrc.file_offset > file_size + || ((ld->pip2_fcrc.file_offset + + ld->pip2_fcrc.file_read_size) > file_size)) { + pt_debug(dev, DL_ERROR, + "%s: Invalid parameters!\n", + __func__); + print_idx = scnprintf(buf, PT_MAX_PRBUF_SIZE, + "Status: %d\n", rc); + goto exit_file_close; + } + + rc = cmd->nonhid_cmd->pip2_file_crc(dev, + file_handle, offset, length, read_buf); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Failed to get file crc, rc=%d\n", + __func__, rc); + print_idx = scnprintf(buf, PT_MAX_PRBUF_SIZE, + "Status: %d\n", rc); + } else { + status = read_buf[PIP2_RESP_STATUS_OFFSET]; + if (status == PIP2_RSP_ERR_NONE) { + file_crc = get_unaligned_le16(&read_buf[5]); + print_idx = scnprintf(buf, PT_MAX_PRBUF_SIZE, + "Status: %d\n" + "FILE CRC: %04X\n", + status, file_crc); + } else + print_idx = scnprintf(buf, PT_MAX_PRBUF_SIZE, + "Status: %d\n" + "FILE CRC: n/a\n", + status); + } + +exit_file_close: + rc = cmd->nonhid_cmd->pip2_file_close(dev, file_handle); + if (file_handle != rc) + pt_debug(dev, DL_ERROR, + "%s Failed to close file %d, rc = %d\n", __func__, + file_handle, rc); + +exit_release: + cmd->release_exclusive(dev); +exit: + ld->pip2_fcrc.para_num = 0; + return print_idx; +} + +/******************************************************************************* + * FUNCTION: pt_pip2_file_crc_store + * + * SUMMARY: The store method for the "pip2_file_crc" sysfs node. The passed in + * data including file_handle, offset and read size are needed by show method. + * + * NOTE: This sysfs node only can be used for BL version 1.8 or greater.. + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes structure + * *buf - pointer to print output buffer + * size - size of buffer + ******************************************************************************/ +static ssize_t pt_pip2_file_crc_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct pt_loader_data *ld = pt_get_loader_data(dev); + u32 input_data[4] = {0}; + int length; + int rc = 0; + + ld->pip2_fcrc.file_handle = PIP2_RAM_FILE; + ld->pip2_fcrc.file_offset = -1; + ld->pip2_fcrc.file_read_size = -1; + + length = cmd->parse_sysfs_input(dev, buf, size, input_data, + ARRAY_SIZE(input_data)); + + if (length != 3) { + pt_debug(dev, DL_WARN, "%s: Invalid number of arguments\n", + __func__); + ld->pip2_fcrc.para_num = 0; + rc = -EINVAL; + goto exit; + } + + if ((input_data[0] < PIP2_FW_FILE) || (input_data[0] > PIP2_FILE_MAX)) { + pt_debug(dev, DL_WARN, "%s: Invalid file_no %d\n", + __func__, input_data[0]); + rc = -EINVAL; + goto exit; + } + + ld->pip2_fcrc.file_handle = input_data[0]; + ld->pip2_fcrc.file_offset = input_data[1]; + ld->pip2_fcrc.file_read_size = input_data[2]; + ld->pip2_fcrc.para_num = 3; + + pt_debug(dev, DL_INFO, + "%s: %s=%d, %s=%d, %s=%d, %s=%d\n", + __func__, + "para_num", ld->pip2_fcrc.para_num, + "handle", ld->pip2_fcrc.file_handle, + "offset", ld->pip2_fcrc.file_offset, + "length", ld->pip2_fcrc.file_read_size); +exit: + if (rc) + return rc; + return size; +} + +static DEVICE_ATTR(pip2_file_crc, 0644, + pt_pip2_file_crc_show, pt_pip2_file_crc_store); +#endif +/******************************************************************************* + * FUNCTION: pt_pip2_file_erase_show + * + * SUMMARY: The show method for the "pip2_file_erase" sysfs node. + * Prints current erase status to output buffer. + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes structure + * *buf - pointer to print output buffer + ******************************************************************************/ +static ssize_t pt_pip2_file_erase_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pt_loader_data *ld = pt_get_loader_data(dev); + struct pt_core_data *cd = dev_get_drvdata(dev); + u8 file_handle; + u8 file = ld->pip2_file_erase_file_no; + int rc; + + pip2_erase_status = -1; + pip2_erase_rc = 0; + + if (file == PIP2_RAM_FILE) { + rc = -EINVAL; + goto exit; + } + + /* This functionality is only available in the BL */ + if (cd->mode != PT_MODE_BOOTLOADER) { + rc = -EPERM; + goto exit; + } + + rc = cmd->request_exclusive(dev, PT_LDR_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(dev, DL_ERROR, + "%s: Failed to get exclusive access rc=%d\n", + __func__, rc); + goto exit; + } + + file_handle = cmd->nonhid_cmd->pip2_file_open(dev, file); + if (file_handle != file) { + rc = -EBADF; + goto exit_release; + } + + file_handle = cmd->nonhid_cmd->pip2_file_erase(dev, file, + &pip2_erase_status); + if (file_handle < 0) { + rc = file_handle; + pt_debug(dev, DL_INFO, "%s: File erase error rc = %d\n", + __func__, rc); + } else if (file_handle == file) { + pt_debug(dev, DL_INFO, "%s: File %d erased\n", + __func__, file_handle); + } else { + rc = -EBADF; + } + + file_handle = cmd->nonhid_cmd->pip2_file_close(dev, file); + if (file_handle != file && !rc) + rc = -EBADF; + +exit_release: + cmd->release_exclusive(dev); +exit: + pip2_erase_rc = rc; + if (pip2_erase_status == -1) { + return scnprintf(buf, PT_MAX_PRBUF_SIZE, + "Status: %d\n" + "Erase Status: n/a\n", + pip2_erase_rc); + } + return scnprintf(buf, PT_MAX_PRBUF_SIZE, + "Status: %d\n" + "Erase Status: 0x%02X\n", + pip2_erase_rc, pip2_erase_status); +} + +/******************************************************************************* + * FUNCTION: pt_pip2_file_erase_store + * + * SUMMARY: The store method for the "pip2_file_erase" sysfs node. Allows the + * caller to provide the file number to erase in FLASH. + * + * NOTE: The DUT must be in BL mode before calling this function. + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes structure + * *buf - pointer to print output buffer + * size - size of buffer + ******************************************************************************/ +static ssize_t pt_pip2_file_erase_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct pt_loader_data *ld = pt_get_loader_data(dev); + u32 input_data[2] = {0}; + int length; + int rc = 0; + + length = cmd->parse_sysfs_input(dev, buf, size, input_data, + ARRAY_SIZE(input_data)); + + if (length != 1) { + pt_debug(dev, DL_WARN, "%s: Invalid number of arguments\n", + __func__); + rc = -EINVAL; + goto exit; + } + + /* Only allow valid files to be erased */ + if (input_data[0] < PIP2_FW_FILE || input_data[0] > PIP2_FILE_MAX) { + pip2_erase_status = PIP2_RSP_ERR_BAD_FILE; + pt_debug(dev, DL_ERROR, "%s: ERROR - Invalid File\n", + __func__); + rc = -EINVAL; + goto exit; + } + + ld->pip2_file_erase_file_no = input_data[0]; + +exit: + if (rc) + return rc; + return size; +} + +static DEVICE_ATTR(pip2_file_erase, 0644, + pt_pip2_file_erase_show, pt_pip2_file_erase_store); + +/******************************************************************************* + * FUNCTION: pt_pip2_bl_status_show + * + * SUMMARY: The show method for the pip2_bl_status sysfs node. + * Shows the percent completion of the current BL or an error message. + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes structure + * *buf - pointer to print output buffer + ******************************************************************************/ +static ssize_t pt_pip2_bl_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t ret; + u8 status = update_fw_status; + + if (update_fw_status <= UPDATE_FW_COMPLETE) { + pt_debug(dev, DL_DEBUG, + "%s BL_STATUS = %d\n", __func__, update_fw_status); + return scnprintf(buf, strlen(buf), "%d\n", update_fw_status); + } + + switch (status) { + case UPDATE_FW_GENERAL_ERROR: + ret = scnprintf(buf, strlen(buf), + "ERROR: %d - General programming failure\n", status); + break; + case UPDATE_FW_PIP_VERSION_ERROR: + ret = scnprintf(buf, strlen(buf), + "ERROR: %d - Wrong PIP version detected\n", status); + break; + case UPDATE_FW_VERSION_ERROR: + ret = scnprintf(buf, strlen(buf), + "ERROR: %d - FW vervion newer than bin file\n", status); + break; + case UPDATE_FW_ERASE_ERROR: + ret = scnprintf(buf, strlen(buf), + "ERROR: %d - ROM BL failed to erase FW file in FLASH\n", + status); + break; + case UPDATE_FW_FILE_CLOSE_ERROR: + ret = scnprintf(buf, strlen(buf), + "ERROR: %d - ROM BL failed to close FW file in FLASH\n", + status); + break; + case UPDATE_FW_WRITE_ERROR: + ret = scnprintf(buf, strlen(buf), + "ERROR: %d - ROM BL file write failure\n", status); + break; + case UPDATE_FW_EXECUTE_ERROR: + ret = scnprintf(buf, strlen(buf), + "ERROR: %d - ROM BL failed to execute RAM image\n", + status); + break; + case UPDATE_FW_RESET_ERROR: + ret = scnprintf(buf, strlen(buf), + "ERROR: %d - Reset DUT failure\n", + status); + break; + case UPDATE_FW_MODE_ERROR: + ret = scnprintf(buf, strlen(buf), + "ERROR: %d - Program complete, Incorrect BL/APP mode detected after reset sentinel\n", + status); + break; + case UPDATE_FW_ENTER_BL_ERROR: + ret = scnprintf(buf, strlen(buf), + "ERROR: %d - Could not enter the BL\n", status); + break; + case UPDATE_FW_FILE_OPEN_ERROR: + ret = scnprintf(buf, strlen(buf), + "ERROR: %d - ROM BL failed to open FW file in FLASH\n", + status); + break; + case UPDATE_FW_SENTINEL_NOT_SEEN: + ret = scnprintf(buf, strlen(buf), + "ERROR: %d - FW Reset Sentinel not seen after XRES\n", + status); + break; + case UPDATE_FW_EXCLUSIVE_ACCESS_ERROR: + ret = scnprintf(buf, strlen(buf), + "ERROR: %d - Failed to get DUT exclusive access\n", + status); + break; + case UPDATE_FW_NO_FW_PROVIDED: + ret = scnprintf(buf, strlen(buf), + "ERROR: %d - No FW provided to load\n", status); + break; + case UPDATE_FW_INVALID_FW_IMAGE: + ret = scnprintf(buf, strlen(buf), "ERROR: %d - Invalid FW image\n", status); + break; + case UPDATE_FW_MISALIGN_FW_IMAGE: + ret = scnprintf(buf, strlen(buf), + "ERROR: %d - FW image is misaligned\n", status); + break; + case UPDATE_FW_SYSTEM_NOMEM: + ret = scnprintf(buf, strlen(buf), + "ERROR: %d - Failed to alloc memory\n", status); + break; + case UPDATE_FW_INIT_BL_ERROR: + ret = scnprintf(buf, strlen(buf), + "ERROR: %d - Failed to init bootloader\n", status); + break; + case UPDATE_FW_PARSE_ROW_ERROR: + ret = scnprintf(buf, strlen(buf), + "ERROR: %d - Failed to parse row of FW image\n", + status); + break; + case UPDATE_FW_PROGRAM_ROW_ERROR: + ret = scnprintf(buf, strlen(buf), + "ERROR: %d - Failed to progrem FW image\n", status); + break; + case UPDATE_FW_EXIT_BL_ERROR: + ret = scnprintf(buf, strlen(buf), + "ERROR: %d - Failed to exit bootloader\n", status); + break; + case UPDATE_FW_CHECK_SUM_ERROR: + ret = scnprintf(buf, strlen(buf), + "ERROR: %d - Failed to verify app checksum\n", status); + break; + case UPDATE_FW_NO_PLATFORM_DATA: + ret = scnprintf(buf, strlen(buf), + "ERROR: %d - No platform data\n", status); + break; + case UPDATE_FW_UNDEFINED_ERROR: + default: + ret = scnprintf(buf, strlen(buf), "ERROR: %d - Unknown error\n", status); + break; + } + return ret; +} +static DEVICE_ATTR(pip2_bl_status, 0444, pt_pip2_bl_status_show, NULL); +#if PT_FW_UPGRADE +static DEVICE_ATTR(update_fw_status, 0444, pt_pip2_bl_status_show, NULL); +#endif +/******************************************************************************* + * FUNCTION: pt_pip2_get_last_error_show + * + * SUMMARY: The show method for the pip2_get_last_error_show sysfs node. + * Shows the last BL error code. + * + * NOTE: If function is called when DUT is already in BL mode, the DUT + * will remain in BL mode when function exits, however if DUT is in + * normal mode when function is called, the DUT will be forced into BL + * mode and then returned to normal mode when function exits. + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes structure + * *buf - pointer to print output buffer + ******************************************************************************/ +static ssize_t pt_pip2_get_last_error_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + int rc; + u8 read_buf[256]; + + rc = cmd->request_exclusive(dev, PT_LDR_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc) + goto exit; + + cmd->request_stop_wd(dev); + + /* This functionality is only available in the BL */ + if (cd->mode != PT_MODE_BOOTLOADER) { + rc = -EPERM; + goto exit_release; + } + + /* Get and log the last error(s) */ + rc = _pt_pip2_log_last_error(dev, read_buf); + +exit_release: + cmd->release_exclusive(dev); +exit: + if (rc) + return scnprintf(buf, PT_MAX_PRBUF_SIZE, "Status: %d\n", rc); + + if (read_buf[PIP2_RESP_STATUS_OFFSET] == PIP2_RSP_ERR_NONE) { + return scnprintf(buf, PT_MAX_PRBUF_SIZE, + "Status: %d\n" + "Last Error No: 0x%02X\n", + PIP2_RSP_ERR_NONE, + read_buf[PIP2_RESP_BODY_OFFSET]); + } else { + return scnprintf(buf, PT_MAX_PRBUF_SIZE, + "Status: %d\n" + "Last Error No: n/a\n", + read_buf[PIP2_RESP_STATUS_OFFSET]); + } +} +static DEVICE_ATTR(pip2_get_last_error, 0444, + pt_pip2_get_last_error_show, NULL); + +#if PT_FW_UPGRADE +/******************************************************************************* + * FUNCTION: pt_loader_attention + * + * SUMMARY: Function to be registered to TTDL attention list to set up + * int_running semaphore. + * + * RETURN: + * 0 = success + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int pt_loader_attention(struct device *dev) +{ + struct pt_loader_data *ld = pt_get_loader_data(dev); + + complete(&ld->int_running); + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_fw_upgrade_cb + * + * SUMMARY: Function to be registered to TTDL attention list to allow upgrade + * if host cannot get response from firmware with ping command. + * RETURN: + * 0 = success + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int pt_fw_upgrade_cb(struct device *dev) +{ + u8 dut_gen = cmd->request_dut_generation(dev); + +#ifdef CONFIG_TOUCHSCREEN_PARADE_PLATFORM_FW_UPGRADE + if (dut_gen == DUT_PIP1_ONLY) { + pt_debug(dev, DL_WARN, "%s: Upgrade Platform FW", __func__); + if (!upgrade_firmware_from_platform(dev, false)) + return 0; + } +#endif + +#ifdef CONFIG_TOUCHSCREEN_PARADE_BINARY_FW_UPGRADE + pt_debug(dev, DL_WARN, "%s: Upgrade Builtin FW", __func__); + if (dut_gen == DUT_PIP2_CAPABLE) { + if (!pt_pip2_upgrade_firmware_from_builtin(dev)) + return 0; + pt_debug(dev, DL_WARN, "%s: Builtin FW upgrade failed", + __func__); + } else { + if (!upgrade_firmware_from_builtin(dev)) + return 0; + } +#endif + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_cancel_fw_upgrade_cb + * + * SUMMARY: Function to be registered to TTDL attention list to allow an upgrade + * to be canceled. + * RETURN: + * 0 = success + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int pt_cancel_fw_upgrade_cb(struct device *dev) +{ + struct pt_loader_data *ld = pt_get_loader_data(dev); + + pt_debug(dev, DL_WARN, + "%s: CANCELLING All Loader work\n", __func__); + cancel_work_sync(&ld->bl_from_file); + cancel_work_sync(&ld->pip2_bl_from_file); + cancel_work_sync(&ld->calibration_work); + cancel_work_sync(&ld->fw_and_config_upgrade); + + return 0; +} +#endif /* PT_FW_UPGRADE */ + +/******************************************************************************* + * FUNCTION: pt_loader_probe + * + * SUMMARY: The probe function for the FW loader. + * + * PARAMETERS: + * *dev - pointer to device structure + * **data - double pointer to the loader data to be created here + ******************************************************************************/ +static int pt_loader_probe(struct device *dev, void **data) +{ + struct pt_loader_data *ld; + struct pip2_loader_data *pip2_data; + struct pt_platform_data *pdata = dev_get_platdata(dev); + int rc; + u8 dut_gen = cmd->request_dut_generation(dev); + +#ifdef TTDL_DIAGNOSTICS + pt_debug(dev, DL_INFO, + "%s: entering %s\n", __func__, __func__); +#endif /* TTDL_DIAGNOSTICS */ + + ld = kzalloc(sizeof(*ld), GFP_KERNEL); + if (!ld) { + rc = -ENOMEM; + goto error_alloc_data_failed; + } + +#if PT_FW_UPGRADE + /* Initialize boot loader status */ + if (update_fw_status != UPDATE_FW_COMPLETE) + _pt_pip2_update_bl_status(dev, UPDATE_FW_IDLE, PT_NO_INC); +#endif + + if (dut_gen == DUT_PIP2_CAPABLE) { + pip2_data = kzalloc(sizeof(*pip2_data), GFP_KERNEL); + if (!pip2_data) { + rc = -ENOMEM; + goto error_alloc_data_failed; + } + pip2_data->dev = dev; + ld->pip2_data = pip2_data; + +#if PT_FW_UPGRADE + rc = device_create_file(dev, &dev_attr_update_fw_status); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Error creating update_fw_status\n", + __func__); + goto remove_files; + } +#endif + +#ifdef CONFIG_TOUCHSCREEN_PARADE_BINARY_FW_UPGRADE + rc = device_create_file(dev, &dev_attr_pt_bl_from_file); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Error creating pt_bl_from_file\n", + __func__); + goto remove_files; + } + rc = device_create_file(dev, &dev_attr_update_fw); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Error creating update_fw\n", + __func__); + goto remove_files; + } +#endif /* CONFIG_TOUCHSCREEN_PARADE_BINARY_FW_UPGRADE */ + +#ifdef CONFIG_TOUCHSCREEN_PARADE_BINARY_FW_UPGRADE + rc = device_create_file(dev, &dev_attr_pip2_manual_upgrade); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Error creating pip2_manual_upgrade\n", + __func__); + goto remove_files; + } + rc = device_create_file(dev, &dev_attr_pip2_manual_ram_upgrade); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Error creating pip2_manual_ram_upgrade\n", + __func__); + goto remove_files; + } + rc = device_create_file(dev, &dev_attr_pip2_file_write); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Error creating pip2_file_write\n", + __func__); + goto remove_files; + } + rc = device_create_file(dev, &dev_attr_pip2_bl_from_file); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Error creating pip2_bl_from_file\n", + __func__); + goto remove_files; + } + +#endif /* CONFIG_TOUCHSCREEN_PARADE_BINARY_FW_UPGRADE */ + rc = device_create_file(dev, &dev_attr_pip2_file_erase); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Error creating pip2_file_erase\n", + __func__); + goto remove_files; + } + rc = device_create_file(dev, &dev_attr_pip2_bl_status); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Error creating pip2_bl_status\n", + __func__); + goto remove_files; + } + +#ifdef TTDL_DIAGNOSTICS + rc = device_create_file(dev, &dev_attr_pip2_get_last_error); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Error creating pip2_get_last_error\n", + __func__); + goto remove_files; + } + rc = device_create_bin_file(dev, &bin_attr_pip2_file_read); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Error creating bin_attr_pip2_file_read\n", + __func__); + goto remove_files; + } + rc = device_create_file(dev, &dev_attr_pip2_file_crc); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Error creating pip2_file_crc\n", + __func__); + goto remove_files; + } +#endif + } else { +#if PT_FW_UPGRADE + rc = device_create_file(dev, &dev_attr_update_fw_status); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Error creating update_fw_status\n", + __func__); + goto remove_files; + } +#endif + +#ifdef CONFIG_TOUCHSCREEN_PARADE_BINARY_FW_UPGRADE + rc = device_create_file(dev, &dev_attr_pt_bl_from_file); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Error creating pt_bl_from_file\n", + __func__); + goto remove_files; + } + rc = device_create_file(dev, &dev_attr_update_fw); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Error creating update_fw\n", + __func__); + goto remove_files; + } +#endif /* CONFIG_TOUCHSCREEN_PARADE_BINARY_FW_UPGRADE */ +#ifdef CONFIG_TOUCHSCREEN_PARADE_PLATFORM_FW_UPGRADE + rc = device_create_file(dev, &dev_attr_forced_upgrade); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Error creating forced_upgrade\n", + __func__); + goto remove_files; + } +#endif /* CONFIG_TOUCHSCREEN_PARADE_PLATFORM_FW_UPGRADE */ +#ifdef CONFIG_TOUCHSCREEN_PARADE_BINARY_FW_UPGRADE + rc = device_create_file(dev, &dev_attr_manual_upgrade); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Error creating manual_upgrade\n", + __func__); + goto remove_files; + } +#endif /* CONFIG_TOUCHSCREEN_PARADE_BINARY_FW_UPGRADE */ +#ifdef CONFIG_TOUCHSCREEN_PARADE_MANUAL_TTCONFIG_UPGRADE + rc = device_create_file(dev, &dev_attr_config_loading); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Error creating config_loading\n", + __func__); + goto remove_files; + } + + rc = device_create_bin_file(dev, &bin_attr_config_data); + if (rc) { + pt_debug(dev, DL_ERROR, + "%s: Error creating config_data\n", + __func__); + goto remove_files; + } +#endif /* CONFIG_TOUCHSCREEN_PARADE_MANUAL_TTCONFIG_UPGRADE */ + } + + if (!pdata || !pdata->loader_pdata) { + pt_debug(dev, DL_ERROR, + "%s: Missing platform data\n", __func__); + rc = -ENODEV; + goto error_no_pdata; + } + + /* Default erase file to an invalid file */ + ld->pip2_file_erase_file_no = PIP2_RAM_FILE; + + ld->loader_pdata = pdata->loader_pdata; + ld->dev = dev; + *data = ld; + +#ifdef CONFIG_TOUCHSCREEN_PARADE_BINARY_FW_UPGRADE + INIT_WORK(&ld->bl_from_file, pt_bl_from_file_work); + INIT_WORK(&ld->pip2_bl_from_file, pt_pip2_bl_from_file_work); +#endif /* CONFIG_TOUCHSCREEN_PARADE_MANUAL_TTCONFIG_UPGRADE */ + +#if PT_FW_UPGRADE + init_completion(&ld->int_running); + + cmd->subscribe_attention(dev, PT_ATTEN_IRQ, PT_LOADER_NAME, + pt_loader_attention, PT_MODE_BOOTLOADER); + + cmd->subscribe_attention(dev, PT_ATTEN_LOADER, PT_LOADER_NAME, + pt_fw_upgrade_cb, PT_MODE_UNKNOWN); + + cmd->subscribe_attention(dev, PT_ATTEN_CANCEL_LOADER, PT_LOADER_NAME, + pt_cancel_fw_upgrade_cb, PT_MODE_UNKNOWN); +#endif +#if PT_FW_UPGRADE || PT_TTCONFIG_UPGRADE + pt_debug(dev, DL_INFO, "%s: INIT_WORK pt_calibrate_idacs\n", + __func__); + init_completion(&ld->calibration_complete); + INIT_WORK(&ld->calibration_work, pt_calibrate_idacs); +#endif + +#ifdef CONFIG_TOUCHSCREEN_PARADE_MANUAL_TTCONFIG_UPGRADE + if (dut_gen == DUT_PIP1_ONLY) + mutex_init(&ld->config_lock); +#endif + + pt_debug(dev, DL_INFO, "%s: Schedule FW upgrade work\n", __func__); + INIT_WORK(&ld->fw_and_config_upgrade, pt_fw_and_config_upgrade); + schedule_work(&ld->fw_and_config_upgrade); + + pt_debug(dev, DL_INFO, "%s: Successful probe %s\n", + __func__, dev_name(dev)); + return 0; + + +remove_files: +#ifdef CONFIG_TOUCHSCREEN_PARADE_MANUAL_TTCONFIG_UPGRADE + device_remove_file(dev, &dev_attr_config_loading); +#endif +#ifdef CONFIG_TOUCHSCREEN_PARADE_BINARY_FW_UPGRADE + device_remove_file(dev, &dev_attr_manual_upgrade); +#endif +#ifdef CONFIG_TOUCHSCREEN_PARADE_PLATFORM_FW_UPGRADE + device_remove_file(dev, &dev_attr_forced_upgrade); +#endif +#ifdef TTDL_DIAGNOSTICS + device_remove_file(dev, &dev_attr_pip2_get_last_error); + device_remove_bin_file(dev, &bin_attr_pip2_file_read); + device_remove_file(dev, &dev_attr_pip2_file_crc); +#endif + device_remove_file(dev, &dev_attr_pip2_bl_status); + device_remove_file(dev, &dev_attr_pip2_file_erase); +#ifdef CONFIG_TOUCHSCREEN_PARADE_BINARY_FW_UPGRADE + device_remove_file(dev, &dev_attr_pip2_bl_from_file); + device_remove_file(dev, &dev_attr_pip2_file_write); + device_remove_file(dev, &dev_attr_pt_bl_from_file); + device_remove_file(dev, &dev_attr_pip2_manual_ram_upgrade); + device_remove_file(dev, &dev_attr_pip2_manual_upgrade); + device_remove_file(dev, &dev_attr_update_fw); + device_remove_file(dev, &dev_attr_update_fw_status); +#endif /* CONFIG_TOUCHSCREEN_PARADE_BINARY_FW_UPGRADE */ + + kfree(ld->pip2_data); + kfree(ld); +error_alloc_data_failed: +error_no_pdata: + pt_debug(dev, DL_ERROR, "%s failed.\n", __func__); + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_loader_release + * + * SUMMARY: Remove function for loader module that does following cleanup: + * - Unsubscibe all registered attention tasks + * - Removes all created sysfs nodes + * - Frees all pointers + * + * PARAMETERS: + * *dev - pointer to device structure + * *data - pointer to the loader data + ******************************************************************************/ +static void pt_loader_release(struct device *dev, void *data) +{ + struct pt_loader_data *ld = (struct pt_loader_data *)data; + u8 dut_gen = cmd->request_dut_generation(dev); + +#if PT_FW_UPGRADE + cmd->unsubscribe_attention(dev, PT_ATTEN_IRQ, PT_LOADER_NAME, + pt_loader_attention, PT_MODE_BOOTLOADER); + + cmd->unsubscribe_attention(dev, PT_ATTEN_LOADER, PT_LOADER_NAME, + pt_fw_upgrade_cb, PT_MODE_UNKNOWN); + + cmd->unsubscribe_attention(dev, PT_ATTEN_CANCEL_LOADER, PT_LOADER_NAME, + pt_cancel_fw_upgrade_cb, PT_MODE_UNKNOWN); +#endif +#if PT_FW_UPGRADE + device_remove_file(dev, &dev_attr_update_fw_status); +#endif + if (dut_gen == DUT_PIP2_CAPABLE) { +#ifdef CONFIG_TOUCHSCREEN_PARADE_BINARY_FW_UPGRADE + device_remove_file(dev, &dev_attr_update_fw); +#endif /* CONFIG_TOUCHSCREEN_PARADE_BINARY_FW_UPGRADE */ +#ifdef TTDL_DIAGNOSTICS + device_remove_bin_file(dev, &bin_attr_pip2_file_read); + device_remove_file(dev, &dev_attr_pip2_file_crc); +#endif + device_remove_file(dev, &dev_attr_pip2_get_last_error); + device_remove_file(dev, &dev_attr_pip2_bl_status); + device_remove_file(dev, &dev_attr_pip2_file_erase); +#ifdef CONFIG_TOUCHSCREEN_PARADE_BINARY_FW_UPGRADE + device_remove_file(dev, &dev_attr_pip2_file_write); + device_remove_file(dev, &dev_attr_pip2_bl_from_file); + device_remove_file(dev, &dev_attr_pt_bl_from_file); + device_remove_file(dev, &dev_attr_pip2_manual_ram_upgrade); + device_remove_file(dev, &dev_attr_pip2_manual_upgrade); +#endif /* CONFIG_TOUCHSCREEN_PARADE_BINARY_FW_UPGRADE */ + kfree(ld->pip2_data); + } else { +#ifdef CONFIG_TOUCHSCREEN_PARADE_BINARY_FW_UPGRADE + device_remove_file(dev, &dev_attr_update_fw); +#endif /* CONFIG_TOUCHSCREEN_PARADE_BINARY_FW_UPGRADE */ +#ifdef CONFIG_TOUCHSCREEN_PARADE_MANUAL_TTCONFIG_UPGRADE + device_remove_bin_file(dev, &bin_attr_config_data); + device_remove_file(dev, &dev_attr_config_loading); + if (!ld->config_data) + kfree(ld->config_data); +#endif /* CONFIG_TOUCHSCREEN_PARADE_MANUAL_TTCONFIG_UPGRADE */ +#ifdef CONFIG_TOUCHSCREEN_PARADE_BINARY_FW_UPGRADE + device_remove_file(dev, &dev_attr_pt_bl_from_file); + device_remove_file(dev, &dev_attr_manual_upgrade); +#endif /* CONFIG_TOUCHSCREEN_PARADE_BINARY_FW_UPGRADE */ +#ifdef CONFIG_TOUCHSCREEN_PARADE_PLATFORM_FW_UPGRADE + device_remove_file(dev, &dev_attr_forced_upgrade); +#endif /* CONFIG_TOUCHSCREEN_PARADE_PLATFORM_FW_UPGRADE */ + } + kfree(ld); +} + +static struct pt_module loader_module = { + .name = PT_LOADER_NAME, + .probe = pt_loader_probe, + .release = pt_loader_release, +}; + +/******************************************************************************* + * FUNCTION: pt_loader_init + * + * SUMMARY: Initialize function for loader module which to register + * loader_module into TTDL module list. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * *data - pointer to the loader data + ******************************************************************************/ +static int __init pt_loader_init(void) +{ + int rc; + + cmd = pt_get_commands(); + if (!cmd) + return -EINVAL; + + rc = pt_register_module(&loader_module); + if (rc < 0) { + pr_err("%s: Error, failed registering module\n", + __func__); + return rc; + } + + pr_info("%s: Parade FW Loader Driver (Version %s) rc=%d\n", + __func__, PT_DRIVER_VERSION, rc); + return 0; +} +module_init(pt_loader_init); + +/******************************************************************************* + * FUNCTION: pt_loader_exit + * + * SUMMARY: Exit function for loader module which to unregister loader_module + * from TTDL module list. + * + ******************************************************************************/ +static void __exit pt_loader_exit(void) +{ + pt_unregister_module(&loader_module); +} +module_exit(pt_loader_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Parade TrueTouch(R) Standard Product FW Loader Driver"); +MODULE_AUTHOR("Parade Technologies "); diff --git a/qcom/opensource/touch-drivers/pt/pt_mt_common.c b/qcom/opensource/touch-drivers/pt/pt_mt_common.c new file mode 100644 index 0000000000..50a8981a6c --- /dev/null +++ b/qcom/opensource/touch-drivers/pt/pt_mt_common.c @@ -0,0 +1,1006 @@ +/* + * pt_mt_common.c + * Parade TrueTouch(TM) Standard Product Multi-Touch Reports Module. + * For use with Parade touchscreen controllers. + * Supported parts include: + * TMA5XX + * TMA448 + * TMA445A + * TT21XXX + * TT31XXX + * TT4XXXX + * TT7XXX + * TC3XXX + * + * Copyright (C) 2015-2020 Parade Technologies + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * 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. + * + * Contact Parade Technologies at www.paradetech.com + */ + +#include "pt_regs.h" + +#define MT_PARAM_SIGNAL(md, sig_ost) PARAM_SIGNAL(md->pdata->frmwrk, sig_ost) +#define MT_PARAM_MIN(md, sig_ost) PARAM_MIN(md->pdata->frmwrk, sig_ost) +#define MT_PARAM_MAX(md, sig_ost) PARAM_MAX(md->pdata->frmwrk, sig_ost) +#define MT_PARAM_FUZZ(md, sig_ost) PARAM_FUZZ(md->pdata->frmwrk, sig_ost) +#define MT_PARAM_FLAT(md, sig_ost) PARAM_FLAT(md->pdata->frmwrk, sig_ost) + +/******************************************************************************* + * FUNCTION: pt_mt_lift_all + * + * SUMMARY: Reports touch liftoff action + * + * PARAMETERS: + * *md - pointer to touch data structure + ******************************************************************************/ +static void pt_mt_lift_all(struct pt_mt_data *md) +{ + int max = md->si->tch_abs[PT_TCH_T].max; + + if (md->num_prv_rec != 0) { + if (md->mt_function.report_slot_liftoff) + md->mt_function.report_slot_liftoff(md, max); + input_sync(md->input); + md->num_prv_rec = 0; + } +} + +/******************************************************************************* + * FUNCTION: pt_get_touch_axis + * + * SUMMARY: Calculates touch axis + * + * PARAMETERS: + * *md - pointer to touch data structure + * *axis - pointer to axis calculation result + * size - size in byte + * max - max value of result + * *xy_data - pointer to input data to be parsed + * bofs - bit offset + ******************************************************************************/ +static void pt_get_touch_axis(struct pt_mt_data *md, + int *axis, int size, int max, u8 *xy_data, int bofs) +{ + int nbyte; + int next; + + for (nbyte = 0, *axis = 0, next = 0; nbyte < size; nbyte++) { + pt_debug(md->dev, DL_DEBUG, + "%s: *axis=%02X(%d) size=%d max=%08X xy_data=%p xy_data[%d]=%02X(%d) bofs=%d\n", + __func__, *axis, *axis, size, max, xy_data, next, + xy_data[next], xy_data[next], bofs); + *axis = *axis + ((xy_data[next] >> bofs) << (nbyte * 8)); + next++; + } + + *axis &= max - 1; + + pt_debug(md->dev, DL_DEBUG, + "%s: *axis=%02X(%d) size=%d max=%08X xy_data=%p xy_data[%d]=%02X(%d)\n", + __func__, *axis, *axis, size, max, xy_data, next, + xy_data[next], xy_data[next]); +} + +/******************************************************************************* + * FUNCTION: pt_get_touch_hdr + * + * SUMMARY: Get the header of touch report + * + * PARAMETERS: + * *md - pointer to touch data structure + * *touch - pointer to pt_touch structure + * *xy_mode - pointer to touch mode data + ******************************************************************************/ +static void pt_get_touch_hdr(struct pt_mt_data *md, + struct pt_touch *touch, u8 *xy_mode) +{ + struct device *dev = md->dev; + struct pt_sysinfo *si = md->si; + enum pt_tch_hdr hdr; + + for (hdr = PT_TCH_TIME; hdr < PT_TCH_NUM_HDR; hdr++) { + if (!si->tch_hdr[hdr].report) + continue; + pt_get_touch_axis(md, &touch->hdr[hdr], + si->tch_hdr[hdr].size, + si->tch_hdr[hdr].max, + xy_mode + si->tch_hdr[hdr].ofs, + si->tch_hdr[hdr].bofs); + pt_debug(dev, DL_DEBUG, "%s: get %s=%04X(%d)\n", + __func__, pt_tch_hdr_string[hdr], + touch->hdr[hdr], touch->hdr[hdr]); + } + + pt_debug(dev, DL_INFO, + "%s: time=%X tch_num=%d lo=%d noise=%d counter=%d\n", + __func__, + touch->hdr[PT_TCH_TIME], + touch->hdr[PT_TCH_NUM], + touch->hdr[PT_TCH_LO], + touch->hdr[PT_TCH_NOISE], + touch->hdr[PT_TCH_COUNTER]); +} + +/******************************************************************************* + * FUNCTION: pt_get_touch_record + * + * SUMMARY: Gets axis of touch report + * + * PARAMETERS: + * *md - pointer to touch data structure + * *touch - pointer to pt_touch structure + * *xy_data - pointer to touch data + ******************************************************************************/ +static void pt_get_touch_record(struct pt_mt_data *md, + struct pt_touch *touch, u8 *xy_data) +{ + struct device *dev = md->dev; + struct pt_sysinfo *si = md->si; + enum pt_tch_abs abs; + + for (abs = PT_TCH_X; abs < PT_TCH_NUM_ABS; abs++) { + if (!si->tch_abs[abs].report) + continue; + pt_get_touch_axis(md, &touch->abs[abs], + si->tch_abs[abs].size, + si->tch_abs[abs].max, + xy_data + si->tch_abs[abs].ofs, + si->tch_abs[abs].bofs); + pt_debug(dev, DL_DEBUG, "%s: get %s=%04X(%d)\n", + __func__, pt_tch_abs_string[abs], + touch->abs[abs], touch->abs[abs]); + } +} + +/******************************************************************************* + * FUNCTION: pt_mt_process_touch + * + * SUMMARY: Process touch includes oritation,axis invert and + * convert MAJOR/MINOR from mm to resolution + * + * PARAMETERS: + * *md - pointer to touch data structure + * *touch - pointer to pt_touch structure + ******************************************************************************/ +static void pt_mt_process_touch(struct pt_mt_data *md, + struct pt_touch *touch) +{ + struct device *dev = md->dev; + struct pt_sysinfo *si = md->si; + int tmp; + bool flipped; + + + /* Orientation is signed */ + touch->abs[PT_TCH_OR] = (int8_t)touch->abs[PT_TCH_OR]; + + if (md->pdata->flags & PT_MT_FLAG_FLIP) { + tmp = touch->abs[PT_TCH_X]; + touch->abs[PT_TCH_X] = touch->abs[PT_TCH_Y]; + touch->abs[PT_TCH_Y] = tmp; + if (touch->abs[PT_TCH_OR] > 0) + touch->abs[PT_TCH_OR] = + md->or_max - touch->abs[PT_TCH_OR]; + else + touch->abs[PT_TCH_OR] = + md->or_min - touch->abs[PT_TCH_OR]; + flipped = true; + } else + flipped = false; + + /* + * 1 is subtracted from each touch location to make the location + * 0 based. e.g. If the resolution of touch panel is 1200x1600, + * the FW touch report must be (0~1199,0~1599). The driver + * should register the (min,max) value to Linux input system as + * (0~1199,0~1599). When the host needs to invert the + * coordinates, the driver would incorrectly use the resolution + * to subtract the reported point directly, such as + * 1200-(0~1199). The input system will lose the 0 point report + * and the 1200 point will be ignored. + */ + if (md->pdata->flags & PT_MT_FLAG_INV_X) { + if (flipped) + touch->abs[PT_TCH_X] = si->sensing_conf_data.res_y - + touch->abs[PT_TCH_X] - 1; + else + touch->abs[PT_TCH_X] = si->sensing_conf_data.res_x - + touch->abs[PT_TCH_X] - 1; + touch->abs[PT_TCH_OR] *= -1; + } + if (md->pdata->flags & PT_MT_FLAG_INV_Y) { + if (flipped) + touch->abs[PT_TCH_Y] = si->sensing_conf_data.res_x - + touch->abs[PT_TCH_Y] - 1; + else + touch->abs[PT_TCH_Y] = si->sensing_conf_data.res_y - + touch->abs[PT_TCH_Y] - 1; + touch->abs[PT_TCH_OR] *= -1; + } + + /* Convert MAJOR/MINOR from mm to resolution */ + tmp = touch->abs[PT_TCH_MAJ] * 100 * si->sensing_conf_data.res_x; + touch->abs[PT_TCH_MAJ] = tmp / si->sensing_conf_data.len_x; + tmp = touch->abs[PT_TCH_MIN] * 100 * si->sensing_conf_data.res_x; + touch->abs[PT_TCH_MIN] = tmp / si->sensing_conf_data.len_x; + + pt_debug(dev, DL_INFO, + "%s: flip=%s inv-x=%s inv-y=%s x=%04X(%d) y=%04X(%d)\n", + __func__, flipped ? "true" : "false", + md->pdata->flags & PT_MT_FLAG_INV_X ? "true" : "false", + md->pdata->flags & PT_MT_FLAG_INV_Y ? "true" : "false", + touch->abs[PT_TCH_X], touch->abs[PT_TCH_X], + touch->abs[PT_TCH_Y], touch->abs[PT_TCH_Y]); +} + +/******************************************************************************* + * FUNCTION: pt_report_event + * + * SUMMARY: Reports touch event + * + * PARAMETERS: + * *md - pointer to touch data structure + * event - type of touch event + * value - value of report event + ******************************************************************************/ +static void pt_report_event(struct pt_mt_data *md, int event, + int value) +{ + int sig = MT_PARAM_SIGNAL(md, event); + + if (sig != PT_IGNORE_VALUE) + input_report_abs(md->input, sig, value); +} + +/******************************************************************************* + * FUNCTION: pt_get_mt_touches + * + * SUMMARY: Parse and report touch event + * + * PARAMETERS: + * *md - pointer to touch data structure + * *tch - pointer to touch structure + * num_cur_tch - number of current touch + ******************************************************************************/ +static void pt_get_mt_touches(struct pt_mt_data *md, + struct pt_touch *tch, int num_cur_tch) +{ + struct device *dev = md->dev; + struct pt_sysinfo *si = md->si; + int sig; + int i, j, t = 0; + DECLARE_BITMAP(ids, PT_TOUCH_ID_MAX); + int mt_sync_count = 0; + u8 *tch_addr; + + if (PT_TOUCH_ID_MAX < si->tch_abs[PT_TCH_T].max) { + pt_debug(dev, DL_ERROR, + "%s: Touch ID num %d is allocated less than needed %d\n", + __func__, PT_TOUCH_ID_MAX, si->tch_abs[PT_TCH_T].max); + return; + } + + bitmap_zero(ids, PT_TOUCH_ID_MAX); + memset(tch->abs, 0, sizeof(tch->abs)); + + for (i = 0; i < num_cur_tch; i++) { + tch_addr = si->xy_data + (i * si->desc.tch_record_size); + pt_get_touch_record(md, tch, tch_addr); + + /* Discard proximity event */ + if (tch->abs[PT_TCH_O] == PT_OBJ_PROXIMITY) { + pt_debug(dev, DL_INFO, + "%s: Discarding proximity event\n", + __func__); + continue; + } + + /* Validate track_id */ + t = tch->abs[PT_TCH_T]; + if (t < md->t_min || t > md->t_max) { + pt_debug(dev, DL_INFO, + "%s: tch=%d -> bad trk_id=%d max_id=%d\n", + __func__, i, t, md->t_max); + if (md->mt_function.input_sync) + md->mt_function.input_sync(md->input); + mt_sync_count++; + continue; + } + + /* Lift-off */ + if (tch->abs[PT_TCH_E] == PT_EV_LIFTOFF) { + pt_debug(dev, DL_INFO, "%s: t=%d e=%d lift-off\n", + __func__, t, tch->abs[PT_TCH_E]); + goto pt_get_mt_touches_pr_tch; + } + + /* Process touch */ + pt_mt_process_touch(md, tch); + + /* use 0 based track id's */ + t -= md->t_min; + + sig = MT_PARAM_SIGNAL(md, PT_ABS_ID_OST); + if (sig != PT_IGNORE_VALUE) { + if (md->mt_function.input_report) + md->mt_function.input_report(md->input, sig, + t, tch->abs[PT_TCH_O]); + __set_bit(t, ids); + } + + pt_report_event(md, PT_ABS_D_OST, 0); + + /* all devices: position and pressure fields */ + for (j = 0; j <= PT_ABS_W_OST; j++) { + if (!si->tch_abs[j].report) + continue; + pt_report_event(md, PT_ABS_X_OST + j, + tch->abs[PT_TCH_X + j]); + } + + /* Get the extended touch fields */ + for (j = 0; j < PT_NUM_EXT_TCH_FIELDS; j++) { + if (!si->tch_abs[PT_ABS_MAJ_OST + j].report) + continue; + pt_report_event(md, PT_ABS_MAJ_OST + j, + tch->abs[PT_TCH_MAJ + j]); + } + if (md->mt_function.input_sync) + md->mt_function.input_sync(md->input); + mt_sync_count++; + +pt_get_mt_touches_pr_tch: + pt_debug(dev, DL_INFO, + "%s: t=%d x=%d y=%d z=%d M=%d m=%d o=%d e=%d obj=%d tip=%d\n", + __func__, t, + tch->abs[PT_TCH_X], + tch->abs[PT_TCH_Y], + tch->abs[PT_TCH_P], + tch->abs[PT_TCH_MAJ], + tch->abs[PT_TCH_MIN], + tch->abs[PT_TCH_OR], + tch->abs[PT_TCH_E], + tch->abs[PT_TCH_O], + tch->abs[PT_TCH_TIP]); + } + + if (md->mt_function.final_sync) + md->mt_function.final_sync(md->input, + si->tch_abs[PT_TCH_T].max, mt_sync_count, ids); + + md->num_prv_rec = num_cur_tch; +} + +/******************************************************************************* + * FUNCTION: pt_xy_worker + * + * SUMMARY: Read xy_data for all current touches + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *md - pointer to touch data structure + ******************************************************************************/ +static int pt_xy_worker(struct pt_mt_data *md) +{ + struct device *dev = md->dev; + struct pt_sysinfo *si = md->si; + int max_tch = si->sensing_conf_data.max_tch; + struct pt_touch tch; + u8 num_cur_tch; + int rc = 0; + + pt_get_touch_hdr(md, &tch, si->xy_mode + 3); + + num_cur_tch = tch.hdr[PT_TCH_NUM]; + if (num_cur_tch > max_tch) { + pt_debug(dev, DL_ERROR, "%s: Num touch err detected (n=%d)\n", + __func__, num_cur_tch); + num_cur_tch = max_tch; + } + + if (tch.hdr[PT_TCH_LO]) { + pt_debug(dev, DL_INFO, "%s: Large area detected\n", + __func__); + if (md->pdata->flags & PT_MT_FLAG_NO_TOUCH_ON_LO) + num_cur_tch = 0; + } + + if (num_cur_tch == 0 && md->num_prv_rec == 0) + goto pt_xy_worker_exit; + + /* extract xy_data for all currently reported touches */ + pt_debug(dev, DL_DEBUG, "%s: extract data num_cur_tch=%d\n", + __func__, num_cur_tch); + if (num_cur_tch) + pt_get_mt_touches(md, &tch, num_cur_tch); + else + pt_mt_lift_all(md); + + rc = 0; + +pt_xy_worker_exit: + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_mt_send_dummy_event + * + * SUMMARY: Send dummy/key event to wakeup upper layer of system + * + * PARAMETERS: + * *cd - pointer to core data structure + * *md - pointer to touch data structure + ******************************************************************************/ +static void pt_mt_send_dummy_event(struct pt_core_data *cd, + struct pt_mt_data *md) +{ +#ifndef EASYWAKE_TSG6 + /* TSG5 EasyWake */ + unsigned long ids = 0; + + /* for easy wakeup */ + if (md->mt_function.input_report) + md->mt_function.input_report(md->input, ABS_MT_TRACKING_ID, + 0, PT_OBJ_STANDARD_FINGER); + if (md->mt_function.input_sync) + md->mt_function.input_sync(md->input); + if (md->mt_function.final_sync) + md->mt_function.final_sync(md->input, 0, 1, &ids); + if (md->mt_function.report_slot_liftoff) + md->mt_function.report_slot_liftoff(md, 1); + if (md->mt_function.final_sync) + md->mt_function.final_sync(md->input, 1, 1, &ids); +#else + /* TSG6 FW1.3 and above only. TSG6 FW1.0 - 1.2 does not */ + /* support EasyWake, and this function will not be called */ + u8 key_value = 0; + + switch (cd->gesture_id) { + case GESTURE_DOUBLE_TAP: + key_value = KEY_WAKEUP; + break; + case GESTURE_TWO_FINGERS_SLIDE: + key_value = KEY_WAKEUP; + break; + case GESTURE_TOUCH_DETECTED: + key_value = KEY_WAKEUP; + break; + case GESTURE_PUSH_BUTTON: + key_value = KEY_F4; + break; + case GESTURE_SINGLE_SLIDE_DE_TX: + key_value = KEY_F5; + break; + case GESTURE_SINGLE_SLIDE_IN_TX: + key_value = KEY_F6; + break; + case GESTURE_SINGLE_SLIDE_DE_RX: + key_value = KEY_F7; + break; + case GESTURE_SINGLE_SLIDE_IN_RX: + key_value = KEY_F8; + break; + default: + break; + } + + if (key_value > 0) { + input_report_key(md->input, key_value, 1); + input_sync(md->input); + input_report_key(md->input, key_value, 0); + input_sync(md->input); + } + + /* + * Caution - this debug print is needed by the TTDL automated + * regression test suite + */ + pt_debug(md->dev, DL_INFO, "%s: report key: %d\n", + __func__, key_value); +#endif +} + +/******************************************************************************* + * FUNCTION: pt_mt_attention + * + * SUMMARY: Wrapper function for pt_xy_worker() that subscribe into the TTDL + * attention list. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int pt_mt_attention(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_mt_data *md = &cd->md; + int rc; + + if (md->si->xy_mode[2] != md->si->desc.tch_report_id) + return 0; + + /* core handles handshake */ + mutex_lock(&md->mt_lock); + rc = pt_xy_worker(md); + mutex_unlock(&md->mt_lock); + if (rc < 0) + pt_debug(dev, DL_ERROR, + "%s: xy_worker error r=%d\n", __func__, rc); + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_mt_wake_attention + * + * SUMMARY: Wrapper function for pt_mt_send_dummy_event() that register to + * attention list. + * + * RETURN: + * 0 = success + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int pt_mt_wake_attention(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_mt_data *md = &cd->md; + + mutex_lock(&md->mt_lock); + pt_mt_send_dummy_event(cd, md); + mutex_unlock(&md->mt_lock); + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_startup_attention + * + * SUMMARY: Wrapper function for pt_mt_lift_all() that subcribe into the TTDL + * attention list. + * + * RETURN: + * 0 = success + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int pt_startup_attention(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_mt_data *md = &cd->md; + + mutex_lock(&md->mt_lock); + pt_mt_lift_all(md); + mutex_unlock(&md->mt_lock); + + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_mt_suspend_attention + * + * SUMMARY: Function for touch to enter suspend state that as following steps: + * 1) Lift all touch + * 2) Set flag with suspend state + * 3) Decrese pm system count + * + * RETURN: + * 0 = success + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int pt_mt_suspend_attention(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_mt_data *md = &cd->md; + + mutex_lock(&md->mt_lock); + pt_mt_lift_all(md); + md->is_suspended = true; + mutex_unlock(&md->mt_lock); + + pm_runtime_put(dev); + + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_mt_resume_attention + * + * SUMMARY: Function for touch to leave suspend state that as following steps: + * 1) Increse pm system count + * 2) Clear suspend state flag + * + * RETURN: + * 0 = success + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int pt_mt_resume_attention(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_mt_data *md = &cd->md; + + pm_runtime_get(dev); + + mutex_lock(&md->mt_lock); + md->is_suspended = false; + mutex_unlock(&md->mt_lock); + + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_mt_open + * + * SUMMARY: Open method for input device(touch) that sets up call back + * functions to TTDL attention list + * + * RETURN: + * 0 = success + * + * PARAMETERS: + * *input - pointer to input_dev structure + ******************************************************************************/ +static int pt_mt_open(struct input_dev *input) +{ + struct device *dev = input->dev.parent; + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_mt_data *md = &cd->md; + + pm_runtime_get_sync(dev); + + mutex_lock(&md->mt_lock); + md->is_suspended = false; + mutex_unlock(&md->mt_lock); + + pt_debug(dev, DL_INFO, "%s: setup subscriptions\n", __func__); + + /* set up touch call back */ + _pt_subscribe_attention(dev, PT_ATTEN_IRQ, PT_MT_NAME, + pt_mt_attention, PT_MODE_OPERATIONAL); + + /* set up startup call back */ + _pt_subscribe_attention(dev, PT_ATTEN_STARTUP, PT_MT_NAME, + pt_startup_attention, 0); + + /* set up wakeup call back */ + _pt_subscribe_attention(dev, PT_ATTEN_WAKE, PT_MT_NAME, + pt_mt_wake_attention, 0); + + /* set up suspend call back */ + _pt_subscribe_attention(dev, PT_ATTEN_SUSPEND, PT_MT_NAME, + pt_mt_suspend_attention, 0); + + /* set up resume call back */ + _pt_subscribe_attention(dev, PT_ATTEN_RESUME, PT_MT_NAME, + pt_mt_resume_attention, 0); + + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_mt_close + * + * SUMMARY: Close method for input device(touch) that clears call back + * functions from TTDL attention list. + * + * PARAMETERS: + * *input - pointer to input_dev structure + ******************************************************************************/ +static void pt_mt_close(struct input_dev *input) +{ + struct device *dev = input->dev.parent; + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_mt_data *md = &cd->md; + + _pt_unsubscribe_attention(dev, PT_ATTEN_IRQ, PT_MT_NAME, + pt_mt_attention, PT_MODE_OPERATIONAL); + + _pt_unsubscribe_attention(dev, PT_ATTEN_STARTUP, PT_MT_NAME, + pt_startup_attention, 0); + + _pt_unsubscribe_attention(dev, PT_ATTEN_WAKE, PT_MT_NAME, + pt_mt_wake_attention, 0); + + _pt_unsubscribe_attention(dev, PT_ATTEN_SUSPEND, PT_MT_NAME, + pt_mt_suspend_attention, 0); + + _pt_unsubscribe_attention(dev, PT_ATTEN_RESUME, PT_MT_NAME, + pt_mt_resume_attention, 0); + + mutex_lock(&md->mt_lock); + if (!md->is_suspended) { + pm_runtime_put(dev); + md->is_suspended = true; + } + mutex_unlock(&md->mt_lock); +} + +/******************************************************************************* + * FUNCTION: pt_setup_input_device + * + * SUMMARY: Set up resolution, event signal capabilities and + * register input device for touch. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int pt_setup_input_device(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_mt_data *md = &cd->md; + int signal = PT_IGNORE_VALUE; + int max_x, max_y, max_p, min, max; + int max_x_tmp, max_y_tmp; + int i; + int rc; + + pt_debug(dev, DL_INFO, "%s: Initialize event signals\n", + __func__); + __set_bit(EV_ABS, md->input->evbit); + __set_bit(EV_REL, md->input->evbit); + __set_bit(EV_KEY, md->input->evbit); +#ifdef INPUT_PROP_DIRECT + __set_bit(INPUT_PROP_DIRECT, md->input->propbit); +#endif + + /* If virtualkeys enabled, don't use all screen */ + if (md->pdata->flags & PT_MT_FLAG_VKEYS) { + max_x_tmp = md->pdata->vkeys_x; + max_y_tmp = md->pdata->vkeys_y; + } else { + max_x_tmp = md->si->sensing_conf_data.res_x; + max_y_tmp = md->si->sensing_conf_data.res_y; + } + + /* get maximum values from the sysinfo data */ + if (md->pdata->flags & PT_MT_FLAG_FLIP) { + max_x = max_y_tmp - 1; + max_y = max_x_tmp - 1; + } else { + max_x = max_x_tmp - 1; + max_y = max_y_tmp - 1; + } + max_p = md->si->sensing_conf_data.max_z; + + /* set event signal capabilities */ + for (i = 0; i < NUM_SIGNALS(md->pdata->frmwrk); i++) { + signal = MT_PARAM_SIGNAL(md, i); + if (signal != PT_IGNORE_VALUE) { + __set_bit(signal, md->input->absbit); + + min = MT_PARAM_MIN(md, i); + max = MT_PARAM_MAX(md, i); + if (i == PT_ABS_ID_OST) { + /* shift track ids down to start at 0 */ + max = max - min; + min = min - min; + } else if (i == PT_ABS_X_OST) + max = max_x; + else if (i == PT_ABS_Y_OST) + max = max_y; + else if (i == PT_ABS_P_OST) + max = max_p; + + input_set_abs_params(md->input, signal, min, max, + MT_PARAM_FUZZ(md, i), MT_PARAM_FLAT(md, i)); + pt_debug(dev, DL_INFO, + "%s: register signal=%02X min=%d max=%d\n", + __func__, signal, min, max); + } + } + + md->or_min = MT_PARAM_MIN(md, PT_ABS_OR_OST); + md->or_max = MT_PARAM_MAX(md, PT_ABS_OR_OST); + + md->t_min = MT_PARAM_MIN(md, PT_ABS_ID_OST); + md->t_max = MT_PARAM_MAX(md, PT_ABS_ID_OST); + + rc = md->mt_function.input_register_device(md->input, + md->si->tch_abs[PT_TCH_T].max); + if (rc < 0) + pt_debug(dev, DL_ERROR, "%s: Error, failed register input device r=%d\n", + __func__, rc); + else + md->input_device_registered = true; + +#ifdef EASYWAKE_TSG6 + input_set_capability(md->input, EV_KEY, KEY_F1); + input_set_capability(md->input, EV_KEY, KEY_F2); + input_set_capability(md->input, EV_KEY, KEY_F3); + input_set_capability(md->input, EV_KEY, KEY_F4); + input_set_capability(md->input, EV_KEY, KEY_F5); + input_set_capability(md->input, EV_KEY, KEY_F6); + input_set_capability(md->input, EV_KEY, KEY_F7); + input_set_capability(md->input, EV_KEY, KEY_F8); + input_set_capability(md->input, EV_KEY, KEY_WAKEUP); +#endif + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_setup_input_attention + * + * SUMMARY: Wrapper function for pt_setup_input_device() register to TTDL + * attention list. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int pt_setup_input_attention(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_mt_data *md = &cd->md; + int rc; + + md->si = _pt_request_sysinfo(dev); + if (!md->si) + return -EINVAL; + + rc = pt_setup_input_device(dev); + + _pt_unsubscribe_attention(dev, PT_ATTEN_STARTUP, PT_MT_NAME, + pt_setup_input_attention, 0); + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_mt_probe + * + * SUMMARY: The probe function for touch input device + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +int pt_mt_probe(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_mt_data *md = &cd->md; + struct pt_platform_data *pdata = dev_get_platdata(dev); + struct pt_mt_platform_data *mt_pdata; + int rc = 0; + + pt_debug(dev, DL_INFO, + "%s: >>>>>> Register MT <<<<<<\n", __func__); + if (!pdata || !pdata->mt_pdata) { + pt_debug(dev, DL_ERROR, + "%s: Missing platform data\n", __func__); + rc = -ENODEV; + goto error_no_pdata; + } + mt_pdata = pdata->mt_pdata; + + pt_init_function_ptrs(md); + + mutex_init(&md->mt_lock); + md->dev = dev; + md->pdata = mt_pdata; + + /* Create the input device and register it. */ + pt_debug(dev, DL_INFO, + "%s: Create the input device and register it\n", __func__); + md->input = input_allocate_device(); + if (!md->input) { + pt_debug(dev, DL_ERROR, "%s: Error, failed to allocate input device\n", + __func__); + rc = -ENODEV; + goto error_alloc_failed; + } else + md->input_device_allocated = true; + + if (md->pdata->inp_dev_name) + md->input->name = md->pdata->inp_dev_name; + else + md->input->name = PT_MT_NAME; + scnprintf(md->phys, sizeof(md->phys), "%s/input%d", dev_name(dev), + cd->phys_num++); + md->input->phys = md->phys; + md->input->dev.parent = md->dev; + md->input->open = pt_mt_open; + md->input->close = pt_mt_close; + input_set_drvdata(md->input, md); + + /* get sysinfo */ + md->si = _pt_request_sysinfo(dev); + + if (md->si) { + rc = pt_setup_input_device(dev); + if (rc) + goto error_init_input; + } else { + pt_debug(dev, DL_ERROR, "%s: Fail get sysinfo pointer from core p=%p\n", + __func__, md->si); + _pt_subscribe_attention(dev, PT_ATTEN_STARTUP, + PT_MT_NAME, pt_setup_input_attention, 0); + } + + return 0; + +error_init_input: + input_free_device(md->input); + md->input_device_allocated = false; +error_alloc_failed: +error_no_pdata: + pt_debug(dev, DL_ERROR, "%s failed.\n", __func__); + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_mt_release + * + * SUMMARY: The release function for touch input device + * + * RETURN: + * 0 = success + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +int pt_mt_release(struct device *dev) +{ + struct pt_core_data *cd; + struct pt_mt_data *md; + + /* Ensure valid pointers before de-referencing them */ + if (dev) { + cd = dev_get_drvdata(dev); + if (cd) + md = &cd->md; + else + return 0; + } else { + return 0; + } + + /* + * Second call this function may cause kernel panic if probe fail. + * Use input_device_registered & input_device_allocated variable to + * avoid unregister or free unavailable devive. + */ + if (md && md->input_device_registered) { + md->input_device_registered = false; + input_unregister_device(md->input); + /* Unregistering device will free the device too */ + md->input_device_allocated = false; + } else if (md && md->input_device_allocated) { + md->input_device_allocated = false; + input_free_device(md->input); + _pt_unsubscribe_attention(dev, PT_ATTEN_STARTUP, + PT_MT_NAME, pt_setup_input_attention, 0); + } + + return 0; +} diff --git a/qcom/opensource/touch-drivers/pt/pt_mta.c b/qcom/opensource/touch-drivers/pt/pt_mta.c new file mode 100644 index 0000000000..5564816fa5 --- /dev/null +++ b/qcom/opensource/touch-drivers/pt/pt_mta.c @@ -0,0 +1,143 @@ +/* + * pt_mta.c + * Parade TrueTouch(TM) Standard Product Multi-Touch Protocol A Module. + * For use with Parade touchscreen controllers. + * Supported parts include: + * TMA5XX + * TMA448 + * TMA445A + * TT21XXX + * TT31XXX + * TT4XXXX + * TT7XXX + * TC3XXX + * + * Copyright (C) 2015-2020 Parade Technologies + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * 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. + * + * Contact Parade Technologies at www.paradetech.com + */ + +#include "pt_regs.h" + +/******************************************************************************* + * FUNCTION: pt_final_sync + * + * SUMMARY: Function to create SYN_REPORT + * + * PARAMETERS: + * *input - pointer to input device structure + * max_slots - max support touch number + * mt_sync_count - current valid touch number + * ids - bit map value + ******************************************************************************/ +static void pt_final_sync(struct input_dev *input, int max_slots, + int mt_sync_count, unsigned long *ids) +{ + if (mt_sync_count) + input_sync(input); +} + +/******************************************************************************* + * FUNCTION: pt_input_sync + * + * SUMMARY: Function to create SYN_MT_REPORT + * + * PARAMETERS: + * *input - pointer to input device structure + ******************************************************************************/ +static void pt_input_sync(struct input_dev *input) +{ + input_mt_sync(input); +} + +/******************************************************************************* + * FUNCTION: pt_input_report + * + * SUMMARY: Function to report coordinate information of touch point + * protocol + * + * PARAMETERS: + * *input - pointer to input device structure + * sig - track id to allow tracking of a touch + * t - event id to indicate an event associated with touch instance + * type - indicate the touch object + ******************************************************************************/ +static void pt_input_report(struct input_dev *input, int sig, + int t, int type) +{ + if (type == PT_OBJ_STANDARD_FINGER || type == PT_OBJ_GLOVE) { + input_report_key(input, BTN_TOOL_FINGER, PT_BTN_PRESSED); + input_report_key(input, BTN_TOOL_PEN, PT_BTN_RELEASED); + } else if (type == PT_OBJ_STYLUS) { + input_report_key(input, BTN_TOOL_PEN, PT_BTN_PRESSED); + input_report_key(input, BTN_TOOL_FINGER, PT_BTN_RELEASED); + } + + input_report_key(input, BTN_TOUCH, PT_BTN_PRESSED); + + input_report_abs(input, sig, t); +} + +/******************************************************************************* + * FUNCTION: pt_report_slot_liftoff + * + * SUMMARY: Function to report all touches are lifted + * protocol + * + * PARAMETERS: + * *md - pointer to input device structure + * max_slots - indicate max number of touch id + ******************************************************************************/ +static void pt_report_slot_liftoff(struct pt_mt_data *md, + int max_slots) +{ + input_report_key(md->input, BTN_TOUCH, PT_BTN_RELEASED); + input_report_key(md->input, BTN_TOOL_FINGER, PT_BTN_RELEASED); + input_report_key(md->input, BTN_TOOL_PEN, PT_BTN_RELEASED); + +} + +/******************************************************************************* + * FUNCTION: pt_input_register_device + * + * SUMMARY: Function to register input device + * protocol + * + * PARAMETERS: + * *input - pointer to input device structure + * max_slots - indicate max number of touch id + ******************************************************************************/ +static int pt_input_register_device(struct input_dev *input, int max_slots) +{ + __set_bit(BTN_TOUCH, input->keybit); + __set_bit(BTN_TOOL_FINGER, input->keybit); + __set_bit(BTN_TOOL_PEN, input->keybit); + return input_register_device(input); +} + +/******************************************************************************* + * FUNCTION: pt_init_function_ptrs + * + * SUMMARY: Function to init function pointer + * + * PARAMETERS: + * *md - pointer to touch data structure + ******************************************************************************/ +void pt_init_function_ptrs(struct pt_mt_data *md) +{ + md->mt_function.report_slot_liftoff = pt_report_slot_liftoff; + md->mt_function.final_sync = pt_final_sync; + md->mt_function.input_sync = pt_input_sync; + md->mt_function.input_report = pt_input_report; + md->mt_function.input_register_device = pt_input_register_device; +} diff --git a/qcom/opensource/touch-drivers/pt/pt_mtb.c b/qcom/opensource/touch-drivers/pt/pt_mtb.c new file mode 100644 index 0000000000..d40f817766 --- /dev/null +++ b/qcom/opensource/touch-drivers/pt/pt_mtb.c @@ -0,0 +1,144 @@ +/* + * pt_mtb.c + * Parade TrueTouch(TM) Standard Product Multi-Touch Protocol B Module. + * For use with Parade touchscreen controllers. + * Supported parts include: + * TMA5XX + * TMA448 + * TMA445A + * TT21XXX + * TT31XXX + * TT4XXXX + * TT7XXX + * TC3XXX + * + * Copyright (C) 2015-2020 Parade Technologies + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * 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. + * + * Contact Parade Technologies at www.paradetech.com + */ + +#include "pt_regs.h" +#include +#include + +/******************************************************************************* + * FUNCTION: pt_final_sync + * + * SUMMARY: Function to create SYN_REPORT + * + * PARAMETERS: + * *input - pointer to input device structure + * max_slots - max support touch number + * mt_sync_count - current valid touch number + * ids - bit map value + ******************************************************************************/ +static void pt_final_sync(struct input_dev *input, int max_slots, + int mt_sync_count, unsigned long *ids) +{ + int t; + + for (t = 0; t < max_slots; t++) { + if (test_bit(t, ids)) + continue; + input_mt_slot(input, t); + input_mt_report_slot_state(input, MT_TOOL_FINGER, false); + } + + input_sync(input); +} + +/******************************************************************************* + * FUNCTION: pt_input_report + * + * SUMMARY: Function to report coordinate information of touch point + * protocol + * + * PARAMETERS: + * *input - pointer to input device structure + * sig - track id to allow tracking of a touch + * t - event id to indicate an event associated with touch instance + * type - indicate the touch object + ******************************************************************************/ +static void pt_input_report(struct input_dev *input, int sig, + int t, int type) +{ + input_mt_slot(input, t); + + if (type == PT_OBJ_STANDARD_FINGER || type == PT_OBJ_GLOVE) + input_mt_report_slot_state(input, MT_TOOL_FINGER, true); + else if (type == PT_OBJ_STYLUS) + input_mt_report_slot_state(input, MT_TOOL_PEN, true); +} + +/******************************************************************************* + * FUNCTION: pt_report_slot_liftoff + * + * SUMMARY: Function to report all touches are lifted + * protocol + * + * PARAMETERS: + * *md - pointer to input device structure + * max_slots - indicate max number of touch id + ******************************************************************************/ +static void pt_report_slot_liftoff(struct pt_mt_data *md, + int max_slots) +{ + int t; + + if (md->num_prv_rec == 0) + return; + + for (t = 0; t < max_slots; t++) { + input_mt_slot(md->input, t); + input_mt_report_slot_state(md->input, + MT_TOOL_FINGER, false); + } +} + +/******************************************************************************* + * FUNCTION: pt_input_register_device + * + * SUMMARY: Function to register input device + * protocol + * + * PARAMETERS: + * *input - pointer to input device structure + * max_slots - indicate max number of touch id + ******************************************************************************/ +static int pt_input_register_device(struct input_dev *input, int max_slots) +{ +#if (KERNEL_VERSION(3, 7, 0) <= LINUX_VERSION_CODE) + input_mt_init_slots(input, max_slots, 0); +#else + input_mt_init_slots(input, max_slots); +#endif + return input_register_device(input); +} + +/******************************************************************************* + * FUNCTION: pt_init_function_ptrs + * + * SUMMARY: Function to init function pointer + * + * PARAMETERS: + * *md - pointer to touch data structure + ******************************************************************************/ +void pt_init_function_ptrs(struct pt_mt_data *md) +{ + md->mt_function.report_slot_liftoff = pt_report_slot_liftoff; + md->mt_function.final_sync = pt_final_sync; + md->mt_function.input_sync = NULL; + md->mt_function.input_report = pt_input_report; + md->mt_function.input_register_device = pt_input_register_device; +} + diff --git a/qcom/opensource/touch-drivers/pt/pt_pen.c b/qcom/opensource/touch-drivers/pt/pt_pen.c new file mode 100644 index 0000000000..679b7bc190 --- /dev/null +++ b/qcom/opensource/touch-drivers/pt/pt_pen.c @@ -0,0 +1,572 @@ +#ifndef TTDL_KERNEL_SUBMISSION +/* + * pt_pen.c + * Parade TrueTouch(TM) Standard Product CapSense Reports Module. + * For use with Parade touchscreen controllers. + * Supported parts include: + * TMA5XX + * TMA448 + * TMA445A + * TT21XXX + * TT31XXX + * TT4XXXX + * TT7XXX + * TC3XXX + * + * Copyright (C) 2015-2021 Parade Technologies + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * 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. + * + * Contact Parade Technologies at www.paradetech.com + */ + +#include "pt_regs.h" + +/******************************************************************************* + * FUNCTION: pt_pen_lift_all + * + * SUMMARY: Reports pen liftoff action + * + * PARAMETERS: + * *pend - pointer to pen data structure + ******************************************************************************/ +static void pt_pen_lift_all(struct pt_pen_data *pend) +{ + input_report_key(pend->input, BTN_STYLUS, 0); + input_report_key(pend->input, BTN_STYLUS2, 0); + input_report_key(pend->input, BTN_TOUCH, 0); + input_report_key(pend->input, BTN_TOOL_PEN, 0); + input_sync(pend->input); +} + +/******************************************************************************* + * FUNCTION: pt_get_pen_data + * + * SUMMARY: Gets axis of pen report + * + * PARAMETERS: + * *pend - pointer to pen data structure + * *xy_data - pointer to touch data + ******************************************************************************/ +static void pt_get_pen(struct pt_pen_data *pend, + struct pt_pen *pen, u8 *xy_data) +{ + struct device *dev = pend->dev; + struct pt_sysinfo *si = pend->si; + enum pt_pen_abs abs; + + for (abs = PT_PEN_X; abs < PT_PEN_NUM_ABS; abs++) { + if (!si->pen_abs[abs].report) + continue; + pt_get_touch_field(dev, &pen->abs[abs], + si->pen_abs[abs].size, + si->pen_abs[abs].max, + xy_data + si->tch_abs[abs].ofs, + si->pen_abs[abs].bofs); + pt_debug(dev, DL_DEBUG, "%s: get %s=%04X(%d)\n", + __func__, pt_pen_abs_string[abs], + pen->abs[abs], pen->abs[abs]); + } +} + + +/******************************************************************************* + * FUNCTION: pt_xy_worker + * + * SUMMARY: Read xy_data for current pen touch + * + * RETURN: + * 0 = success + * + * PARAMETERS: + * *pend - pointer to pen data structure + ******************************************************************************/ +static int pt_xy_worker(struct pt_pen_data *pend) +{ + struct pt_sysinfo *si = pend->si; + struct pt_pen pen; + bool tool; + + pt_get_pen(pend, &pen, si->xy_data + 3); + + tool = pen.abs[PT_PEN_IV] ? + BTN_TOOL_RUBBER : BTN_TOOL_PEN; + + input_report_abs(pend->input, ABS_X, pen.abs[PT_PEN_X]); + input_report_abs(pend->input, ABS_Y, pen.abs[PT_PEN_Y]); + input_report_abs(pend->input, ABS_PRESSURE, pen.abs[PT_PEN_P]); + input_report_key(pend->input, BTN_STYLUS, pen.abs[PT_PEN_BS]); + + if (si->pen_abs[PT_PEN_2ND_BS].report) + input_report_key(pend->input, BTN_STYLUS2, + pen.abs[PT_PEN_2ND_BS]); + + input_report_key(pend->input, BTN_TOUCH, pen.abs[PT_PEN_TS]); + input_report_key(pend->input, tool, pen.abs[PT_PEN_IR]); + + if (si->pen_abs[PT_PEN_X_TILT].report) + input_report_abs(pend->input, ABS_TILT_X, + pen.abs[PT_PEN_X_TILT]); + + if (si->pen_abs[PT_PEN_Y_TILT].report) + input_report_abs(pend->input, ABS_TILT_Y, + pen.abs[PT_PEN_Y_TILT]); + + input_sync(pend->input); + + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_pen_attention + * + * SUMMARY: Wrapper function for pt_xy_worker() that register to TTDL attention + * list. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int pt_pen_attention(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_pen_data *pend = &cd->pend; + int rc; + + if (pend->si->xy_mode[2] != pend->si->desc.pen_report_id) + return 0; + + /* core handles handshake */ + mutex_lock(&pend->pen_lock); + rc = pt_xy_worker(pend); + mutex_unlock(&pend->pen_lock); + if (rc < 0) + pt_debug(dev, DL_ERROR, + "%s: xy_worker error r=%d\n", __func__, rc); + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_startup_attention + * + * SUMMARY: Wrapper function for pt_pen_lift_all() that register to TTDL + * attention list. + * + * RETURN: + * 0 = success + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int pt_startup_attention(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_pen_data *pend = &cd->pend; + + mutex_lock(&pend->pen_lock); + pt_pen_lift_all(pend); + mutex_unlock(&pend->pen_lock); + + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_pen_suspend_attention + * + * SUMMARY: Function for pen to enter suspend state that as following steps: + * 1) Lift pen touch + * 2) Set flag with suspend state + * 3) Decrese pm system count + * + * RETURN: + * 0 = success + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int pt_pen_suspend_attention(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_pen_data *pend = &cd->pend; + + mutex_lock(&pend->pen_lock); + pt_pen_lift_all(pend); + pend->is_suspended = true; + mutex_unlock(&pend->pen_lock); + + pm_runtime_put(dev); + + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_pen_resume_attention + * + * SUMMARY: Function for pen to leave suspend state that as following steps: + * 1) Increse pm system count + * 2) Clear suspend state flag + * + * RETURN: + * 0 = success + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int pt_pen_resume_attention(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_pen_data *pend = &cd->pend; + + pm_runtime_get(dev); + + mutex_lock(&pend->pen_lock); + pend->is_suspended = false; + mutex_unlock(&pend->pen_lock); + + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_pen_open + * + * SUMMARY: Open method for input device(pen) that sets up call back + * functions to TTDL attention list + * + * RETURN: + * 0 = success + * + * PARAMETERS: + * *input - pointer to input_dev structure + ******************************************************************************/ +static int pt_pen_open(struct input_dev *input) +{ + struct device *dev = input->dev.parent; + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_pen_data *pend = &cd->pend; + + pm_runtime_get_sync(dev); + + mutex_lock(&pend->pen_lock); + pend->is_suspended = false; + mutex_unlock(&pend->pen_lock); + + pt_debug(dev, DL_INFO, "%s: setup subscriptions\n", __func__); + + /* set up touch call back */ + _pt_subscribe_attention(dev, PT_ATTEN_IRQ, PT_PEN_NAME, + pt_pen_attention, PT_MODE_OPERATIONAL); + + /* set up startup call back */ + _pt_subscribe_attention(dev, PT_ATTEN_STARTUP, PT_PEN_NAME, + pt_startup_attention, 0); + + /* set up suspend call back */ + _pt_subscribe_attention(dev, PT_ATTEN_SUSPEND, PT_PEN_NAME, + pt_pen_suspend_attention, 0); + + /* set up resume call back */ + _pt_subscribe_attention(dev, PT_ATTEN_RESUME, PT_PEN_NAME, + pt_pen_resume_attention, 0); + + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_pen_close + * + * SUMMARY: Close method for input device(pen) that clears call back + * functions from TTDL attention list. + * + * PARAMETERS: + * *input - pointer to input_dev structure + ******************************************************************************/ +static void pt_pen_close(struct input_dev *input) +{ + struct device *dev = input->dev.parent; + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_pen_data *pend = &cd->pend; + + _pt_unsubscribe_attention(dev, PT_ATTEN_IRQ, PT_PEN_NAME, + pt_pen_attention, PT_MODE_OPERATIONAL); + + _pt_unsubscribe_attention(dev, PT_ATTEN_STARTUP, PT_PEN_NAME, + pt_startup_attention, 0); + + _pt_unsubscribe_attention(dev, PT_ATTEN_SUSPEND, PT_PEN_NAME, + pt_pen_suspend_attention, 0); + + _pt_unsubscribe_attention(dev, PT_ATTEN_RESUME, PT_PEN_NAME, + pt_pen_resume_attention, 0); + + mutex_lock(&pend->pen_lock); + if (!pend->is_suspended) { + pm_runtime_put(dev); + pend->is_suspended = true; + } + mutex_unlock(&pend->pen_lock); +} + +/******************************************************************************* + * FUNCTION: pt_setup_input_device + * + * SUMMARY: Set up resolution, event signal capabilities and register input + * device for pen. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int pt_setup_input_device(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_pen_data *pend = &cd->pend; + int i; + int rc; + u32 usage; + int max_x, max_y, max_p; + + pt_debug(dev, DL_INFO, "%s: Initialize event signals\n", + __func__); + __set_bit(EV_ABS, pend->input->evbit); + __set_bit(EV_KEY, pend->input->evbit); + + for (i = PT_PEN_X; i < PT_PEN_NUM_ABS; i++) { + usage = pt_pen_abs_field_map[i]; + + switch (usage) { + case HID_GD_X: + max_x = pend->si->sensing_conf_data.res_x; + input_set_abs_params(pend->input, + ABS_X, 0, max_x, 0, 0); + break; + case HID_GD_Y: + max_y = pend->si->sensing_conf_data.res_y; + input_set_abs_params(pend->input, + ABS_Y, 0, max_y, 0, 0); + break; + case HID_DG_TIPPRESSURE: + max_p = pend->si->sensing_conf_data.max_z; + input_set_abs_params(pend->input, + ABS_PRESSURE, 0, max_p, 0, 0); + break; + case HID_DG_INRANGE: + input_set_capability(pend->input, + EV_KEY, BTN_TOOL_PEN); + break; + case HID_DG_INVERT: + input_set_capability(pend->input, + EV_KEY, BTN_TOOL_RUBBER); + break; + case HID_DG_TILT_X: + max_x = pend->si->pen_abs[i].max; + input_set_abs_params(pend->input, + ABS_TILT_X, 0, max_x, 0, 0); + break; + case HID_DG_TILT_Y: + max_x = pend->si->pen_abs[i].max; + input_set_abs_params(pend->input, + ABS_TILT_Y, 0, max_x, 0, 0); + break; + case HID_DG_ERASER: + case HID_DG_TIPSWITCH: + input_set_capability(pend->input, + EV_KEY, BTN_TOUCH); + break; + case HID_DG_BARRELSWITCH: + input_set_capability(pend->input, + EV_KEY, BTN_STYLUS); + break; + case HID_DG_BARRELSWITCH2: + input_set_capability(pend->input, + EV_KEY, BTN_STYLUS2); + break; + } + } + + rc = input_register_device(pend->input); + if (rc < 0) + pt_debug(dev, DL_ERROR, + "%s: Error, failed register input device r=%d\n", + __func__, rc); + else + pend->input_device_registered = true; + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_setup_input_attention + * + * SUMMARY: Wrapper function for pt_setup_input_device() register to TTDL + * attention list. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int pt_setup_input_attention(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_pen_data *pend = &cd->pend; + int rc; + + pend->si = _pt_request_sysinfo(dev); + if (!pend->si) + return -1; + + rc = pt_setup_input_device(dev); + + _pt_unsubscribe_attention(dev, PT_ATTEN_STARTUP, PT_PEN_NAME, + pt_setup_input_attention, 0); + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_pen_probe + * + * SUMMARY: The probe function for pen input device + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +int pt_pen_probe(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_pen_data *pend = &cd->pend; + struct pt_platform_data *pdata = dev_get_platdata(dev); + struct pt_pen_platform_data *pen_pdata; + int rc = 0; + + if (!pdata || !pdata->pen_pdata) { + pt_debug(dev, DL_ERROR, + "%s: Missing platform data\n", __func__); + rc = -ENODEV; + goto error_no_pdata; + } + pen_pdata = pdata->pen_pdata; + + mutex_init(&pend->pen_lock); + pend->dev = dev; + pend->pdata = pen_pdata; + + /* Create the input device and register it. */ + pt_debug(dev, DL_INFO, + "%s: Create the input device and register it\n", __func__); + pend->input = input_allocate_device(); + if (!pend->input) { + pt_debug(dev, DL_ERROR, + "%s: Error, failed to allocate input device\n", + __func__); + rc = -ENODEV; + goto error_alloc_failed; + } else + pend->input_device_allocated = true; + + if (pend->pdata->inp_dev_name) + pend->input->name = pend->pdata->inp_dev_name; + else + pend->input->name = PT_PEN_NAME; + scnprintf(pend->phys, sizeof(pend->phys), "%s/input%d", dev_name(dev), + cd->phys_num++); + pend->input->phys = pend->phys; + pend->input->dev.parent = pend->dev; + pend->input->open = pt_pen_open; + pend->input->close = pt_pen_close; + input_set_drvdata(pend->input, pend); + + /* get sysinfo */ + pend->si = _pt_request_sysinfo(dev); + + if (pend->si) { + rc = pt_setup_input_device(dev); + if (rc) + goto error_init_input; + } else { + pt_debug(dev, DL_ERROR, + "%s: Fail get sysinfo pointer from core p=%p\n", + __func__, pend->si); + _pt_subscribe_attention(dev, PT_ATTEN_STARTUP, + PT_PEN_NAME, pt_setup_input_attention, 0); + } + + return 0; + +error_init_input: + input_free_device(pend->input); + pend->input_device_allocated = false; +error_alloc_failed: +error_no_pdata: + pt_debug(dev, DL_ERROR, "%s failed.\n", __func__); + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_pen_release + * + * SUMMARY: The release function for pen input device + * + * RETURN: + * 0 = success + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +int pt_pen_release(struct device *dev) +{ + struct pt_core_data *cd; + struct pt_pen_data *pend; + + /* Ensure valid pointers before de-referencing them */ + if (dev) { + cd = dev_get_drvdata(dev); + if (cd) + pend = &cd->pend; + else + return 0; + } else { + return 0; + } + + /* + * Second call this function may cause kernel panic if probe fail. + * Use input_device_registered & input_device_allocated variable to + * avoid unregister or free unavailable devive. + */ + if (pend && pend->input_device_registered) { + pend->input_device_registered = false; + input_unregister_device(pend->input); + /* Unregistering device will free the device too */ + pend->input_device_allocated = false; + } else if (pend && pend->input_device_allocated) { + pend->input_device_allocated = false; + input_free_device(pend->input); + _pt_unsubscribe_attention(dev, PT_ATTEN_STARTUP, + PT_PEN_NAME, pt_setup_input_attention, 0); + } + + return 0; +} +#endif /*!TTDL_KERNEL_SUBMISSION */ diff --git a/qcom/opensource/touch-drivers/pt/pt_platform.c b/qcom/opensource/touch-drivers/pt/pt_platform.c new file mode 100644 index 0000000000..4508774f55 --- /dev/null +++ b/qcom/opensource/touch-drivers/pt/pt_platform.c @@ -0,0 +1,1051 @@ +/* + * pt_platform.c + * Parade TrueTouch(TM) Standard Product Platform Module. + * For use with Parade touchscreen controllers. + * Supported parts include: + * TMA5XX + * TMA448 + * TMA445A + * TT21XXX + * TT31XXX + * TT4XXXX + * TT7XXX + * TC3XXX + * + * Copyright (C) 2015-2020 Parade Technologies + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * 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. + * + * Contact Parade Technologies at www.paradetech.com + */ + +#include "pt_regs.h" +#include "pt_platform.h" + +#ifdef CONFIG_TOUCHSCREEN_PARADE_PLATFORM_FW_UPGRADE +/* FW for Panel ID = 0x00 */ +#include "pt_fw_pid00.h" +static struct pt_touch_firmware pt_firmware_pid00 = { + .img = pt_img_pid00, + .size = ARRAY_SIZE(pt_img_pid00), + .ver = pt_ver_pid00, + .vsize = ARRAY_SIZE(pt_ver_pid00), + .panel_id = 0x00, +}; + +/* FW for Panel ID = 0x01 */ +#include "pt_fw_pid01.h" +static struct pt_touch_firmware pt_firmware_pid01 = { + .img = pt_img_pid01, + .size = ARRAY_SIZE(pt_img_pid01), + .ver = pt_ver_pid01, + .vsize = ARRAY_SIZE(pt_ver_pid01), + .panel_id = 0x01, +}; + +/* FW for Panel ID not enabled (legacy) */ +#include "pt_fw.h" +static struct pt_touch_firmware pt_firmware = { + .img = pt_img, + .size = ARRAY_SIZE(pt_img), + .ver = pt_ver, + .vsize = ARRAY_SIZE(pt_ver), +}; +#else +/* FW for Panel ID not enabled (legacy) */ +static struct pt_touch_firmware pt_firmware = { + .img = NULL, + .size = 0, + .ver = NULL, + .vsize = 0, +}; +#endif + +#ifdef CONFIG_TOUCHSCREEN_PARADE_PLATFORM_TTCONFIG_UPGRADE +/* TT Config for Panel ID = 0x00 */ +#include "pt_params_pid00.h" +static struct touch_settings pt_sett_param_regs_pid00 = { + .data = (uint8_t *)&pt_param_regs_pid00[0], + .size = ARRAY_SIZE(pt_param_regs_pid00), + .tag = 0, +}; + +static struct touch_settings pt_sett_param_size_pid00 = { + .data = (uint8_t *)&pt_param_size_pid00[0], + .size = ARRAY_SIZE(pt_param_size_pid00), + .tag = 0, +}; + +static struct pt_touch_config pt_ttconfig_pid00 = { + .param_regs = &pt_sett_param_regs_pid00, + .param_size = &pt_sett_param_size_pid00, + .fw_ver = ttconfig_fw_ver_pid00, + .fw_vsize = ARRAY_SIZE(ttconfig_fw_ver_pid00), + .panel_id = 0x00, +}; + +/* TT Config for Panel ID = 0x01 */ +#include "pt_params_pid01.h" +static struct touch_settings pt_sett_param_regs_pid01 = { + .data = (uint8_t *)&pt_param_regs_pid01[0], + .size = ARRAY_SIZE(pt_param_regs_pid01), + .tag = 0, +}; + +static struct touch_settings pt_sett_param_size_pid01 = { + .data = (uint8_t *)&pt_param_size_pid01[0], + .size = ARRAY_SIZE(pt_param_size_pid01), + .tag = 0, +}; + +static struct pt_touch_config pt_ttconfig_pid01 = { + .param_regs = &pt_sett_param_regs_pid01, + .param_size = &pt_sett_param_size_pid01, + .fw_ver = ttconfig_fw_ver_pid01, + .fw_vsize = ARRAY_SIZE(ttconfig_fw_ver_pid01), + .panel_id = 0x01, +}; + +/* TT Config for Panel ID not enabled (legacy)*/ +#include "pt_params.h" +static struct touch_settings pt_sett_param_regs = { + .data = (uint8_t *)&pt_param_regs[0], + .size = ARRAY_SIZE(pt_param_regs), + .tag = 0, +}; + +static struct touch_settings pt_sett_param_size = { + .data = (uint8_t *)&pt_param_size[0], + .size = ARRAY_SIZE(pt_param_size), + .tag = 0, +}; + +static struct pt_touch_config pt_ttconfig = { + .param_regs = &pt_sett_param_regs, + .param_size = &pt_sett_param_size, + .fw_ver = ttconfig_fw_ver, + .fw_vsize = ARRAY_SIZE(ttconfig_fw_ver), +}; +#else +/* TT Config for Panel ID not enabled (legacy)*/ +static struct pt_touch_config pt_ttconfig = { + .param_regs = NULL, + .param_size = NULL, + .fw_ver = NULL, + .fw_vsize = 0, +}; +#endif + +static struct pt_touch_firmware *pt_firmwares[] = { +#ifdef CONFIG_TOUCHSCREEN_PARADE_PLATFORM_FW_UPGRADE + &pt_firmware_pid00, + &pt_firmware_pid01, +#endif + NULL, /* Last item should always be NULL */ +}; + +static struct pt_touch_config *pt_ttconfigs[] = { +#ifdef CONFIG_TOUCHSCREEN_PARADE_PLATFORM_TTCONFIG_UPGRADE + &pt_ttconfig_pid00, + &pt_ttconfig_pid01, +#endif + NULL, /* Last item should always be NULL */ +}; + +struct pt_loader_platform_data _pt_loader_platform_data = { + .fw = &pt_firmware, + .ttconfig = &pt_ttconfig, + .fws = pt_firmwares, + .ttconfigs = pt_ttconfigs, + .flags = PT_LOADER_FLAG_NONE, +}; + +/******************************************************************************* + * FUNCTION: pt_xres + * + * SUMMARY: Toggles the reset gpio (TP_XRES) to perform a HW reset + * + * RETURN: + * 0 = success + * !0 = fail + * + * PARAMETERS: + * *pdata - pointer to the platform data structure + * *dev - pointer to Device structure + ******************************************************************************/ +int pt_xres(struct pt_core_platform_data *pdata, + struct device *dev) +{ + int rst_gpio = pdata->rst_gpio; + int rc = 0; + int ddi_rst_gpio = pdata->ddi_rst_gpio; + + pt_debug(dev, DL_WARN, "%s: 20ms HARD RESET on gpio=%d\n", + __func__, pdata->rst_gpio); + + /* Toggling only TP_XRES as DDI_XRES resets the entire part */ + gpio_set_value(rst_gpio, 1); + if (ddi_rst_gpio) + gpio_set_value(ddi_rst_gpio, 1); + usleep_range(3000, 4000); + gpio_set_value(rst_gpio, 0); + usleep_range(6000, 7000); + gpio_set_value(rst_gpio, 1); + if (ddi_rst_gpio) + gpio_set_value(ddi_rst_gpio, 1); + + /* Sleep to allow the DUT to boot */ + usleep_range(3000, 4000); + return rc; +} + +#ifdef PT_PINCTRL_EN +/******************************************************************************* + * FUNCTION: pt_pinctrl_init + * + * SUMMARY: Pinctrl method to obtain pin state handler for TP_RST, IRQ + * + * RETURN: + * 0 = success + * !0 = fail + * + * PARAMETERS: + * *pdata - pointer to the platform data structure + * *dev - pointer to Device structure + ******************************************************************************/ +static int pt_pinctrl_init(struct pt_core_platform_data *pdata, + struct device *dev) +{ + int ret = 0; + + pdata->pinctrl = devm_pinctrl_get(dev); + if (IS_ERR_OR_NULL(pdata->pinctrl)) { + pt_debug(dev, DL_ERROR, + "Failed to get pinctrl, please check dts"); + ret = PTR_ERR(pdata->pinctrl); + goto err_pinctrl_get; + } + + pdata->pins_active = + pinctrl_lookup_state(pdata->pinctrl, "pmx_ts_active"); + if (IS_ERR_OR_NULL(pdata->pins_active)) { + pt_debug(dev, DL_ERROR, "pmx_ts_active not found"); + ret = PTR_ERR(pdata->pins_active); + goto err_pinctrl_lookup; + } + + pdata->pins_suspend = + pinctrl_lookup_state(pdata->pinctrl, "pmx_ts_suspend"); + if (IS_ERR_OR_NULL(pdata->pins_suspend)) { + pt_debug(dev, DL_ERROR, "pmx_ts_suspend not found"); + ret = PTR_ERR(pdata->pins_suspend); + goto err_pinctrl_lookup; + } + + pdata->pins_release = + pinctrl_lookup_state(pdata->pinctrl, "pmx_ts_release"); + if (IS_ERR_OR_NULL(pdata->pins_release)) { + pt_debug(dev, DL_ERROR, "pmx_ts_release not found"); + ret = PTR_ERR(pdata->pins_release); + goto err_pinctrl_lookup; + } + + return 0; + +err_pinctrl_lookup: + devm_pinctrl_put(pdata->pinctrl); + +err_pinctrl_get: + pdata->pinctrl = NULL; + pdata->pins_release = NULL; + pdata->pins_suspend = NULL; + pdata->pins_active = NULL; + return ret; +} + +/******************************************************************************* + * FUNCTION: pt_pinctrl_select_normal + * + * SUMMARY: Pinctrl method to configure drive mode for TP_RST, IRQ - normal + * + * RETURN: + * 0 = success + * !0 = fail + * + * PARAMETERS: + * *pdata - pointer to the platform data structure + * *dev - pointer to Device structure + ******************************************************************************/ +static int pt_pinctrl_select_normal(struct pt_core_platform_data *pdata, + struct device *dev) +{ + int ret = 0; + + if (pdata->pinctrl && pdata->pins_active) { + ret = pinctrl_select_state(pdata->pinctrl, pdata->pins_active); + if (ret < 0) { + pt_debug(dev, DL_ERROR, "Set normal pin state error=%d", + ret); + } + } + + return ret; +} + +/******************************************************************************* + * FUNCTION: pt_pinctrl_select_suspend + * + * SUMMARY: Pinctrl method to configure drive mode for TP_RST, IRQ - suspend + * + * RETURN: + * 0 = success + * !0 = fail + * + * PARAMETERS: + * *pdata - pointer to the platform data structure + * *dev - pointer to Device structure + ******************************************************************************/ +static int pt_pinctrl_select_suspend(struct pt_core_platform_data *pdata, + struct device *dev) +{ + int ret = 0; + + if (pdata->pinctrl && pdata->pins_suspend) { + ret = pinctrl_select_state(pdata->pinctrl, pdata->pins_suspend); + if (ret < 0) { + pt_debug(dev, DL_ERROR, + "Set suspend pin state error=%d", ret); + } + } + + return ret; +} + +/******************************************************************************* + * FUNCTION: pt_pinctrl_select_release + * + * SUMMARY: Pinctrl method to configure drive mode for TP_RST, IRQ - release + * + * RETURN: + * 0 = success + * !0 = fail + * + * PARAMETERS: + * *pdata - pointer to the platform data structure + * *dev - pointer to Device structure + ******************************************************************************/ +static int pt_pinctrl_select_release(struct pt_core_platform_data *pdata, + struct device *dev) +{ + int ret = 0; + + if (pdata->pinctrl) { + if (IS_ERR_OR_NULL(pdata->pins_release)) { + devm_pinctrl_put(pdata->pinctrl); + pdata->pinctrl = NULL; + } else { + ret = pinctrl_select_state(pdata->pinctrl, + pdata->pins_release); + if (ret < 0) + pt_debug(dev, DL_ERROR, + "Set gesture pin state error=%d", ret); + } + } + + return ret; +} +#endif /* PT_PINCTRL_EN */ + +/******************************************************************************* + * FUNCTION: pt_init + * + * SUMMARY: Set up/free gpios for TP_RST, IRQ, DDI_RST. + * + * RETURN: + * 0 = success + * !0 = fail + * + * PARAMETERS: + * *pdata - pointer to the platform data structure + * on - flag to set up or free gpios(0:free; !0:set up) + * *dev - pointer to Device structure + ******************************************************************************/ +int pt_init(struct pt_core_platform_data *pdata, + int on, struct device *dev) +{ + int rst_gpio = pdata->rst_gpio; + int irq_gpio = pdata->irq_gpio; + int ddi_rst_gpio = pdata->ddi_rst_gpio; + int rc = 0; + +#ifdef PT_PINCTRL_EN + if (on) { + rc = pt_pinctrl_init(pdata, dev); + if (!rc) { + pt_pinctrl_select_normal(pdata, dev); + } else { + pt_debug(dev, DL_ERROR, + "%s: Failed to request pinctrl\n", __func__); + } + } +#endif + if (on && rst_gpio) { + /* Configure RST GPIO */ + pt_debug(dev, DL_WARN, "%s: Request RST GPIO %d", + __func__, rst_gpio); + rc = gpio_request(rst_gpio, NULL); + if (rc < 0) { + gpio_free(rst_gpio); + rc = gpio_request(rst_gpio, NULL); + } + if (rc < 0) { + pt_debug(dev, DL_ERROR, + "%s: Failed requesting RST GPIO %d\n", + __func__, rst_gpio); + goto fail_rst_gpio; + } else { + /* + * Set the GPIO direction and the starting level + * The start level is high because the DUT needs + * to stay in reset during power up. + */ + rc = gpio_direction_output(rst_gpio, 1); + if (rc < 0) { + pt_debug(dev, DL_ERROR, + "%s: Output Setup ERROR: RST GPIO %d\n", + __func__, rst_gpio); + goto fail_rst_gpio; + } + } + } + + if (on && irq_gpio) { + /* Configure IRQ GPIO */ + pt_debug(dev, DL_WARN, "%s: Request IRQ GPIO %d", + __func__, irq_gpio); + rc = gpio_request(irq_gpio, NULL); + if (rc < 0) { + gpio_free(irq_gpio); + rc = gpio_request(irq_gpio, NULL); + } + if (rc < 0) { + pt_debug(dev, DL_ERROR, + "%s: Failed requesting IRQ GPIO %d\n", + __func__, irq_gpio); + goto fail_irq_gpio; + } else { + /* Set the GPIO direction */ + rc = gpio_direction_input(irq_gpio); + if (rc < 0) { + pt_debug(dev, DL_ERROR, + "%s: Input Setup ERROR: IRQ GPIO %d\n", + __func__, irq_gpio); + goto fail_irq_gpio; + } + } + } + + if (on && ddi_rst_gpio) { + /* Configure DDI RST GPIO */ + pt_debug(dev, DL_WARN, "%s: Request DDI RST GPIO %d", + __func__, ddi_rst_gpio); + rc = gpio_request(ddi_rst_gpio, NULL); + if (rc < 0) { + gpio_free(ddi_rst_gpio); + rc = gpio_request(ddi_rst_gpio, NULL); + } + if (rc < 0) { + pt_debug(dev, DL_ERROR, + "%s: Failed requesting DDI RST GPIO %d\n", + __func__, ddi_rst_gpio); + goto fail_ddi_rst_gpio; + } else { + /* Set the GPIO direction and the starting level */ + rc = gpio_direction_output(ddi_rst_gpio, 0); + if (rc < 0) { + pt_debug(dev, DL_ERROR, + "%s: Output Setup ERROR: RST GPIO %d\n", + __func__, ddi_rst_gpio); + goto fail_ddi_rst_gpio; + } + } + } + + if (!on) { + /* "on" not set, therefore free all gpio's */ + if (ddi_rst_gpio) + gpio_free(ddi_rst_gpio); + if (irq_gpio) + gpio_free(irq_gpio); + if (rst_gpio) + gpio_free(rst_gpio); +#ifdef PT_PINCTRL_EN + pt_pinctrl_select_release(pdata, dev); +#endif + } + + /* All GPIO's created successfully */ + goto success; + +fail_ddi_rst_gpio: + pt_debug(dev, DL_ERROR, + "%s: ERROR - GPIO setup Failure, freeing DDI_XRES GPIO %d\n", + __func__, ddi_rst_gpio); + gpio_free(ddi_rst_gpio); +fail_irq_gpio: + pt_debug(dev, DL_ERROR, + "%s: ERROR - GPIO setup Failure, freeing IRQ GPIO %d\n", + __func__, irq_gpio); + gpio_free(irq_gpio); +fail_rst_gpio: + pt_debug(dev, DL_ERROR, + "%s: ERROR - GPIO setup Failure, freeing TP_XRES GPIO %d\n", + __func__, rst_gpio); + gpio_free(rst_gpio); + +success: + pt_debug(dev, DL_INFO, + "%s: SUCCESS - Configured DDI_XRES GPIO %d, IRQ GPIO %d, TP_XRES GPIO %d\n", + __func__, ddi_rst_gpio, irq_gpio, rst_gpio); + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_wakeup + * + * SUMMARY: Resume power for "power on/off" sleep strategy which against to + * "deepsleep" strategy. + * + * RETURN: + * 0 = success + * !0 = fail + * + * PARAMETERS: + * *pdata - pointer to the platform data structure + * *dev - pointer to Device structure + * *ignore_irq - pointer to atomic structure to allow the host ignoring false + * IRQ during power up + ******************************************************************************/ +static int pt_wakeup(struct pt_core_platform_data *pdata, + struct device *dev, atomic_t *ignore_irq) +{ + /* Example for TT7XXX */ + int rc = 0; + +#ifdef PT_PINCTRL_EN + rc = pt_pinctrl_select_normal(pdata, dev); + if (rc) + pr_err("%s: GPIO pins activation error: rc=%d\n", + __func__, rc); +#endif + +#ifdef TT7XXX_EXAMPLE + pt_debug(dev, DL_INFO, + "%s: Enable defined pwr: VDDI, VCC\n", __func__); + /* + * Force part into RESET by holding XRES#(TP_XRES) + * while powering it up + */ + if (pdata->rst_gpio) + gpio_set_value(pdata->rst_gpio, 0); + + /* Turn on VDDI [Digital Interface] (+1.8v) */ + if (pdata->vddi_gpio) { + rc = gpio_request(pdata->vddi_gpio, NULL); + if (rc < 0) { + gpio_free(pdata->vddi_gpio); + rc = gpio_request(pdata->vddi_gpio, NULL); + } + if (rc < 0) { + pr_err("%s: Failed requesting VDDI GPIO %d\n", + __func__, pdata->vddi_gpio); + } + rc = gpio_direction_output(pdata->vddi_gpio, 1); + if (rc) + pr_err("%s: setcfg for VDDI GPIO %d failed\n", + __func__, pdata->vddi_gpio); + gpio_free(pdata->vddi_gpio); + usleep_range(3000, 4000); + } + + /* Turn on VCC */ + if (pdata->vcc_gpio) { + rc = gpio_request(pdata->vcc_gpio, NULL); + if (rc < 0) { + gpio_free(pdata->vcc_gpio); + rc = gpio_request(pdata->vcc_gpio, NULL); + } + if (rc < 0) { + pr_err("%s: Failed requesting VCC GPIO %d\n", + __func__, pdata->vcc_gpio); + } + rc = gpio_direction_output(pdata->vcc_gpio, 1); + if (rc) + pr_err("%s: setcfg for VCC GPIO %d failed\n", + __func__, pdata->vcc_gpio); + gpio_free(pdata->vcc_gpio); + usleep_range(3000, 4000); + } + + usleep_range(12000, 15000); + /* Force part out of RESET by releasing XRES#(TP_XRES) */ + if (pdata->rst_gpio) + gpio_set_value(pdata->rst_gpio, 1); +#else + pt_debug(dev, DL_INFO, "%s: Enable defined pwr\n", __func__); +#endif + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_sleep + * + * SUMMARY: Suspend power for "power on/off" sleep strategy which against to + * "deepsleep" strategy. + * + * RETURN: + * 0 = success + * !0 = fail + * + * PARAMETERS: + * *pdata - pointer to the platform data structure + * *dev - pointer to Device structure + * *ignore_irq - pointer to atomic structure to allow the host ignoring false + * IRQ during power down + ******************************************************************************/ +static int pt_sleep(struct pt_core_platform_data *pdata, + struct device *dev, atomic_t *ignore_irq) +{ + /* Example for TT7XXX */ + int rc = 0; + +#ifdef TT7XXX_EXAMPLE + pt_debug(dev, DL_INFO, + "%s: Turn off defined pwr: VCC, VDDI\n", __func__); + /* + * Force part into RESET by holding XRES#(TP_XRES) + * while powering it up + */ + if (pdata->rst_gpio) + gpio_set_value(pdata->rst_gpio, 0); + + /* Turn off VCC */ + if (pdata->vcc_gpio) { + rc = gpio_request(pdata->vcc_gpio, NULL); + if (rc < 0) { + gpio_free(pdata->vcc_gpio); + rc = gpio_request(pdata->vcc_gpio, NULL); + } + if (rc < 0) { + pr_err("%s: Failed requesting VCC GPIO %d\n", + __func__, pdata->vcc_gpio); + } + rc = gpio_direction_output(pdata->vcc_gpio, 0); + if (rc) + pr_err("%s: setcfg for VCC GPIO %d failed\n", + __func__, pdata->vcc_gpio); + gpio_free(pdata->vcc_gpio); + } + + /* Turn off VDDI [Digital Interface] (+1.8v) */ + if (pdata->vddi_gpio) { + rc = gpio_request(pdata->vddi_gpio, NULL); + if (rc < 0) { + gpio_free(pdata->vddi_gpio); + rc = gpio_request(pdata->vddi_gpio, NULL); + } + if (rc < 0) { + pr_err("%s: Failed requesting VDDI GPIO %d\n", + __func__, pdata->vddi_gpio); + } + rc = gpio_direction_output(pdata->vddi_gpio, 0); + if (rc) + pr_err("%s: setcfg for VDDI GPIO %d failed\n", + __func__, pdata->vddi_gpio); + gpio_free(pdata->vddi_gpio); + usleep_range(10000, 12000); + } +#else + pt_debug(dev, DL_INFO, "%s: Turn off defined pwr\n", __func__); +#endif +#ifdef PT_PINCTRL_EN + rc = pt_pinctrl_select_suspend(pdata, dev); + if (rc) + pr_err("%s: GPIO pins suspend error: rc=%d\n", + __func__, rc); +#endif + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_power + * + * SUMMARY: Wrapper function to resume/suspend power with function + * pt_wakeup()/pt_sleep(). + * + * RETURN: + * 0 = success + * !0 = fail + * + * PARAMETERS: + * *pdata - pointer to the platform data structure + * on - flag to remsume/suspend power(0:resume; 1:suspend) + * *dev - pointer to Device structure + * *ignore_irq - pointer to atomic structure to allow the host ignoring false + * IRQ during power up/down + ******************************************************************************/ +int pt_power(struct pt_core_platform_data *pdata, + int on, struct device *dev, atomic_t *ignore_irq) +{ + if (on) + return pt_wakeup(pdata, dev, ignore_irq); + + return pt_sleep(pdata, dev, ignore_irq); +} + +/******************************************************************************* + * FUNCTION: pt_irq_stat + * + * SUMMARY: Obtain the level state of IRQ gpio. + * + * RETURN: + * level state of IRQ gpio + * + * PARAMETERS: + * *pdata - pointer to the platform data structure + * *dev - pointer to Device structure + ******************************************************************************/ +int pt_irq_stat(struct pt_core_platform_data *pdata, + struct device *dev) +{ + return gpio_get_value(pdata->irq_gpio); +} + +#ifdef PT_DETECT_HW +/******************************************************************************* + * FUNCTION: pt_detect + * + * SUMMARY: Detect the I2C device by reading one byte(FW sentiel) after the + * reset operation. + * + * RETURN: + * 0 - detected + * !0 - undetected + * + * PARAMETERS: + * *pdata - pointer to the platform data structure + * *dev - pointer to Device structure + * read - pointer to the function to perform a read operation + ******************************************************************************/ +int pt_detect(struct pt_core_platform_data *pdata, + struct device *dev, pt_platform_read read) +{ + int retry = 3; + int rc; + char buf[1]; + + while (retry--) { + /* Perform reset, wait for 100 ms and perform read */ + pt_debug(dev, DL_WARN, "%s: Performing a reset\n", + __func__); + pdata->xres(pdata, dev); + msleep(100); + rc = read(dev, buf, 1); + if (!rc) + return 0; + + pt_debug(dev, DL_ERROR, "%s: Read unsuccessful, try=%d\n", + __func__, 3 - retry); + } + + return rc; +} +#endif + +/******************************************************************************* + * FUNCTION: pt_setup_power + * + * SUMMARY: Turn on/turn off voltage regulator + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *pdata - pointer to core platform data + * on - flag to decide power state,PT_MT_POWER_ON/PT_MT_POWER_OFF + * *dev - pointer to device + ******************************************************************************/ +int pt_setup_power(struct pt_core_platform_data *pdata, int on, + struct device *dev) +{ + int en_vcc = pdata->vcc_gpio; + int en_vddi = pdata->vddi_gpio; + int en_avdd = pdata->avdd_gpio; + int en_avee = pdata->avee_gpio; + int rc = 0; + + /* + * For TDDI parts, force part into RESET by holding DDI XRES + * while powering it up + */ + if (pdata->ddi_rst_gpio) + gpio_set_value(pdata->ddi_rst_gpio, 0); + + /* + * Force part into RESET by holding XRES#(TP_XRES) + * while powering it up + */ + if (pdata->rst_gpio) + gpio_set_value(pdata->rst_gpio, 0); + + if (on == PT_MT_POWER_ON) { + /* + * Enable GPIOs to turn on voltage regulators to pwr up DUT + * - TC device power up order: VDDI, VCC, AVDD, AVEE + * - TT device power up order: VDDI, VCC + * NOTE: VDDI must be stable for >10ms before XRES is released + */ + pt_debug(dev, DL_INFO, + "%s: Enable defined pwr: VDDI, VCC, AVDD, AVEE\n", __func__); + + /* Turn on VDDI [Digital Interface] (+1.8v) */ + if (pdata->vddi_gpio) { + rc = gpio_request(en_vddi, NULL); + if (rc < 0) { + gpio_free(en_vddi); + rc = gpio_request(en_vddi, NULL); + } + if (rc < 0) { + pr_err("%s: Failed requesting VDDI GPIO %d\n", + __func__, en_vddi); + } + rc = gpio_direction_output(en_vddi, 1); + if (rc) + pr_err("%s: setcfg for VDDI GPIO %d failed\n", + __func__, en_vddi); + gpio_free(en_vddi); + usleep_range(3000, 4000); + } + + /* Turn on VCC */ + if (pdata->vcc_gpio) { + rc = gpio_request(en_vcc, NULL); + if (rc < 0) { + gpio_free(en_vcc); + rc = gpio_request(en_vcc, NULL); + } + if (rc < 0) { + pr_err("%s: Failed requesting VCC GPIO %d\n", + __func__, en_vcc); + } + rc = gpio_direction_output(en_vcc, 1); + if (rc) + pr_err("%s: setcfg for VCC GPIO %d failed\n", + __func__, en_vcc); + gpio_free(en_vcc); + usleep_range(3000, 4000); + } + + /* Turn on AVDD (+5.0v) */ + if (pdata->avdd_gpio) { + rc = gpio_request(en_avdd, NULL); + if (rc < 0) { + gpio_free(en_avdd); + rc = gpio_request(en_avdd, NULL); + } + if (rc < 0) { + pr_err("%s: Failed requesting AVDD GPIO %d\n", + __func__, en_avdd); + } + rc = gpio_direction_output(en_avdd, 1); + if (rc) + pr_err("%s: setcfg for AVDD GPIO %d failed\n", + __func__, en_avdd); + gpio_free(en_avdd); + usleep_range(3000, 4000); + } + + /* Turn on AVEE (-5.0v) */ + if (pdata->avee_gpio) { + rc = gpio_request(en_avee, NULL); + if (rc < 0) { + gpio_free(en_avee); + rc = gpio_request(en_avee, NULL); + } + if (rc < 0) { + pr_err("%s: Failed requesting AVEE GPIO %d\n", + __func__, en_avee); + } + rc = gpio_direction_output(en_avee, 1); + if (rc) + pr_err("%s: setcfg for AVEE GPIO %d failed\n", + __func__, en_avee); + gpio_free(en_avee); + usleep_range(3000, 4000); + } + } else { + /* + * Disable GPIOs to turn off voltage regulators to pwr down + * TC device The power down order is: AVEE, AVDD, VDDI + * TT device The power down order is: VCC, VDDI + * + * Note:Turn off some of regulators may effect display + * parts for TDDI chip + */ + pt_debug(dev, DL_INFO, + "%s: Turn off defined pwr: VCC, AVEE, AVDD, VDDI\n", __func__); + + /* Turn off VCC */ + if (pdata->vcc_gpio) { + rc = gpio_request(en_vcc, NULL); + if (rc < 0) { + gpio_free(en_vcc); + rc = gpio_request(en_vcc, NULL); + } + if (rc < 0) { + pr_err("%s: Failed requesting VCC GPIO %d\n", + __func__, en_vcc); + } + rc = gpio_direction_output(en_vcc, 0); + if (rc) + pr_err("%s: setcfg for VCC GPIO %d failed\n", + __func__, en_vcc); + gpio_free(en_vcc); + } + + /* Turn off AVEE (-5.0v) */ + if (pdata->avee_gpio) { + rc = gpio_request(en_avee, NULL); + if (rc < 0) { + gpio_free(en_avee); + rc = gpio_request(en_avee, NULL); + } + if (rc < 0) { + pr_err("%s: Failed requesting AVEE GPIO %d\n", + __func__, en_avee); + } + rc = gpio_direction_output(en_avee, 0); + if (rc) + pr_err("%s: setcfg for AVEE GPIO %d failed\n", + __func__, en_avee); + gpio_free(en_avee); + } + + /* Turn off AVDD (+5.0v) */ + if (pdata->avdd_gpio) { + rc = gpio_request(en_avdd, NULL); + if (rc < 0) { + gpio_free(en_avdd); + rc = gpio_request(en_avdd, NULL); + } + if (rc < 0) { + pr_err("%s: Failed requesting AVDD GPIO %d\n", + __func__, en_avdd); + } + rc = gpio_direction_output(en_avdd, 0); + if (rc) + pr_err("%s: setcfg for AVDD GPIO %d failed\n", + __func__, en_avdd); + gpio_free(en_avdd); + } + + /* Turn off VDDI [Digital Interface] (+1.8v) */ + if (pdata->vddi_gpio) { + rc = gpio_request(en_vddi, NULL); + if (rc < 0) { + gpio_free(en_vddi); + rc = gpio_request(en_vddi, NULL); + } + if (rc < 0) { + pr_err("%s: Failed requesting VDDI GPIO %d\n", + __func__, en_vddi); + } + rc = gpio_direction_output(en_vddi, 0); + if (rc) + pr_err("%s: setcfg for VDDI GPIO %d failed\n", + __func__, en_vddi); + gpio_free(en_vddi); + usleep_range(10000, 12000); + } + } + + /* Force part out of RESET by releasing XRES#(TP_XRES) */ + if (pdata->rst_gpio) + gpio_set_value(pdata->rst_gpio, 1); + + /* Force part out of RESET by releasing DDI XRES */ + if (pdata->ddi_rst_gpio) + gpio_set_value(pdata->ddi_rst_gpio, 1); + + return rc; +} + + +/******************************************************************************* + * FUNCTION: pt_setup_irq + * + * SUMMARY: Configure the IRQ GPIO used by the TT DUT + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *pdata - pointer to core platform data + * on - flag to setup interrupt process work(PT_MT_IRQ_FREE/) + * *dev - pointer to device + ******************************************************************************/ +int pt_setup_irq(struct pt_core_platform_data *pdata, int on, + struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + unsigned long irq_flags; + int rc = 0; + + if (on == PT_MT_IRQ_REG) { + /* + * When TTDL has direct access to the GPIO the irq_stat function + * will be defined and the gpio_to_irq conversion must be + * performed. e.g. For CHROMEOS this is not the case, the irq is + * passed in directly. + */ + if (pdata->irq_stat) { + /* Initialize IRQ */ + dev_vdbg(dev, "%s: Value Passed to gpio_to_irq =%d\n", + __func__, pdata->irq_gpio); + cd->irq = gpio_to_irq(pdata->irq_gpio); + dev_vdbg(dev, + "%s: Value Returned from gpio_to_irq =%d\n", + __func__, cd->irq); + } + if (cd->irq < 0) + return -EINVAL; + cd->irq_enabled = true; + pt_debug(dev, DL_INFO, "%s: initialize threaded irq=%d\n", + __func__, cd->irq); + if (pdata->level_irq_udelay > 0) + /* use level triggered interrupts */ + irq_flags = IRQF_TRIGGER_LOW; + else + /* use edge triggered interrupts */ + irq_flags = IRQF_TRIGGER_FALLING; + rc = request_threaded_irq(cd->irq, NULL, pt_irq, + irq_flags | IRQF_ONESHOT, dev_name(dev), cd); + if (rc < 0) + pt_debug(dev, DL_ERROR, + "%s: Error, could not request irq\n", __func__); + } else { + disable_irq_nosync(cd->irq); + free_irq(cd->irq, cd); + } + return rc; +} diff --git a/qcom/opensource/touch-drivers/pt/pt_platform.h b/qcom/opensource/touch-drivers/pt/pt_platform.h new file mode 100644 index 0000000000..17b490f759 --- /dev/null +++ b/qcom/opensource/touch-drivers/pt/pt_platform.h @@ -0,0 +1,69 @@ +/* + * pt_platform.h + * Parade TrueTouch(TM) Standard Product Platform Module. + * For use with Parade touchscreen controllers. + * Supported parts include: + * TMA5XX + * TMA448 + * TMA445A + * TT21XXX + * TT31XXX + * TT4XXXX + * TT7XXX + * TC3XXX + * + * Copyright (C) 2015-2020 Parade Technologies + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * 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. + * + * Contact Parade Technologies at www.parade.com + */ + +#ifndef _LINUX_PT_PLATFORM_H +#define _LINUX_PT_PLATFORM_H + +#include "pt_core.h" +#include + +#if defined(CONFIG_TOUCHSCREEN_PARADE) \ + || defined(CONFIG_TOUCHSCREEN_PARADE_MODULE) +extern struct pt_loader_platform_data _pt_loader_platform_data; +extern irqreturn_t pt_irq(int irq, void *handle); + +int pt_xres(struct pt_core_platform_data *pdata, struct device *dev); +int pt_init(struct pt_core_platform_data *pdata, int on, + struct device *dev); +int pt_power(struct pt_core_platform_data *pdata, int on, + struct device *dev, atomic_t *ignore_irq); +#ifdef PT_DETECT_HW +int pt_detect(struct pt_core_platform_data *pdata, + struct device *dev, pt_platform_read read); +#else +#define pt_detect NULL +#endif +int pt_irq_stat(struct pt_core_platform_data *pdata, + struct device *dev); +int pt_setup_power(struct pt_core_platform_data *pdata, int on, + struct device *dev); +int pt_setup_irq(struct pt_core_platform_data *pdata, int on, + struct device *dev); +#else /* !CONFIG_TOUCHSCREEN_PARADE */ +static struct pt_loader_platform_data _pt_loader_platform_data; +#define pt_xres NULL +#define pt_init NULL +#define pt_power NULL +#define pt_irq_stat NULL +#define pt_detect NULL +#define pt_setup_power NULL +#define pt_setup_irq NULL +#endif /* CONFIG_TOUCHSCREEN_PARADE */ + +#endif /* _LINUX_PT_PLATFORM_H */ diff --git a/qcom/opensource/touch-drivers/pt/pt_proximity.c b/qcom/opensource/touch-drivers/pt/pt_proximity.c new file mode 100644 index 0000000000..1ad0006c08 --- /dev/null +++ b/qcom/opensource/touch-drivers/pt/pt_proximity.c @@ -0,0 +1,815 @@ +/* + * pt_proximity.c + * Parade TrueTouch(TM) Standard Product Proximity Module. + * For use with Parade touchscreen controllers. + * Supported parts include: + * TMA5XX + * TMA448 + * TMA445A + * TT21XXX + * TT31XXX + * TT4XXXX + * TT7XXX + * TC3XXX + * + * Copyright (C) 2015-2020 Parade Technologies + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * 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. + * + * Contact Parade Technologies at www.paradetech.com + * + */ + +#include "pt_regs.h" + +#define PT_PROXIMITY_NAME "pt_proximity" + +/* Timeout value in ms. */ +#define PT_PROXIMITY_REQUEST_EXCLUSIVE_TIMEOUT 1000 + +#define PT_PROXIMITY_ON 0 +#define PT_PROXIMITY_OFF 1 + +/******************************************************************************* + * FUNCTION: get_prox_data + * + * SUMMARY: Gets pointer of proximity data from core data structure + * + * RETURN: + * pointer of pt_proximity_data structure in core data structure + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static inline struct pt_proximity_data *get_prox_data(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + return &cd->pd; +} + +/******************************************************************************* + * FUNCTION: pt_report_proximity + * + * SUMMARY: Reports proximity event + * + * PARAMETERS: + * *pd - pointer to proximity data structure + * on - state of proximity(true:on; false:off) + ******************************************************************************/ +static void pt_report_proximity(struct pt_proximity_data *pd, + bool on) +{ + int val = on ? PT_PROXIMITY_ON : PT_PROXIMITY_OFF; + + input_report_abs(pd->input, ABS_DISTANCE, val); + input_sync(pd->input); +} + +/******************************************************************************* + * FUNCTION: pt_get_touch_axis + * + * SUMMARY: Calculates touch axis + * + * PARAMETERS: + * *pd - pointer to proximity data structure + * *axis - pointer to axis calculation result + * size - size in bytes + * max - max value of result + * *xy_data - pointer to input data to be parsed + * bofs - bit offset + ******************************************************************************/ +static void pt_get_touch_axis(struct pt_proximity_data *pd, + int *axis, int size, int max, u8 *xy_data, int bofs) +{ + int nbyte; + int next; + + for (nbyte = 0, *axis = 0, next = 0; nbyte < size; nbyte++) { + pt_debug(pd->dev, DL_INFO, + "%s: *axis=%02X(%d) size=%d max=%08X xy_data=%p xy_data[%d]=%02X(%d) bofs=%d\n", + __func__, *axis, *axis, size, max, xy_data, next, + xy_data[next], xy_data[next], bofs); + *axis = *axis + ((xy_data[next] >> bofs) << (nbyte * 8)); + next++; + } + + *axis &= max - 1; + + pt_debug(pd->dev, DL_INFO, + "%s: *axis=%02X(%d) size=%d max=%08X xy_data=%p xy_data[%d]=%02X(%d)\n", + __func__, *axis, *axis, size, max, xy_data, next, + xy_data[next], xy_data[next]); +} + +/******************************************************************************* + * FUNCTION: pt_get_touch_hdr + * + * SUMMARY: Gets header of touch report + * + * PARAMETERS: + * *pd - pointer to proximity data structure + * *touch - pointer to pt_touch structure + * *xy_mode - pointer to input mode data + ******************************************************************************/ +static void pt_get_touch_hdr(struct pt_proximity_data *pd, + struct pt_touch *touch, u8 *xy_mode) +{ + struct device *dev = pd->dev; + struct pt_sysinfo *si = pd->si; + enum pt_tch_hdr hdr; + + for (hdr = PT_TCH_TIME; hdr < PT_TCH_NUM_HDR; hdr++) { + if (!si->tch_hdr[hdr].report) + continue; + pt_get_touch_axis(pd, &touch->hdr[hdr], + si->tch_hdr[hdr].size, + si->tch_hdr[hdr].max, + xy_mode + si->tch_hdr[hdr].ofs, + si->tch_hdr[hdr].bofs); + pt_debug(dev, DL_INFO, "%s: get %s=%04X(%d)\n", + __func__, pt_tch_hdr_string[hdr], + touch->hdr[hdr], touch->hdr[hdr]); + } +} + +/******************************************************************************* + * FUNCTION: pt_get_touch + * + * SUMMARY: Parse proximity touch event + * + * PARAMETERS: + * *pd - pointer to proximity data structure + * *touch - pointer to touch structure + * xy_data - pointer to touch data + ******************************************************************************/ +static void pt_get_touch(struct pt_proximity_data *pd, + struct pt_touch *touch, u8 *xy_data) +{ + struct device *dev = pd->dev; + struct pt_sysinfo *si = pd->si; + enum pt_tch_abs abs; + + for (abs = PT_TCH_X; abs < PT_TCH_NUM_ABS; abs++) { + if (!si->tch_abs[abs].report) + continue; + pt_get_touch_axis(pd, &touch->abs[abs], + si->tch_abs[abs].size, + si->tch_abs[abs].max, + xy_data + si->tch_abs[abs].ofs, + si->tch_abs[abs].bofs); + pt_debug(dev, DL_INFO, "%s: get %s=%04X(%d)\n", + __func__, pt_tch_abs_string[abs], + touch->abs[abs], touch->abs[abs]); + } + + pt_debug(dev, DL_INFO, "%s: x=%04X(%d) y=%04X(%d)\n", + __func__, touch->abs[PT_TCH_X], touch->abs[PT_TCH_X], + touch->abs[PT_TCH_Y], touch->abs[PT_TCH_Y]); +} + +/******************************************************************************* + * FUNCTION: pt_get_proximity_touch + * + * SUMMARY: Parse and report proximity touch event + * + * PARAMETERS: + * *pd - pointer to proximity data structure + * *touch - pointer to pt_touch structure + ******************************************************************************/ +static void pt_get_proximity_touch(struct pt_proximity_data *pd, + struct pt_touch *tch, int num_cur_tch) +{ + struct pt_sysinfo *si = pd->si; + int i; + + for (i = 0; i < num_cur_tch; i++) { + pt_get_touch(pd, tch, si->xy_data + + (i * si->desc.tch_record_size)); + + /* Check for proximity event */ + if (tch->abs[PT_TCH_O] == PT_OBJ_PROXIMITY) { + if (tch->abs[PT_TCH_E] == PT_EV_TOUCHDOWN) + pt_report_proximity(pd, true); + else if (tch->abs[PT_TCH_E] == PT_EV_LIFTOFF) + pt_report_proximity(pd, false); + break; + } + } +} + +/******************************************************************************* + * FUNCTION: pt_xy_worker + * + * SUMMARY: Read xy_data for all current touches + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *pd - pointer to proximity data structure + ******************************************************************************/ +static int pt_xy_worker(struct pt_proximity_data *pd) +{ + struct device *dev = pd->dev; + struct pt_sysinfo *si = pd->si; + struct pt_touch tch; + u8 num_cur_tch; + + pt_get_touch_hdr(pd, &tch, si->xy_mode + 3); + + num_cur_tch = tch.hdr[PT_TCH_NUM]; + if (num_cur_tch > si->sensing_conf_data.max_tch) { + pt_debug(dev, DL_ERROR, "%s: Num touch err detected (n=%d)\n", + __func__, num_cur_tch); + num_cur_tch = si->sensing_conf_data.max_tch; + } + + if (tch.hdr[PT_TCH_LO]) + pt_debug(dev, DL_WARN, "%s: Large area detected\n", + __func__); + + /* extract xy_data for all currently reported touches */ + pt_debug(dev, DL_INFO, "%s: extract data num_cur_rec=%d\n", + __func__, num_cur_tch); + if (num_cur_tch) + pt_get_proximity_touch(pd, &tch, num_cur_tch); + else + pt_report_proximity(pd, false); + + return 0; +} + +/******************************************************************************* + * FUNCTION: pt_mt_attention + * + * SUMMARY: Wrapper function for pt_xy_worker() that subscribe into the TTDL + * attention list. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int pt_proximity_attention(struct device *dev) +{ + struct pt_proximity_data *pd = get_prox_data(dev); + int rc = 0; + + if (pd->si->xy_mode[2] != pd->si->desc.tch_report_id) + return 0; + + mutex_lock(&pd->prox_lock); + rc = pt_xy_worker(pd); + mutex_unlock(&pd->prox_lock); + if (rc < 0) + pt_debug(dev, DL_ERROR, "%s: xy_worker error r=%d\n", + __func__, rc); + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_startup_attention + * + * SUMMARY: Wrapper function for pt_report_proximity() that subcribe into the + * TTDL attention list. + * + * RETURN: + * 0 = success + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int pt_startup_attention(struct device *dev) +{ + struct pt_proximity_data *pd = get_prox_data(dev); + + mutex_lock(&pd->prox_lock); + pt_report_proximity(pd, false); + mutex_unlock(&pd->prox_lock); + + return 0; +} + +/******************************************************************************* + * FUNCTION: _pt_set_proximity_via_touchmode_enabled + * + * SUMMARY: Enable/Disable proximity via touchmode parameter + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *pd - pointer to proximity data structure + * enable - enable or disable proximity(true:enable; false:disable) + ******************************************************************************/ +static int _pt_set_proximity_via_touchmode_enabled( + struct pt_proximity_data *pd, bool enable) +{ + struct device *dev = pd->dev; + u32 touchmode_enabled; + int rc; + + rc = _pt_request_pip_get_param(dev, 0, + PT_RAM_ID_TOUCHMODE_ENABLED, &touchmode_enabled); + if (rc) + return rc; + + if (enable) + touchmode_enabled |= 0x80; + else + touchmode_enabled &= 0x7F; + + rc = _pt_request_pip_set_param(dev, 0, + PT_RAM_ID_TOUCHMODE_ENABLED, touchmode_enabled, + PT_RAM_ID_TOUCHMODE_ENABLED_SIZE); + + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_set_proximity_via_proximity_enable + * + * SUMMARY: Enable/Disable proximity via proximity parameter + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *pd - pointer to proximity data structure + * enable - enable or disable proximity(true:enable; false:disable) + ******************************************************************************/ +static int _pt_set_proximity_via_proximity_enable( + struct pt_proximity_data *pd, bool enable) +{ + struct device *dev = pd->dev; + u32 proximity_enable; + int rc; + + rc = _pt_request_pip_get_param(dev, 0, + PT_RAM_ID_PROXIMITY_ENABLE, &proximity_enable); + if (rc) + return rc; + + if (enable) + proximity_enable |= 0x01; + else + proximity_enable &= 0xFE; + + rc = _pt_request_pip_set_param(dev, 0, + PT_RAM_ID_PROXIMITY_ENABLE, proximity_enable, + PT_RAM_ID_PROXIMITY_ENABLE_SIZE); + + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_set_proximity + * + * SUMMARY: Set proximity mode via touchmode parameter or proximity parameter. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *pd - pointer to proximity data structure + * enable - enable or disable proximity(true:enable; false:disable) + ******************************************************************************/ +static int _pt_set_proximity(struct pt_proximity_data *pd, + bool enable) +{ + if (!IS_PIP_VER_GE(pd->si, 1, 4)) + return _pt_set_proximity_via_touchmode_enabled(pd, + enable); + + return _pt_set_proximity_via_proximity_enable(pd, enable); +} + +/******************************************************************************* + * FUNCTION: _pt_set_proximity + * + * SUMMARY: Enable proximity mode and subscribe into IRQ and STARTUP TTDL + * attention list. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *pd - pointer to proximity data structure + ******************************************************************************/ +static int _pt_proximity_enable(struct pt_proximity_data *pd) +{ + struct device *dev = pd->dev; + int rc = 0; + + pm_runtime_get_sync(dev); + + rc = pt_request_exclusive(dev, + PT_PROXIMITY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(dev, DL_ERROR, "%s: Error on request exclusive r=%d\n", + __func__, rc); + goto exit; + } + + rc = _pt_set_proximity(pd, true); + if (rc < 0) { + pt_debug(dev, DL_ERROR, "%s: Error on request enable proximity scantype r=%d\n", + __func__, rc); + goto exit_release; + } + + pt_debug(dev, DL_INFO, "%s: setup subscriptions\n", __func__); + + /* set up touch call back */ + _pt_subscribe_attention(dev, PT_ATTEN_IRQ, PT_PROXIMITY_NAME, + pt_proximity_attention, PT_MODE_OPERATIONAL); + + /* set up startup call back */ + _pt_subscribe_attention(dev, PT_ATTEN_STARTUP, + PT_PROXIMITY_NAME, pt_startup_attention, 0); + +exit_release: + pt_release_exclusive(dev); +exit: + return rc; +} + +/******************************************************************************* + * FUNCTION: _pt_proximity_disable + * + * SUMMARY: Disable proximity mode and unsubscribe from IRQ and STARTUP TTDL + * attention list. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *pd - pointer to proximity data structure + ******************************************************************************/ +static int _pt_proximity_disable(struct pt_proximity_data *pd, + bool force) +{ + struct device *dev = pd->dev; + int rc = 0; + + rc = pt_request_exclusive(dev, + PT_PROXIMITY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + pt_debug(dev, DL_ERROR, "%s: Error on request exclusive r=%d\n", + __func__, rc); + goto exit; + } + + rc = _pt_set_proximity(pd, false); + if (rc < 0) { + pt_debug(dev, DL_ERROR, "%s: Error on request disable proximity scan r=%d\n", + __func__, rc); + goto exit_release; + } + +exit_release: + pt_release_exclusive(dev); + +exit: + if (!rc || force) { + _pt_unsubscribe_attention(dev, PT_ATTEN_IRQ, + PT_PROXIMITY_NAME, pt_proximity_attention, + PT_MODE_OPERATIONAL); + + _pt_unsubscribe_attention(dev, PT_ATTEN_STARTUP, + PT_PROXIMITY_NAME, pt_startup_attention, 0); + } + + pm_runtime_put(dev); + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_proximity_enable_show + * + * SUMMARY: Show method for the prox_enable sysfs node that will show the + * enable_count of proximity + * + * RETURN: Size of printed buffer + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to output buffer + ******************************************************************************/ +static ssize_t pt_proximity_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pt_proximity_data *pd = get_prox_data(dev); + int val = 0; + + mutex_lock(&pd->sysfs_lock); + val = pd->enable_count; + mutex_unlock(&pd->sysfs_lock); + + return scnprintf(buf, PT_MAX_PRBUF_SIZE, "%d\n", val); +} + +/******************************************************************************* + * FUNCTION: pt_proximity_enable_store + * + * SUMMARY: The store method for the prox_enable sysfs node that allows to + * enable or disable proxmity mode. + * + * RETURN: Size of passed in buffer + * + * PARAMETERS: + * *dev - pointer to device structure + * *attr - pointer to device attributes + * *buf - pointer to buffer that hold the command parameters + * size - size of buf + ******************************************************************************/ +static ssize_t pt_proximity_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct pt_proximity_data *pd = get_prox_data(dev); + unsigned long value; + int rc; + + rc = kstrtoul(buf, 10, &value); + if (rc < 0 || (value != 0 && value != 1)) { + pt_debug(dev, DL_ERROR, "%s: Invalid value\n", __func__); + return -EINVAL; + } + + mutex_lock(&pd->sysfs_lock); + if (value) { + if (pd->enable_count++) { + pt_debug(dev, DL_WARN, "%s: '%s' already enabled\n", + __func__, pd->input->name); + } else { + rc = _pt_proximity_enable(pd); + if (rc) + pd->enable_count--; + } + } else { + if (--pd->enable_count) { + if (pd->enable_count < 0) { + pt_debug(dev, DL_ERROR, "%s: '%s' unbalanced disable\n", + __func__, pd->input->name); + pd->enable_count = 0; + } + } else { + rc = _pt_proximity_disable(pd, false); + if (rc) + pd->enable_count++; + } + } + mutex_unlock(&pd->sysfs_lock); + + if (rc) + return rc; + + return size; +} + +static DEVICE_ATTR(prox_enable, 0600, + pt_proximity_enable_show, + pt_proximity_enable_store); + +/******************************************************************************* + * FUNCTION: pt_setup_input_device_and_sysfs + * + * SUMMARY: Create sysnode, set event signal capabilities and register input + * device for proximity. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int pt_setup_input_device_and_sysfs(struct device *dev) +{ + struct pt_proximity_data *pd = get_prox_data(dev); + int signal = PT_IGNORE_VALUE; + int i; + int rc; + + rc = device_create_file(dev, &dev_attr_prox_enable); + if (rc) { + pt_debug(dev, DL_ERROR, "%s: Error, could not create enable\n", + __func__); + goto exit; + } + + pt_debug(dev, DL_INFO, "%s: Initialize event signals\n", + __func__); + + __set_bit(EV_ABS, pd->input->evbit); + + /* set event signal capabilities */ + for (i = 0; i < NUM_SIGNALS(pd->pdata->frmwrk); i++) { + signal = PARAM_SIGNAL(pd->pdata->frmwrk, i); + if (signal != PT_IGNORE_VALUE) { + input_set_abs_params(pd->input, signal, + PARAM_MIN(pd->pdata->frmwrk, i), + PARAM_MAX(pd->pdata->frmwrk, i), + PARAM_FUZZ(pd->pdata->frmwrk, i), + PARAM_FLAT(pd->pdata->frmwrk, i)); + } + } + + rc = input_register_device(pd->input); + if (rc) { + pt_debug(dev, DL_ERROR, "%s: Error, failed register input device r=%d\n", + __func__, rc); + goto unregister_enable; + } + + pd->input_device_registered = true; + return rc; + +unregister_enable: + device_remove_file(dev, &dev_attr_prox_enable); +exit: + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_setup_input_attention + * + * SUMMARY: Wrapper function for pt_setup_input_device_and_sysfs() that + * subscribe into TTDL attention list. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +static int pt_setup_input_attention(struct device *dev) +{ + struct pt_proximity_data *pd = get_prox_data(dev); + int rc; + + pd->si = _pt_request_sysinfo(dev); + if (!pd->si) + return -EINVAL; + + rc = pt_setup_input_device_and_sysfs(dev); + if (!rc) + rc = _pt_set_proximity(pd, false); + + _pt_unsubscribe_attention(dev, PT_ATTEN_STARTUP, + PT_PROXIMITY_NAME, pt_setup_input_attention, 0); + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_proximity_probe + * + * SUMMARY: The probe function for proximity input device + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +int pt_proximity_probe(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + struct pt_proximity_data *pd = &cd->pd; + struct pt_platform_data *pdata = dev_get_platdata(dev); + struct pt_proximity_platform_data *prox_pdata; + int rc = 0; + + if (!pdata || !pdata->prox_pdata) { + pt_debug(dev, DL_ERROR, + "%s: Missing platform data\n", __func__); + rc = -ENODEV; + goto error_no_pdata; + } + prox_pdata = pdata->prox_pdata; + + mutex_init(&pd->prox_lock); + mutex_init(&pd->sysfs_lock); + pd->dev = dev; + pd->pdata = prox_pdata; + + /* Create the input device and register it. */ + pt_debug(dev, DL_INFO, + "%s: Create the input device and register it\n", __func__); + pd->input = input_allocate_device(); + if (!pd->input) { + pt_debug(dev, DL_ERROR, "%s: Error, failed to allocate input device\n", + __func__); + rc = -ENODEV; + goto error_alloc_failed; + } else + pd->input_device_allocated = true; + + if (pd->pdata->inp_dev_name) + pd->input->name = pd->pdata->inp_dev_name; + else + pd->input->name = PT_PROXIMITY_NAME; + scnprintf(pd->phys, sizeof(pd->phys), "%s/input%d", dev_name(dev), + cd->phys_num++); + pd->input->phys = pd->phys; + pd->input->dev.parent = pd->dev; + input_set_drvdata(pd->input, pd); + + /* get sysinfo */ + pd->si = _pt_request_sysinfo(dev); + + if (pd->si) { + rc = pt_setup_input_device_and_sysfs(dev); + if (rc) + goto error_init_input; + + rc = _pt_set_proximity(pd, false); + } else { + pt_debug(dev, DL_ERROR, "%s: Fail get sysinfo pointer from core p=%p\n", + __func__, pd->si); + _pt_subscribe_attention(dev, PT_ATTEN_STARTUP, + PT_PROXIMITY_NAME, pt_setup_input_attention, + 0); + } + + return 0; + +error_init_input: + input_free_device(pd->input); + pd->input_device_allocated = false; +error_alloc_failed: +error_no_pdata: + pt_debug(dev, DL_ERROR, "%s failed.\n", __func__); + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_proximity_release + * + * SUMMARY: The release function for proximity input device + * + * RETURN: + * 0 = success + * + * PARAMETERS: + * *dev - pointer to device structure + ******************************************************************************/ +int pt_proximity_release(struct device *dev) +{ + struct pt_proximity_data *pd; + + /* Ensure valid pointers before de-referencing them */ + if (dev) + pd = get_prox_data(dev); + else + return 0; + + /* + * Second call this function may cause kernel panic if probe fail. + * Use input_device_registered & input_device_allocated variable to + * avoid unregister or free unavailable devive. + */ + if (pd && pd->input_device_registered) { + /* Disable proximity sensing */ + pd->input_device_registered = false; + mutex_lock(&pd->sysfs_lock); + if (pd->enable_count) + _pt_proximity_disable(pd, true); + mutex_unlock(&pd->sysfs_lock); + device_remove_file(dev, &dev_attr_prox_enable); + input_unregister_device(pd->input); + /* Unregistering device will free the device too */ + pd->input_device_allocated = false; + } else if (pd && pd->input_device_allocated) { + pd->input_device_allocated = false; + input_free_device(pd->input); + _pt_unsubscribe_attention(dev, PT_ATTEN_STARTUP, + PT_PROXIMITY_NAME, pt_setup_input_attention, + 0); + } + + return 0; +} diff --git a/qcom/opensource/touch-drivers/pt/pt_regs.h b/qcom/opensource/touch-drivers/pt/pt_regs.h new file mode 100644 index 0000000000..7e53bc5261 --- /dev/null +++ b/qcom/opensource/touch-drivers/pt/pt_regs.h @@ -0,0 +1,1769 @@ +/* + * pt_regs.h + * Parade TrueTouch(TM) Standard Product Registers. + * For use with Parade touchscreen controllers. + * Supported parts include: + * TMA5XX + * TMA448 + * TMA445A + * TT21XXX + * TT31XXX + * TT4XXXX + * TT7XXX + * TC3XXX + * + * Copyright (C) 2015-2020 Parade Technologies + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * 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. + * + * Contact Parade Technologies at www.paradetech.com + * + */ + +#ifndef _PT_REGS_H +#define _PT_REGS_H + +#define PT_PANEL_ID_DEFAULT 0 + +#define PT_MAX_PATH_SIZE 128 +#define PT_PIP2_BIN_FILE_PATH "/data/ttdl/pt_fw.bin" +#define PT_SUPPRESS_AUTO_BL 0 +#define PT_ALLOW_AUTO_BL 1 + +#define PT_PIP2_MAX_FILE_SIZE 0x18000 +#define PT_PIP2_FILE_SECTOR_SIZE 0x1000 + +#ifndef CONFIG_DRM +#define CONFIG_DRM +#endif + +#if IS_ENABLED(CONFIG_QCOM_PANEL_EVENT_NOTIFIER) +#define CONFIG_PANEL_NOTIFIER +#endif + +#include +#include +#include +#ifdef CONFIG_HAS_EARLYSUSPEND +#include +#elif defined(CONFIG_DRM) || defined(CONFIG_PANEL_NOTIFIER) +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pt_core.h" + +#include +#include +#include +#include +#include +#include + +#define STATUS_SUCCESS 0 +#define STATUS_FAIL -1 + +#define PT_FW_FILE_PREFIX "tt_fw" +#define PT_FW_FILE_SUFFIX ".bin" +#define PT_FW_FILE_NAME "tt_fw.bin" +#define PT_FW_RAM_FILE_NAME "tt_fw_ram.bin" +/* Enable special TTDL features */ +#ifndef TTHE_TUNER_SUPPORT +#define TTHE_TUNER_SUPPORT +#endif + +#ifndef TTDL_DIAGNOSTICS +#define TTDL_DIAGNOSTICS +#endif + +#ifndef EASYWAKE_TSG6 +#define EASYWAKE_TSG6 +#endif + +#ifdef TTHE_TUNER_SUPPORT +#define PT_TTHE_TUNER_FILE_NAME "tthe_tuner" +#endif +#define PT_MAX_PRBUF_SIZE PIPE_BUF +#define PT_PR_TRUNCATED " truncated..." + +#define PT_DEFAULT_CORE_ID "pt_core0" +#define PT_MAX_NUM_CORE_DEVS 5 +#define PT_IRQ_ASSERTED_VALUE 0 + +#ifdef PT_ENABLE_MAX_ELEN +#define PT_MAX_ELEN 100 +#endif + +/* Power Management Macros Enablement */ + +#ifndef CONFIG_PM +#define CONFIG_PM +#endif + +#ifndef CONFIG_PM_RUNTIME +#define CONFIG_PM_RUNTIME +#endif + +#ifndef CONFIG_PM_SLEEP +#define CONFIG_PM_SLEEP +#endif + +/* Pin Control Macro Enablement */ +#ifndef PT_PINCTRL_EN +#define PT_PINCTRL_EN +#endif + +#ifndef TT7XXX_EXAMPLE +#define TT7XXX_EXAMPLE +#endif + +#define TOUCH_TO_WAKE_POWER_FEATURE_WORK_AROUND + +/* + * The largest PIP message is the PIP2 FILE_WRITE which has: + * 2 byte register + * 4 byte header + * 256 byte payload + * 2 byte CRC + */ +#define PT_MAX_PIP2_MSG_SIZE 264 +#define PT_MAX_PIP1_MSG_SIZE 255 + +/* + * The minimun size of PIP2 packet includes: + * 2 byte length + * 1 byte sequence + * 1 byte command ID + * 2 byte CRC + */ +#define PT_MIN_PIP2_PACKET_SIZE 6 + +static const u8 pt_data_block_security_key[] = { + 0xA5, 0x01, 0x02, 0x03, 0xFF, 0xFE, 0xFD, 0x5A +}; + +/* Enum for debug reporting levels */ +enum PT_DEBUG_LEVEL { + DL_QUIET = 0, + DL_ERROR = 1, + DL_WARN = 2, + DL_INFO = 3, + DL_DEBUG = 4, + DL_MAX +}; +#define PT_INITIAL_DEBUG_LEVEL DL_WARN + +/* Startup DUT enum status bitmask */ +enum PT_STARTUP_STATUS { + STARTUP_STATUS_START = 0, + STARTUP_STATUS_BL_RESET_SENTINEL = 0x001, + STARTUP_STATUS_FW_RESET_SENTINEL = 0x002, + STARTUP_STATUS_GET_DESC = 0x004, + STARTUP_STATUS_FW_OUT_OF_BOOT = 0x008, + STARTUP_STATUS_GET_RPT_DESC = 0x010, + STARTUP_STATUS_GET_SYS_INFO = 0x020, + STARTUP_STATUS_GET_CFG_CRC = 0x040, + STARTUP_STATUS_RESTORE_PARM = 0x080, + STARTUP_STATUS_COMPLETE = 0x100, + STARTUP_STATUS_FULL = 0x1FF +}; + +#define PT_INITIAL_SHOW_TIME_STAMP 0 + +/* + * Print out all debug prints that are less then or equal to set level. + */ +#define pt_debug(dev, dlevel, format, arg...) \ + do { \ + struct pt_core_data *cd_tmp = dev_get_drvdata(dev);\ + if (cd_tmp->debug_level >= dlevel) {\ + if (dlevel == DL_ERROR)\ + dev_err(dev, "[%d] "format, dlevel, ##arg);\ + else\ + dev_info(dev, "[%d] "format, dlevel, ##arg);\ + } \ + } while (0) + +enum PT_PIP_REPORT_ID { + PT_PIP_INVALID_REPORT_ID = 0x00, + PT_PIP_TOUCH_REPORT_ID = 0x01, + PT_PIP_TOUCH_REPORT_WIN8_ID = 0x02, + PT_PIP_CAPSENSE_BTN_REPORT_ID = 0x03, + PT_PIP_WAKEUP_REPORT_ID = 0x04, + PT_PIP_NOISE_METRIC_REPORT_ID = 0x05, + PT_PIP_PUSH_BUTTON_REPORT_ID = 0x06, + PT_PIP_SELFCAP_INPUT_REPORT_ID = 0x0D, + PT_PIP_TRACKING_HEATMAP_REPORT_ID = 0x0E, + PT_PIP_SENSOR_DATA_REPORT_ID = 0x0F, + PT_PIP_NON_HID_RESPONSE_ID = 0x1F, + PT_PIP_NON_HID_COMMAND_ID = 0x2F, + PT_PIP_BL_RESPONSE_REPORT_ID = 0x30, + PT_PIP_BL_COMMAND_REPORT_ID = 0x40 +}; + +enum PT_HID_REPORT_ID { + PT_HID_FINGER_REPORT_ID = 0x01, + PT_HID_PEN_REPORT_ID = 0x02 +}; + + +/* HID IDs and commands */ +#define HID_VENDOR_ID 0x04B4 +#define HID_APP_PRODUCT_ID 0xC101 +#define HID_VERSION 0x0100 +#define HID_APP_REPORT_ID 0xF7 +#define HID_BL_REPORT_ID 0xFF +#define HID_RESPONSE_REPORT_ID 0xF0 +#define HID_POWER_ON 0x0 +#define HID_POWER_SLEEP 0x1 +#define HID_POWER_STANDBY 0x2 + +/* PIP1 offsets and masks */ +#define PIP1_RESP_REPORT_ID_OFFSET 2 +#define PIP1_RESP_COMMAND_ID_OFFSET 4 +#define PIP1_RESP_COMMAND_ID_MASK 0x7F +#define PIP1_CMD_COMMAND_ID_OFFSET 6 +#define PIP1_CMD_COMMAND_ID_MASK 0x7F + +#define PIP1_SYSINFO_TTDATA_OFFSET 5 +#define PIP1_SYSINFO_SENSING_OFFSET 33 +#define PIP1_SYSINFO_BTN_OFFSET 48 +#define PIP1_SYSINFO_BTN_MASK 0xFF +#define PIP1_SYSINFO_MAX_BTN 8 + +/* Timeouts in ms */ +#define PT_PTSBC_INIT_WATCHDOG_TIMEOUT 20000 +#define PT_REQUEST_EXCLUSIVE_TIMEOUT 8000 +#define PT_WATCHDOG_TIMEOUT 2000 +#define PT_FW_EXIT_BOOT_MODE_TIMEOUT 1000 +#define PT_BL_WAIT_FOR_SENTINEL 500 +#define PT_REQUEST_ENUM_TIMEOUT 4000 +#define PT_GET_HID_DESCRIPTOR_TIMEOUT 500 +#define PT_HID_CMD_DEFAULT_TIMEOUT 500 +#define PT_PIP_CMD_DEFAULT_TIMEOUT 2000 +#define PT_PIP1_CMD_DEFAULT_TIMEOUT 1000 +#define PT_PIP1_START_BOOTLOADER_TIMEOUT 2000 +#define PT_PIP1_CMD_GET_SYSINFO_TIMEOUT 500 +#define PT_PIP1_CMD_CALIBRATE_IDAC_TIMEOUT 5000 +#define PT_PIP1_CMD_CALIBRATE_EXT_TIMEOUT 5000 +#define PT_PIP1_CMD_WRITE_CONF_BLOCK_TIMEOUT 400 +#define PT_PIP1_CMD_RUN_SELF_TEST_TIMEOUT 10000 +#define PT_PIP1_CMD_INITIATE_BL_TIMEOUT 20000 +#define PT_PIP1_CMD_PROGRAM_AND_VERIFY_TIMEOUT 400 +#define PT_PIP2_CMD_FILE_ERASE_TIMEOUT 3000 + +/* Max counts */ +#define PT_WATCHDOG_RETRY_COUNT 30 +#define PT_BUS_READ_INPUT_RETRY_COUNT 2 + +#define PT_FLUSH_BUS_BASED_ON_LEN 0 +#define PT_FLUSH_BUS_FULL_256_READ 1 + +/* maximum number of concurrent tracks */ +#define TOUCH_REPORT_SIZE 10 +#define TOUCH_INPUT_HEADER_SIZE 7 +#define TOUCH_COUNT_BYTE_OFFSET 5 +#define BTN_REPORT_SIZE 9 +#define BTN_INPUT_HEADER_SIZE 5 +#define SENSOR_REPORT_SIZE 150 +#define SENSOR_HEADER_SIZE 4 + +/* helpers */ +#define GET_NUM_TOUCHES(x) ((x) & 0x1F) +#define IS_LARGE_AREA(x) ((x) & 0x20) +#define IS_BAD_PKT(x) ((x) & 0x20) +#define IS_TMO(t) ((t) == 0) +#define HI_BYTE(x) (u8)(((x) >> 8) & 0xFF) +#define LOW_BYTE(x) (u8)((x) & 0xFF) +#define SET_CMD_LOW(byte, bits) \ + ((byte) = (((byte) & 0xF0) | ((bits) & 0x0F))) +#define SET_CMD_HIGH(byte, bits)\ + ((byte) = (((byte) & 0x0F) | ((bits) & 0xF0))) + +#define GET_MASK(length) \ + ((1 << length) - 1) +#define GET_FIELD(name, length, shift) \ + ((name >> shift) & GET_MASK(length)) + +#define _base(x) ((x >= '0' && x <= '9') ? '0' : \ + (x >= 'a' && x <= 'f') ? 'a' - 10 : \ + (x >= 'A' && x <= 'F') ? 'A' - 10 : \ + '\255') +#define HEXOF(x) (x - _base(x)) + +#define IS_EASY_WAKE_CONFIGURED(x) \ + ((x) != 0 && (x) != 0xFF) + +#define IS_PIP_VER_GE(p, maj, min) \ + ((p)->ttdata.pip_ver_major > (maj) ? \ + 1 : \ + (((p)->ttdata.pip_ver_major == (maj) ? \ + ((p)->ttdata.pip_ver_minor >= (min) ? \ + 1 : 0) : \ + 0))) +#define IS_PIP_VER_EQ(p, maj, min) \ + ((p)->ttdata.pip_ver_major == (maj) ? \ + ((p)->ttdata.pip_ver_minor == (min) ? \ + 1 : \ + 0 : \ + 0)) +#define PT_PANEL_ID_BITMASK 0x0000000C +#define PT_PANEL_ID_SHIFT 2 + +#define TTDL_PTVIRTDUT_SUPPORT 1 + +/* DUT Debug commands (dut_debug sysfs) */ +#define PT_DUT_DBG_HID_RESET 50 +#define PT_DUT_DBG_HID_GET_REPORT 51 /* depricated */ +#define PT_DUT_DBG_HID_SET_REPORT 52 /* depricated */ +#define PT_DUT_DBG_HID_SET_POWER_ON 53 +#define PT_DUT_DBG_HID_SET_POWER_SLEEP 54 +#define PT_DUT_DBG_HID_SET_POWER_STANDBY 55 +#define PT_DUT_DBG_PIP_SOFT_RESET 97 +#define PT_DUT_DBG_RESET 98 +#define PT_DUT_DBG_PIP_NULL 100 +#define PT_DUT_DBG_PIP_ENTER_BL 101 +#define PT_DUT_DBG_HID_SYSINFO 102 +#define PT_DUT_DBG_PIP_SUSPEND_SCAN 103 +#define PT_DUT_DBG_PIP_RESUME_SCAN 104 +#define PT_DUT_DBG_HID_DESC 109 + +/* Driver Debug commands (drv_debug sysfs) */ +#define PT_DRV_DBG_SUSPEND 4 +#define PT_DRV_DBG_RESUME 5 +#define PT_DRV_DBG_STOP_WD 105 +#define PT_DRV_DBG_START_WD 106 +#define PT_DRV_DBG_TTHE_TUNER_EXIT 107 +#define PT_DRV_DBG_TTHE_BUF_CLEAN 108 +#define PT_DRV_DBG_CLEAR_PARM_LIST 110 +#define PT_DRV_DBG_FORCE_BUS_READ 111 +#define PT_DRV_DBG_CLEAR_CAL_DATA 112 + +/* + * Commands that require additional parameters + * will be in the 200 range. Commands that do not + * require additional parameters remain below 200. + */ +#define PT_DRV_DBG_REPORT_LEVEL 200 +#define PT_DRV_DBG_WATCHDOG_INTERVAL 201 +#define PT_DRV_DBG_SHOW_TIMESTAMP 202 +#define PT_DRV_DBG_SET_GENERATION 210 + +#ifdef TTDL_DIAGNOSTICS +#define PT_DRV_DBG_FLUSH_BUS 204 /* deprecated */ +#define PT_DRV_DBG_SETUP_PWR 205 +#define PT_DRV_DBG_GET_PUT_SYNC 206 +#define PT_DRV_DBG_SET_PIP2_LAUNCH_APP 207 /* deprecated */ +#define PT_DRV_DBG_SET_TT_DATA 208 +#define PT_DRV_DBG_SET_RUN_FW_PIN 209 /* deprecated */ +#define PT_DRV_DBG_SET_BRIDGE_MODE 211 +#define PT_DRV_DBG_SET_I2C_ADDRESS 212 +#define PT_DRV_DBG_SET_FLASHLESS_DUT 213 +#define PT_DRV_DBG_SET_FORCE_SEQ 214 +#define PT_DRV_DBG_BL_WITH_NO_INT 215 +#define PT_DRV_DBG_CAL_CACHE_IN_HOST 216 +#define PT_DRV_DBG_MULTI_CHIP 217 +#define PT_DRV_DBG_SET_PANEL_ID_TYPE 218 +#define PT_DRV_DBG_PIP_TIMEOUT 219 +#define PT_DRV_DBG_TTHE_HID_USB_FORMAT 220 +#ifdef TTDL_PTVIRTDUT_SUPPORT +#define PT_DRV_DBG_SET_HW_DETECT 298 +#define PT_DRV_DBG_VIRTUAL_I2C_DUT 299 +#endif /* TTDL_PTVIRTDUT_SUPPORT */ + +/* TTDL Built In Self Test selection bit masks */ +#define PT_TTDL_BIST_BUS_TEST 0x01 +#define PT_TTDL_BIST_IRQ_TEST 0x02 +#define PT_TTDL_BIST_TP_XRES_TEST 0x04 +#define PT_TTDL_BIST_SLAVE_BUS_TEST 0x08 +#define PT_TTDL_BIST_SLAVE_IRQ_TEST 0x10 +#define PT_TTDL_BIST_SLAVE_XRES_TEST 0x20 + +#define SLAVE_DETECT_MASK 0x01 + +#define VIRT_MAX_IRQ_RELEASE_TIME_US 500000 +#endif /* TTDL DIAGNOSTICS */ + +/* Recognized usages */ +/* undef them first for possible redefinition in Linux */ +#undef HID_DI_PRESSURE +#undef HID_DI_TIP +#undef HID_DI_CONTACTID +#undef HID_DI_CONTACTCOUNT +#undef HID_DI_SCANTIME +#define HID_DI_PRESSURE 0x000d0030 +#define HID_DI_TIP 0x000d0042 +#define HID_DI_CONTACTID 0x000d0051 +#define HID_DI_CONTACTCOUNT 0x000d0054 +#define HID_DI_SCANTIME 0x000d0056 + +/* Parade vendor specific usages */ +#define HID_PT_UNDEFINED 0xff010000 +#define HID_PT_BOOTLOADER 0xff010001 +#define HID_PT_TOUCHAPPLICATION 0xff010002 +#define HID_PT_BUTTONS 0xff010020 +#define HID_PT_GENERICITEM 0xff010030 +#define HID_PT_LARGEOBJECT 0xff010040 +#define HID_PT_NOISEEFFECTS 0xff010041 +#define HID_PT_REPORTCOUNTER 0xff010042 +#define HID_PT_TOUCHTYPE 0xff010060 +#define HID_PT_EVENTID 0xff010061 +#define HID_PT_MAJORAXISLENGTH 0xff010062 +#define HID_PT_MINORAXISLENGTH 0xff010063 +#define HID_PT_ORIENTATION 0xff010064 +#define HID_PT_BUTTONSIGNAL 0xff010065 +#define HID_PT_MAJOR_CONTACT_AXIS_LENGTH 0xff010066 +#define HID_PT_MINOR_CONTACT_AXIS_LENGTH 0xff010067 +#define HID_PT_TCH_COL_USAGE_PG 0x000D0022 +#define HID_PT_BTN_COL_USAGE_PG 0xFF010020 + +#define PANEL_ID_NOT_ENABLED 0xFF + +#ifdef EASYWAKE_TSG6 +#define GESTURE_DOUBLE_TAP (1) +#define GESTURE_TWO_FINGERS_SLIDE (2) +#define GESTURE_TOUCH_DETECTED (3) +#define GESTURE_PUSH_BUTTON (4) +#define GESTURE_SINGLE_SLIDE_DE_TX (5) +#define GESTURE_SINGLE_SLIDE_IN_TX (6) +#define GESTURE_SINGLE_SLIDE_DE_RX (7) +#define GESTURE_SINGLE_SLIDE_IN_RX (8) +#endif + +/* FW RAM parameters */ +#define PT_RAM_ID_TOUCHMODE_ENABLED 0x02 +#define PT_RAM_ID_PROXIMITY_ENABLE 0x20 +#define PT_RAM_ID_TOUCHMODE_ENABLED_SIZE 1 +#define PT_RAM_ID_PROXIMITY_ENABLE_SIZE 1 + +/* abs signal capabilities offsets in the frameworks array */ +enum pt_sig_caps { + PT_SIGNAL_OST, + PT_MIN_OST, + PT_MAX_OST, + PT_FUZZ_OST, + PT_FLAT_OST, + PT_NUM_ABS_SET /* number of signal capability fields */ +}; + +/* helpers */ +#define NUM_SIGNALS(frmwrk) ((frmwrk)->size / PT_NUM_ABS_SET) +#define PARAM(frmwrk, sig_ost, cap_ost) \ + ((frmwrk)->abs[((sig_ost) * PT_NUM_ABS_SET) + (cap_ost)]) + +#define PARAM_SIGNAL(frmwrk, sig_ost) PARAM(frmwrk, sig_ost, PT_SIGNAL_OST) +#define PARAM_MIN(frmwrk, sig_ost) PARAM(frmwrk, sig_ost, PT_MIN_OST) +#define PARAM_MAX(frmwrk, sig_ost) PARAM(frmwrk, sig_ost, PT_MAX_OST) +#define PARAM_FUZZ(frmwrk, sig_ost) PARAM(frmwrk, sig_ost, PT_FUZZ_OST) +#define PARAM_FLAT(frmwrk, sig_ost) PARAM(frmwrk, sig_ost, PT_FLAT_OST) + +/* abs axis signal offsets in the framworks array */ +enum pt_sig_ost { + PT_ABS_X_OST, + PT_ABS_Y_OST, + PT_ABS_P_OST, + PT_ABS_W_OST, + PT_ABS_ID_OST, + PT_ABS_MAJ_OST, + PT_ABS_MIN_OST, + PT_ABS_OR_OST, + PT_ABS_TOOL_OST, + PT_ABS_D_OST, + PT_NUM_ABS_OST /* number of abs signals */ +}; + +enum hid_command { + HID_CMD_RESERVED = 0x0, + HID_CMD_RESET = 0x1, + HID_CMD_GET_REPORT = 0x2, + HID_CMD_SET_REPORT = 0x3, + HID_CMD_GET_IDLE = 0x4, + HID_CMD_SET_IDLE = 0x5, + HID_CMD_GET_PROTOCOL = 0x6, + HID_CMD_SET_PROTOCOL = 0x7, + HID_CMD_SET_POWER = 0x8, + HID_CMD_VENDOR = 0xE, +}; + +enum PIP1_cmd_type { + PIP1_CMD_TYPE_FW, + PIP1_CMD_TYPE_BL, +}; + +/* PIP BL cmd IDs and input for dut_debug sysfs */ +enum pip1_bl_cmd_id { + PIP1_BL_CMD_ID_VERIFY_APP_INTEGRITY = 0x31, /* 49 */ + PIP1_BL_CMD_ID_GET_INFO = 0x38, /* 56 */ + PIP1_BL_CMD_ID_PROGRAM_AND_VERIFY = 0x39, /* 57 */ + PIP1_BL_CMD_ID_LAUNCH_APP = 0x3B, /* 59 */ + PIP1_BL_CMD_ID_GET_PANEL_ID = 0x3E, /* 62 */ + PIP1_BL_CMD_ID_INITIATE_BL = 0x48, /* 72 */ + PIP1_BL_CMD_ID_LAST, +}; +#define PIP1_BL_SOP 0x1 +#define PIP1_BL_EOP 0x17 + +/* PIP1 Command/Response IDs */ +enum PIP1_CMD_ID { + PIP1_CMD_ID_NULL = 0x00, + PIP1_CMD_ID_START_BOOTLOADER = 0x01, + PIP1_CMD_ID_GET_SYSINFO = 0x02, + PIP1_CMD_ID_SUSPEND_SCANNING = 0x03, + PIP1_CMD_ID_RESUME_SCANNING = 0x04, + PIP1_CMD_ID_GET_PARAM = 0x05, + PIP1_CMD_ID_SET_PARAM = 0x06, + PIP1_CMD_ID_GET_NOISE_METRICS = 0x07, + PIP1_CMD_ID_RESERVED = 0x08, + PIP1_CMD_ID_ENTER_EASYWAKE_STATE = 0x09, + PIP1_CMD_ID_VERIFY_CONFIG_BLOCK_CRC = 0x20, + PIP1_CMD_ID_GET_CONFIG_ROW_SIZE = 0x21, + PIP1_CMD_ID_READ_DATA_BLOCK = 0x22, + PIP1_CMD_ID_WRITE_DATA_BLOCK = 0x23, + PIP1_CMD_ID_GET_DATA_STRUCTURE = 0x24, + PIP1_CMD_ID_LOAD_SELF_TEST_PARAM = 0x25, + PIP1_CMD_ID_RUN_SELF_TEST = 0x26, + PIP1_CMD_ID_GET_SELF_TEST_RESULT = 0x27, + PIP1_CMD_ID_CALIBRATE_IDACS = 0x28, + PIP1_CMD_ID_INITIALIZE_BASELINES = 0x29, + PIP1_CMD_ID_EXEC_PANEL_SCAN = 0x2A, + PIP1_CMD_ID_RETRIEVE_PANEL_SCAN = 0x2B, + PIP1_CMD_ID_START_SENSOR_DATA_MODE = 0x2C, + PIP1_CMD_ID_STOP_SENSOR_DATA_MODE = 0x2D, + PIP1_CMD_ID_START_TRACKING_HEATMAP_MODE = 0x2E, + PIP1_CMD_ID_START_SELF_CAP_RPT_MODE = 0x2F, + PIP1_CMD_ID_CALIBRATE_DEVICE_EXTENDED = 0x30, + PIP1_CMD_ID_INT_PIN_OVERRIDE = 0x40, + PIP1_CMD_ID_STORE_PANEL_SCAN = 0x60, + PIP1_CMD_ID_PROCESS_PANEL_SCAN = 0x61, + PIP1_CMD_ID_DISCARD_INPUT_REPORT, + PIP1_CMD_ID_LAST, + PIP1_CMD_ID_USER_CMD, +}; + +/* PIP2 Command/Response data and structures */ +enum PIP2_CMD_ID { + PIP2_CMD_ID_PING = 0x00, + PIP2_CMD_ID_STATUS = 0x01, + PIP2_CMD_ID_CTRL = 0x02, + PIP2_CMD_ID_CONFIG = 0x03, + PIP2_CMD_ID_RESERVE = 0x04, + PIP2_CMD_ID_CLEAR = 0x05, + PIP2_CMD_ID_RESET = 0x06, + PIP2_CMD_ID_VERSION = 0x07, + PIP2_CMD_ID_FILE_OPEN = 0x10, + PIP2_CMD_ID_FILE_CLOSE = 0x11, + PIP2_CMD_ID_FILE_READ = 0x12, + PIP2_CMD_ID_FILE_WRITE = 0x13, + PIP2_CMD_ID_FILE_IOCTL = 0x14, + PIP2_CMD_ID_FLASH_INFO = 0x15, + PIP2_CMD_ID_EXECUTE = 0x16, + PIP2_CMD_ID_GET_LAST_ERRNO = 0x17, + PIP2_CMD_ID_EXIT_HOST_MODE = 0x18, + PIP2_CMD_ID_READ_GPIO = 0x19, + PIP2_CMD_EXECUTE_SCAN = 0x21, + PIP2_CMD_SET_PARAMETER = 0x40, + PIP2_CMD_GET_PARAMETER = 0x41, + PIP2_CMD_SET_DDI_REG = 0x42, + PIP2_CMD_GET_DDI_REG = 0x43, + PIP2_CMD_ID_END = 0x7F +}; + +enum PIP2_STATUS_EXEC_RUNNING { + PIP2_STATUS_BOOT_EXEC = 0x00, + PIP2_STATUS_APP_EXEC = 0x01, +}; + +/* FW_SYS_MODE_UNDEFINED must be 1 greater than FW_SYS_MODE_MAX */ +enum PIP2_FW_SYSTEM_MODE { + FW_SYS_MODE_BOOT = 0x00, + FW_SYS_MODE_SCANNING = 0x01, + FW_SYS_MODE_DEEP_SLEEP = 0x02, + FW_SYS_MODE_TEST = 0x03, + FW_SYS_MODE_DEEP_STANDBY = 0x04, + FW_SYS_MODE_MAX = FW_SYS_MODE_DEEP_STANDBY, + FW_SYS_MODE_UNDEFINED = FW_SYS_MODE_MAX + 1, +}; + +/* PIP2 Command/Response data and structures */ +enum PIP2_FILE_ID { + PIP2_RAM_FILE = 0x00, + PIP2_FW_FILE = 0x01, + PIP2_CONFIG_FILE = 0x02, + PIP2_FILE_3 = 0x03, + PIP2_FILE_4 = 0x04, + PIP2_FILE_5 = 0x05, + PIP2_FILE_6 = 0x06, + PIP2_FILE_7 = 0x07, + PIP2_FILE_MAX = PIP2_FILE_7, +}; + +/* Optimize packet sizes per Allwinner H3 bus drivers */ +#define PIP2_FILE_WRITE_LEN_PER_PACKET 245 +#define PIP2_BL_I2C_FILE_WRITE_LEN_PER_PACKET 245 +#define PIP2_BL_SPI_FILE_WRITE_LEN_PER_PACKET 256 + +enum DUT_GENERATION { + DUT_UNKNOWN = 0x00, + DUT_PIP1_ONLY = 0x01, + DUT_PIP2_CAPABLE = 0x02, +}; + +enum PIP2_RSP_ERR { + PIP2_RSP_ERR_NONE = 0x00, + PIP2_RSP_ERR_BUSY = 0x01, + PIP2_RSP_ERR_INIT_FAILURE = 0x02, + PIP2_RSP_ERR_ALREADY_OPEN = 0x03, + PIP2_RSP_ERR_NOT_OPEN = 0x04, + PIP2_RSP_ERR_IO_FAILURE = 0x05, + PIP2_RSP_ERR_UNKNOWN_IOCTL = 0x06, + PIP2_RSP_ERR_BAD_ADDRESS = 0x07, + PIP2_RSP_ERR_BAD_FILE = 0x08, + PIP2_RSP_ERR_END_OF_FILE = 0x09, + PIP2_RSP_ERR_TOO_MANY_FILES = 0x0A, + PIP2_RSP_ERR_TIMEOUT = 0x0B, + PIP2_RSP_ERR_ABORTED = 0x0C, + PIP2_RSP_ERR_CRC = 0x0D, + PIP2_RSP_ERR_UNKNOWN_REC_TYPE = 0x0E, + PIP2_RSP_ERR_BAD_FRAME = 0x0F, + PIP2_RSP_ERR_NO_PERMISSION = 0x10, + PIP2_RSP_ERR_UNKNOWN_COMMAND = 0x11, + PIP2_RSP_ERR_INVALID_PARAM = 0x12, + PIP2_RSP_ERR_IO_ALREADY_ACTIVE = 0x13, + PIP2_RSP_ERR_SHUTDOWN = 0x14, + PIP2_RSP_ERR_INVALID_IMAGE = 0x15, + PIP2_RSP_ERR_UNKNOWN_REGISTER = 0x16, + PIP2_RSP_ERR_BAD_LENGTH = 0x17, + PIP2_RSP_ERR_TRIM_FAILURE = 0x18, +}; + +/* + * Extra bytes for PIP2 = 4 + 2: + * 4 byte header - (len_lsb, len_msb, report ID, Tag, Sequence) + * 2 byte footer - (crc_lsb, crc_msb) + */ +#define PIP2_CMD_COMMAND_ID_OFFSET 5 +#define PIP2_CMD_COMMAND_ID_MASK 0x7F +#define PIP2_RESP_COMMAND_ID_OFFSET 3 +#define PIP2_RESP_SEQUENCE_OFFSET 2 +#define PIP2_RESP_SEQUENCE_MASK 0x0F +#define PIP2_RESP_REPORT_ID_OFFSET 3 +#define PIP2_RESP_STATUS_OFFSET 4 +#define PIP2_RESP_BODY_OFFSET 5 +#define PIP2_CRC_SIZE 2 +#define PIP2_LEN_FIELD_SIZE 2 +#define PIP2_VERSION_CHIP_REV_OFFSET 14 +#define PIP2_EXTRA_BYTES_NUM (PIP2_RESP_STATUS_OFFSET + PIP2_CRC_SIZE) + +/* File IOCTL commands */ +#define PIP2_FILE_IOCTL_CODE_ERASE_FILE 0 +#define PIP2_FILE_IOCTL_CODE_SEEK_POINTER 1 +#define PIP2_FILE_IOCTL_CODE_AES_CONTROL 2 +#define PIP2_FILE_IOCTL_CODE_FILE_STATS 3 +#define PIP2_FILE_IOCTL_CODE_FILE_CRC 4 + +struct pip2_cmd_structure { + u8 reg[2]; + u16 len; + u8 id; + u8 seq; + u8 *data; + u8 crc[2]; /* MSB:crc[0], LSB:crc[1] */ +}; + +struct pip2_cmd_response_structure { + u8 id; + u16 response_len; + u32 response_time_min; + u32 response_time_max; +}; + +enum pip1_bl_status { + ERROR_SUCCESS, + ERROR_KEY, + ERROR_VERIFICATION, + ERROR_LENGTH, + ERROR_DATA, + ERROR_COMMAND, + ERROR_CRC = 8, + ERROR_FLASH_ARRAY, + ERROR_FLASH_ROW, + ERROR_FLASH_PROTECTION, + ERROR_UNKNOWN = 15, + ERROR_INVALID, +}; + +enum pt_mode { + PT_MODE_UNKNOWN = 0, + PT_MODE_BOOTLOADER = 1, + PT_MODE_OPERATIONAL = 2, + PT_MODE_IGNORE = 255, +}; + +enum PT_ENTER_BL_RESULT { + PT_ENTER_BL_PASS = 0, + PT_ENTER_BL_ERROR = 1, + PT_ENTER_BL_RESET_FAIL = 2, + PT_ENTER_BL_HID_START_BL_FAIL = 3, + PT_ENTER_BL_CONFIRM_FAIL = 4, + PT_ENTER_BL_GET_FLASH_INFO_FAIL = 5, +}; + +enum TTDL_EXTENDED_ERROR_CODES { + EX_ERR_FREAD = 400, + EX_ERR_FWRITE = 401, + EX_ERR_FOPEN = 402, + EX_ERR_FCLOSE = 403, + EX_ERR_FLEN = 404, +}; + +enum pt_cmd_status { + PT_CMD_STATUS_SUCCESS = 0, + PT_CMD_STATUS_FAILURE = 1, +}; + +enum { + PT_IC_GRPNUM_RESERVED, + PT_IC_GRPNUM_CMD_REGS, + PT_IC_GRPNUM_TCH_REP, + PT_IC_GRPNUM_DATA_REC, + PT_IC_GRPNUM_TEST_REC, + PT_IC_GRPNUM_PCFG_REC, + PT_IC_GRPNUM_TCH_PARM_VAL, + PT_IC_GRPNUM_TCH_PARM_SIZE, + PT_IC_GRPNUM_RESERVED1, + PT_IC_GRPNUM_RESERVED2, + PT_IC_GRPNUM_OPCFG_REC, + PT_IC_GRPNUM_DDATA_REC, + PT_IC_GRPNUM_MDATA_REC, + PT_IC_GRPNUM_TEST_REGS, + PT_IC_GRPNUM_BTN_KEYS, + PT_IC_GRPNUM_TTHE_REGS, + PT_IC_GRPNUM_SENSING_CONF, + PT_IC_GRPNUM_NUM, +}; + +enum pt_event_id { + PT_EV_NO_EVENT, + PT_EV_TOUCHDOWN, + PT_EV_MOVE, /* significant displacement (> act dist) */ + PT_EV_LIFTOFF, /* record reports last position */ +}; + +enum pt_object_id { + PT_OBJ_STANDARD_FINGER, + PT_OBJ_PROXIMITY, + PT_OBJ_STYLUS, + PT_OBJ_GLOVE, +}; + +enum pt_self_test_result { + PT_ST_RESULT_PASS = 0, + PT_ST_RESULT_FAIL = 1, + PT_ST_RESULT_ABORTED = 2, + PT_ST_RESULT_PARAM_ERR = 3, + PT_ST_RESULT_CFG_ERR = 4, + PT_ST_RESULT_CAL_ERR = 5, + PT_ST_RESULT_DDI_STATE_ERR = 6, + PT_ST_RESULT_HOST_MUST_INTERPRET = 0xFF, +}; +#define PT_ST_PRINT_RESULTS true +#define PT_ST_NOPRINT false +#define PT_ST_GET_RESULTS true +#define PT_ST_DONT_GET_RESULTS false + +/* + * Maximum number of parameters for the fw_self_test sysfs (255 - 12 + 2) + * 255 - Max PIP message size + * 12 - Header size for PIP message 0x25 (Load Self Test Parameters) + * 2 - Additional parameters for fw_self_test for test_id and format + */ +#define PT_FW_SELF_TEST_MAX_PARM 245 + +enum pt_self_test_id { + PT_ST_ID_NULL = 0, + PT_ST_ID_BIST = 1, + PT_ST_ID_SHORTS = 2, + PT_ST_ID_OPENS = 3, + PT_ST_ID_AUTOSHORTS = 4, + PT_ST_ID_CM_PANEL = 5, + PT_ST_ID_CP_PANEL = 6, + PT_ST_ID_CM_BUTTON = 7, + PT_ST_ID_CP_BUTTON = 8, + PT_ST_ID_FORCE = 9, + PT_ST_ID_OPENS_HIZ = 10, + PT_ST_ID_OPENS_GND = 11, + PT_ST_ID_CP_LFT = 12, + PT_ST_ID_SC_NOISE = 13, + PT_ST_ID_LFT_NOISE = 14, + PT_ST_ID_CP_CHIP_ROUTE_PARASITIC_CAP = 15, + PT_ST_ID_NORMALIZED_RAW_CNT_PANEL = 16, + PT_ST_ID_NORMALIZED_RAW_CNT_LFT = 17, + PT_ST_ID_INVALID = 255 +}; + +enum pt_scan_state { + PT_SCAN_STATE_UNKNOWN = 0, + PT_SCAN_STATE_ACTIVE = 1, + PT_SCAN_STATE_INACTIVE = 2, +}; + +#define PT_CAL_DATA_MAX_SIZE 2048 +#define PT_CAL_DATA_ROW_SIZE 128 +#define PT_WAFER_LOT_SIZE 5 +#define PT_UID_SIZE 12 + +enum pt_cal_data_actions { + PT_CAL_DATA_SAVE = 0, + PT_CAL_DATA_RESTORE = 1, + PT_CAL_DATA_CLEAR = 2, + PT_CAL_DATA_INFO = 3 +}; + +enum pt_feature_enable_state { + PT_FEATURE_DISABLE = 0, + PT_FEATURE_ENABLE = 1 +}; + +#define PT_NUM_MFGID 8 +/* System Information interface definitions */ +struct pt_ttdata_dev { + u8 pip_ver_major; + u8 pip_ver_minor; + __le16 fw_pid; + u8 fw_ver_major; + u8 fw_ver_minor; + __le32 revctrl; + __le16 fw_ver_conf; + u8 bl_ver_major; + u8 bl_ver_minor; + __le16 jtag_si_id_l; + __le16 jtag_si_id_h; + u8 mfg_id[PT_NUM_MFGID]; + __le16 post_code; +} __packed; + +/* Struct to cast over PIP2 VERSION response */ +struct pt_pip2_version_full { + u8 status_code; + u8 pip2_version_lsb; + u8 pip2_version_msb; + u8 fw_version_lsb; + u8 fw_version_msb; + u8 bl_version_lsb; + u8 bl_version_msb; + __le16 chip_rev; + __le16 chip_id; + u8 uid[PT_UID_SIZE]; +} __packed; + +struct pt_pip2_version { + u8 status_code; + u8 pip2_version_lsb; + u8 pip2_version_msb; + u8 bl_version_lsb; + u8 bl_version_msb; + u8 fw_version_lsb; + u8 fw_version_msb; + __le16 chip_id; + __le16 chip_rev; +} __packed; + +struct pt_sensing_conf_data_dev { + u8 electrodes_x; + u8 electrodes_y; + __le16 len_x; + __le16 len_y; + __le16 res_x; + __le16 res_y; + __le16 max_z; + u8 origin_x; + u8 origin_y; + u8 panel_id; + u8 btn; + u8 scan_mode; + u8 max_num_of_tch_per_refresh_cycle; +} __packed; + +struct pt_ttdata { + u8 pip_ver_major; + u8 pip_ver_minor; + u8 bl_ver_major; + u8 bl_ver_minor; + u8 fw_ver_major; + u8 fw_ver_minor; + u16 fw_pid; + u16 fw_ver_conf; + u16 post_code; + u32 revctrl; + u16 jtag_id_l; + u16 jtag_id_h; + u8 mfg_id[PT_NUM_MFGID]; + u16 chip_rev; + u16 chip_id; + u8 uid[PT_UID_SIZE]; +}; + +struct pt_sensing_conf_data { + u16 res_x; + u16 res_y; + u16 max_z; + u16 len_x; + u16 len_y; + u8 electrodes_x; + u8 electrodes_y; + u8 origin_x; + u8 origin_y; + u8 panel_id; + u8 btn; + u8 scan_mode; + u8 max_tch; + u8 rx_num; + u8 tx_num; +}; + +enum pt_tch_abs { /* for ordering within the extracted touch data array */ + PT_TCH_X, /* X */ + PT_TCH_Y, /* Y */ + PT_TCH_P, /* P (Z) */ + PT_TCH_T, /* TOUCH ID */ + PT_TCH_E, /* EVENT ID */ + PT_TCH_O, /* OBJECT ID */ + PT_TCH_TIP, /* OBJECT ID */ + PT_TCH_MAJ, /* TOUCH_MAJOR */ + PT_TCH_MIN, /* TOUCH_MINOR */ + PT_TCH_OR, /* ORIENTATION */ + PT_TCH_NUM_ABS, +}; + +enum pt_tch_hdr { + PT_TCH_TIME, /* SCAN TIME */ + PT_TCH_NUM, /* NUMBER OF RECORDS */ + PT_TCH_LO, /* LARGE OBJECT */ + PT_TCH_NOISE, /* NOISE EFFECT */ + PT_TCH_COUNTER, /* REPORT_COUNTER */ + PT_TCH_NUM_HDR, +}; + +static const char * const pt_tch_abs_string[] = { + [PT_TCH_X] = "X", + [PT_TCH_Y] = "Y", + [PT_TCH_P] = "P", + [PT_TCH_T] = "T", + [PT_TCH_E] = "E", + [PT_TCH_O] = "O", + [PT_TCH_TIP] = "TIP", + [PT_TCH_MAJ] = "MAJ", + [PT_TCH_MIN] = "MIN", + [PT_TCH_OR] = "OR", + [PT_TCH_NUM_ABS] = "INVALID", +}; + +static const char * const pt_tch_hdr_string[] = { + [PT_TCH_TIME] = "SCAN TIME", + [PT_TCH_NUM] = "NUMBER OF RECORDS", + [PT_TCH_LO] = "LARGE OBJECT", + [PT_TCH_NOISE] = "NOISE EFFECT", + [PT_TCH_COUNTER] = "REPORT_COUNTER", + [PT_TCH_NUM_HDR] = "INVALID", +}; + +static const int pt_tch_abs_field_map[] = { + [PT_TCH_X] = 0x00010030 /* HID_GD_X */, + [PT_TCH_Y] = 0x00010031 /* HID_GD_Y */, + [PT_TCH_P] = HID_DI_PRESSURE, + [PT_TCH_T] = HID_DI_CONTACTID, + [PT_TCH_E] = HID_PT_EVENTID, + [PT_TCH_O] = HID_PT_TOUCHTYPE, + [PT_TCH_TIP] = HID_DI_TIP, + [PT_TCH_MAJ] = HID_PT_MAJORAXISLENGTH, + [PT_TCH_MIN] = HID_PT_MINORAXISLENGTH, + [PT_TCH_OR] = HID_PT_ORIENTATION, + [PT_TCH_NUM_ABS] = 0, +}; + +static const int pt_tch_hdr_field_map[] = { + [PT_TCH_TIME] = HID_DI_SCANTIME, + [PT_TCH_NUM] = HID_DI_CONTACTCOUNT, + [PT_TCH_LO] = HID_PT_LARGEOBJECT, + [PT_TCH_NOISE] = HID_PT_NOISEEFFECTS, + [PT_TCH_COUNTER] = HID_PT_REPORTCOUNTER, + [PT_TCH_NUM_HDR] = 0, +}; + +#define PT_TOUCH_ID_MAX 32 +#define PT_NUM_EXT_TCH_FIELDS 3 + +struct pt_tch_abs_params { + size_t ofs; /* abs byte offset */ + size_t size; /* size in bits */ + size_t min; /* min value */ + size_t max; /* max value */ + size_t bofs; /* bit offset */ + u8 report; +}; + +struct pt_touch { + int hdr[PT_TCH_NUM_HDR]; + int abs[PT_TCH_NUM_ABS]; +}; + +/* button to keycode support */ +#define PT_BITS_PER_BTN 1 +#define PT_NUM_BTN_EVENT_ID ((1 << PT_BITS_PER_BTN) - 1) + +enum pt_btn_state { + PT_BTN_RELEASED = 0, + PT_BTN_PRESSED = 1, + PT_BTN_NUM_STATE +}; + +struct pt_btn { + bool enabled; + int state; /* PT_BTN_PRESSED, PT_BTN_RELEASED */ + int key_code; +}; + +enum pt_ic_ebid { + PT_TCH_PARM_EBID = 0x00, + PT_MDATA_EBID = 0x01, + PT_DDATA_EBID = 0x02, + PT_CAL_EBID = 0xF0, +}; + +/* ttconfig block */ +#define PT_TTCONFIG_VERSION_OFFSET 8 +#define PT_TTCONFIG_VERSION_SIZE 2 +#define PT_TTCONFIG_VERSION_ROW 0 + +struct pt_ttconfig { + u16 version; + u16 crc; +}; + +struct pt_report_desc_data { + u16 tch_report_id; + u16 tch_record_size; + u16 tch_header_size; + u16 btn_report_id; +}; + +struct pt_sysinfo { + bool ready; + struct pt_ttdata ttdata; + struct pt_sensing_conf_data sensing_conf_data; + struct pt_report_desc_data desc; + int num_btns; + struct pt_btn *btn; + struct pt_ttconfig ttconfig; + struct pt_tch_abs_params tch_hdr[PT_TCH_NUM_HDR]; + struct pt_tch_abs_params tch_abs[PT_TCH_NUM_ABS]; + u8 *xy_mode; + u8 *xy_data; +}; + +struct pt_bl_info { + bool ready; + u16 chip_id; +}; + +enum pt_atten_type { + PT_ATTEN_IRQ, + PT_ATTEN_STARTUP, + PT_ATTEN_EXCLUSIVE, + PT_ATTEN_WAKE, + PT_ATTEN_LOADER, + PT_ATTEN_SUSPEND, + PT_ATTEN_RESUME, + PT_ATTEN_CANCEL_LOADER, + PT_ATTEN_NUM_ATTEN, +}; + +enum pt_sleep_state { + SS_SLEEP_NONE, + SS_SLEEP_OFF, + SS_SLEEP_ON, + SS_SLEEPING, + SS_WAKING, + SS_EASY_WAKING_ON, + SS_EASY_WAKING_OFF, +}; + +enum pt_fb_state { + FB_NONE, + FB_ON, + FB_OFF, +}; + +enum pt_startup_state { + STARTUP_NONE, + STARTUP_QUEUED, + STARTUP_RUNNING, + STARTUP_ILLEGAL, +}; + +struct pt_hid_desc { + __le16 hid_desc_len; + u8 packet_id; + u8 reserved_byte; + __le16 bcd_version; + __le16 report_desc_len; + __le16 report_desc_register; + __le16 input_register; + __le16 max_input_len; + __le16 output_register; + __le16 max_output_len; + __le16 command_register; + __le16 data_register; + __le16 vendor_id; + __le16 product_id; + __le16 version_id; + u8 reserved[4]; +} __packed; + +struct pt_hid_core { + u16 hid_vendor_id; + u16 hid_product_id; + __le16 hid_desc_register; + u16 hid_report_desc_len; + u16 hid_max_input_len; + u16 hid_max_output_len; +}; + +#define PT_HID_MAX_REPORTS 8 +#define PT_HID_MAX_FIELDS 128 +#define PT_HID_MAX_COLLECTIONS 3 +#define PT_HID_MAX_NESTED_COLLECTIONS PT_HID_MAX_COLLECTIONS + +/* Max input is for ASCII representation of hex characters */ +#define PT_MAX_INPUT (PT_MAX_PIP2_MSG_SIZE * 2) +#define PT_PIP_1P7_EMPTY_BUF 0xFF00 + +enum pt_module_id { + PT_MODULE_MT, + PT_MODULE_BTN, + PT_MODULE_PROX, + PT_MODULE_LAST, +}; + +struct pt_mt_data; +struct pt_mt_function { + int (*mt_release)(struct device *dev); + int (*mt_probe)(struct device *dev, struct pt_mt_data *md); + void (*report_slot_liftoff)(struct pt_mt_data *md, int max_slots); + void (*input_sync)(struct input_dev *input); + void (*input_report)(struct input_dev *input, int sig, int t, int type); + void (*final_sync)(struct input_dev *input, int max_slots, + int mt_sync_count, unsigned long *ids); + int (*input_register_device)(struct input_dev *input, int max_slots); +}; + +struct pt_mt_data { + struct device *dev; + struct pt_mt_platform_data *pdata; + struct pt_sysinfo *si; + struct input_dev *input; + struct pt_mt_function mt_function; + struct mutex mt_lock; + bool is_suspended; + bool input_device_registered; + bool input_device_allocated; + char phys[NAME_MAX]; + int num_prv_rec; + int or_min; + int or_max; + int t_min; + int t_max; +}; + +struct pt_btn_data { + struct device *dev; + struct pt_btn_platform_data *pdata; + struct pt_sysinfo *si; + struct input_dev *input; + struct mutex btn_lock; + bool is_suspended; + bool input_device_registered; + bool input_device_allocated; + char phys[NAME_MAX]; +}; + +struct pt_proximity_data { + struct device *dev; + struct pt_proximity_platform_data *pdata; + struct pt_sysinfo *si; + struct input_dev *input; + struct mutex prox_lock; + struct mutex sysfs_lock; + int enable_count; + bool input_device_registered; + bool input_device_allocated; + char phys[NAME_MAX]; +}; + +enum pt_calibrate_idacs_sensing_mode { + PT_CI_SM_MUTCAP_FINE, + PT_CI_SM_MUTCAP_BUTTON, + PT_CI_SM_SELFCAP, +}; + +enum pt_initialize_baselines_sensing_mode { + PT_IB_SM_MUTCAP = 1, + PT_IB_SM_BUTTON = 2, + PT_IB_SM_SELFCAP = 4, + PT_IB_SM_BALANCED = 8, +}; + +/* parameters for extended calibrate command(0x30)*/ +struct pt_cal_ext_data { + u8 mode; + u8 data0; + u8 data1; + u8 data2; +} __packed; +#define PT_CAL_EXT_MODE_UNDEFINED 0xFF + +#define PT_BIN_FILE_MIN_HDR_LENGTH 14 +#define PT_BIN_FILE_MAX_HDR_LENGTH 18 +struct pt_bin_file_hdr { + u8 length; + u16 ttpid; + u8 fw_major; + u8 fw_minor; + u32 fw_rev_ctrl; + u32 fw_crc; + u16 si_rev; + u16 si_id; + u16 config_ver; + u32 hex_file_size; +}; + +struct pt_core_nonhid_cmd { + int (*start_bl)(struct device *dev, int protect); + int (*suspend_scanning)(struct device *dev, int protect); + int (*resume_scanning)(struct device *dev, int protect); + int (*get_param)(struct device *dev, int protect, u8 param_id, + u32 *value); + int (*set_param)(struct device *dev, int protect, u8 param_id, + u32 value, u8 size); + int (*verify_cfg_block_crc)(struct device *dev, int protect, + u8 ebid, u8 *status, u16 *calculated_crc, + u16 *stored_crc); + int (*get_config_row_size)(struct device *dev, int protect, + u16 *row_size); + int (*get_data_structure)(struct device *dev, int protect, + u16 read_offset, u16 read_length, u8 data_id, + u8 *status, u8 *data_format, u16 *actual_read_len, + u8 *data); + int (*run_selftest)(struct device *dev, int protect, u8 test_id, + u8 write_idacs_to_flash, u8 *status, u8 *summary_result, + u8 *results_available); + int (*get_selftest_result)(struct device *dev, int protect, + u16 read_offset, u16 read_length, u8 test_id, u8 *status, + u16 *actual_read_len, u8 *data); + int (*load_self_test_param)(struct device *dev, int protect, + u8 self_test_id, u16 load_offset, u16 load_length, + u8 *parameters, u8 *status, u8 *ret_test_id, u16 *act_load_len); + int (*calibrate_idacs)(struct device *dev, int protect, u8 mode, + u8 *status); + int (*calibrate_ext)(struct device *dev, + int protect, struct pt_cal_ext_data *cal_data, u8 *status); + int (*initialize_baselines)(struct device *dev, int protect, + u8 test_id, u8 *status); + int (*exec_panel_scan)(struct device *dev, int protect, u8 scan_type); + int (*retrieve_panel_scan)(struct device *dev, int protect, + u16 read_offset, u16 read_count, u8 data_id, + u8 *response, u8 *config, u16 *actual_read_len, + u8 *read_buf); + int (*read_data_block)(struct device *dev, u16 row_number, + u16 length, u8 ebid, u16 *actual_read_len, + u8 *read_buf, u16 read_buf_size, u16 *crc); + int (*write_data_block)(struct device *dev, u16 row_number, + u16 write_length, u8 ebid, u8 *write_buf, + u8 *security_key, u16 *actual_write_len); + int (*user_cmd)(struct device *dev, int protect, u16 read_len, + u8 *read_buf, u16 write_len, u8 *write_buf, + u16 *actual_read_len); + int (*get_bl_info)(struct device *dev, int protect, u8 *return_data); + int (*initiate_bl)(struct device *dev, int protect, u16 key_size, + u8 *key_buf, u16 row_size, u8 *metadata_row_buf); + int (*launch_app)(struct device *dev, int protect); + int (*prog_and_verify)(struct device *dev, int protect, u16 data_len, + u8 *data_buf); + int (*verify_app_integrity)(struct device *dev, int protect, + u8 *result); + int (*get_panel_id)(struct device *dev, int protect, u8 *panel_id); + int (*pip2_send_cmd)(struct device *dev, int protect, + u8 id, u8 *data, u16 report_body_len, u8 *read_buf, + u16 *actual_read_len); + int (*pip2_send_cmd_no_int)(struct device *dev, int protect, + u8 id, u8 *data, u16 report_body_len, u8 *read_buf, + u16 *actual_read_len); + int (*get_bl_pip2_version)(struct device *dev); + int (*pip2_file_open)(struct device *dev, u8 file_no); + int (*pip2_file_close)(struct device *dev, u8 file_no); + int (*pip2_file_erase)(struct device *dev, u8 file_no, int *status); + int (*read_us_file)(struct device *dev, u8 *file_path, u8 *buf, + int *size); + int (*pip2_file_read)(struct device *dev, u8 file_no, + u16 num_bytes, u8 *read_buf); + int (*pip2_file_seek_offset)(struct device *dev, u8 file_no, + u32 read_offset, u32 write_offset); + int (*pip2_file_get_stats)(struct device *dev, u8 file_no, + u32 *address, u32 *file_size); + int (*pip2_file_crc)(struct device *dev, u8 file_no, + u32 offset, u32 length, u8 *read_buf); + int (*manage_cal_data)(struct device *dev, u8 action, u16 *size, + unsigned short *crc); + unsigned short (*calc_crc)(unsigned char *q, int len); +}; + +typedef int (*pt_atten_func) (struct device *); + +struct pt_core_commands { + int (*subscribe_attention)(struct device *dev, + enum pt_atten_type type, char *id, + pt_atten_func func, int flags); + int (*unsubscribe_attention)(struct device *dev, + enum pt_atten_type type, char *id, + pt_atten_func func, int flags); + int (*request_exclusive)(struct device *dev, int timeout_ms); + int (*release_exclusive)(struct device *dev); + int (*request_reset)(struct device *dev, int protect); + int (*request_pip2_launch_app)(struct device *dev, int protect); + int (*request_enum)(struct device *dev, bool wait); + struct pt_sysinfo * (*request_sysinfo)(struct device *dev); + struct pt_loader_platform_data + *(*request_loader_pdata)(struct device *dev); + int (*request_stop_wd)(struct device *dev); + int (*request_start_wd)(struct device *dev); + int (*request_get_mode)(struct device *dev, int protect, u8 *mode); + int (*request_pip2_get_mode_sysmode)(struct device *dev, int protect, + u8 *mode, u8 *sys_mode); + int (*request_active_pip_prot)(struct device *dev, int protect, + u8 *pip_version_major, u8 *pip_version_minor); + int (*request_enable_scan_type)(struct device *dev, u8 scan_type); + int (*request_disable_scan_type)(struct device *dev, u8 scan_type); + int (*request_pip2_enter_bl)(struct device *dev, u8 *start_mode, + int *result); + int (*request_pip2_bin_hdr)(struct device *dev, + struct pt_bin_file_hdr *hdr); + int (*request_dut_generation)(struct device *dev); + int (*request_hw_version)(struct device *dev, char *hw_version); + int (*parse_sysfs_input)(struct device *dev, + const char *buf, size_t buf_size, + u32 *out_buf, size_t out_buf_size); +#ifdef TTHE_TUNER_SUPPORT + int (*request_tthe_print)(struct device *dev, u8 *buf, int buf_len, + const u8 *data_name); +#endif +#ifdef TTDL_DIAGNOSTICS + void (*request_toggle_err_gpio)(struct device *dev, u8 type); +#endif + struct pt_core_nonhid_cmd *nonhid_cmd; + int (*request_get_fw_mode)(struct device *dev, int protect, + u8 *sys_mode, u8 *mode); +}; + +enum core_command_protected_status { + PT_CORE_CMD_UNPROTECTED = 0, + PT_CORE_CMD_PROTECTED = 1 +}; + +enum pt_err_gpio_type { + PT_ERR_GPIO_NONE = 0, + PT_ERR_GPIO_I2C_TRANS = 1, + PT_ERR_GPIO_IRQ_STUCK = 2, + PT_ERR_GPIO_EXCLUSIVE_ACCESS = 3, + PT_ERR_GPIO_EMPTY_PACKET = 4, + PT_ERR_GPIO_BL_RETRY_PACKET = 5, + PT_ERR_GPIO_MAX_TYPE = PT_ERR_GPIO_BL_RETRY_PACKET, +}; + +struct pt_features { + uint8_t easywake; + uint8_t noise_metric; + uint8_t tracking_heatmap; + uint8_t sensor_data; +}; + +#if defined(CONFIG_PM_SLEEP) && defined(CONFIG_PM_RUNTIME) +#if (KERNEL_VERSION(3, 3, 0) > LINUX_VERSION_CODE) +#define NEED_SUSPEND_NOTIFIER +#endif /* CONFIG_PM_SLEEP && CONFIG_PM_RUNTIME */ +#endif /* LINUX_VERSION_CODE */ + +struct pt_module { + struct list_head node; + char *name; + int (*probe)(struct device *dev, void **data); + void (*release)(struct device *dev, void *data); +}; + +struct pt_bus_ops { + u16 bustype; + int (*read_default)(struct device *dev, void *buf, int size); + int (*read_default_nosize)(struct device *dev, u8 *buf, u32 max); + int (*write_read_specific)(struct device *dev, u16 write_len, + u8 *write_buf, u8 *read_buf); +}; + +struct pt_core_data { + struct pinctrl *ts_pinctrl; + struct pinctrl_state *pinctrl_state_active; + struct pinctrl_state *pinctrl_state_suspend; + struct pinctrl_state *pinctrl_state_release; + + struct regulator *vdd; + struct regulator *vcc_i2c; + + struct list_head node; + struct list_head module_list; /* List of probed modules */ + char core_id[20]; + struct device *dev; + struct workqueue_struct *pt_workqueue; + struct work_struct resume_offload_work; + struct work_struct suspend_offload_work; + struct work_struct suspend_work; + struct work_struct resume_work; + + struct list_head atten_list[PT_ATTEN_NUM_ATTEN]; + struct list_head param_list; + struct mutex module_list_lock; + struct mutex system_lock; + struct mutex sysfs_lock; + struct mutex ttdl_restart_lock; + struct mutex firmware_class_lock; + enum pt_mode mode; + spinlock_t spinlock; + struct pt_mt_data md; + struct pt_btn_data bd; + struct pt_proximity_data pd; + int phys_num; + int pip_cmd_timeout; + int pip_cmd_timeout_default; + void *pt_dynamic_data[PT_MODULE_LAST]; + struct pt_platform_data *pdata; + struct pt_core_platform_data *cpdata; + const struct pt_bus_ops *bus_ops; + wait_queue_head_t wait_q; + enum pt_sleep_state sleep_state; + enum pt_startup_state startup_state; + int irq; + bool irq_enabled; + bool irq_wake; + bool irq_disabled; + bool hw_detected; + u8 easy_wakeup_gesture; +#ifdef EASYWAKE_TSG6 + u8 gesture_id; + u8 gesture_data_length; + u8 gesture_data[80]; +#endif + bool wait_until_wake; + u8 pid_for_loader; + char hw_version[13]; +#ifdef NEED_SUSPEND_NOTIFIER + /* + * This notifier is used to receive suspend prepare events + * When device is PM runtime suspended, pm_generic_suspend() + * does not call our PM suspend callback for kernels with + * version less than 3.3.0. + */ + struct notifier_block pm_notifier; +#endif + struct pt_sysinfo sysinfo; + struct pt_bl_info bl_info; + void *exclusive_dev; + int exclusive_waits; + struct timer_list watchdog_timer; + struct work_struct watchdog_work; + struct work_struct enum_work; + struct work_struct ttdl_restart_work; + u16 startup_retry_count; + struct pt_hid_core hid_core; + int hid_cmd_state; + int hid_reset_cmd_state; /* reset can happen any time */ + struct pt_hid_desc hid_desc; + struct pt_features features; +#define PT_PREALLOCATED_CMD_BUFFER 32 + u8 cmd_buf[PT_PREALLOCATED_CMD_BUFFER]; + u8 input_buf[PT_MAX_INPUT]; + u8 response_buf[PT_MAX_INPUT]; + u8 cmd_rsp_buf[PT_MAX_INPUT]; + u16 cmd_rsp_buf_len; + int raw_cmd_status; +#ifdef CONFIG_HAS_EARLYSUSPEND + struct early_suspend es; +#elif defined(CONFIG_PANEL_NOTIFIER) + struct panel_event_notifier_entry *entry; + enum pt_fb_state fb_state; +#elif defined(CONFIG_FB) || defined(CONFIG_DRM) || defined(CONFIG_PANEL_NOTIFIER) + struct notifier_block fb_notifier; + enum pt_fb_state fb_state; +#endif +#ifdef TTHE_TUNER_SUPPORT + struct dentry *tthe_debugfs; + u8 *tthe_buf; + u32 tthe_buf_len; + u32 tthe_buf_size; + struct mutex tthe_lock; + u8 tthe_exit; +#endif + u8 debug_level; + u8 watchdog_enabled; + bool watchdog_force_stop; + u32 watchdog_interval; + u8 show_timestamp; + u32 startup_status; + u8 pip2_cmd_tag_seq; + u8 pip2_prot_active; + u8 pip2_send_user_cmd; + u8 get_param_id; + bool bl_pip_ver_ready; + bool app_pip_ver_ready; + u8 core_probe_complete; + u8 active_dut_generation; + bool set_dut_generation; + u8 fw_system_mode; + u8 flashless_dut; + u8 bl_with_no_int; + u8 cal_cache_in_host; + u8 multi_chip; + u8 tthe_hid_usb_format; + u8 flashless_auto_bl; + u8 pip2_us_file_path[PT_MAX_PATH_SIZE]; + bool fw_updating; + bool fw_sys_mode_in_standby_state; +#ifdef TTDL_PTVIRTDUT_SUPPORT + u8 route_bus_virt_dut; +#endif /* TTDL_PTVIRTDUT_SUPPORT */ + u8 panel_id_support; +#ifdef TTDL_DIAGNOSTICS + u8 t_refresh_active; + u8 flush_bus_type; + u8 ttdl_bist_select; + u8 force_pip2_seq; + u16 ping_test_size; + u16 pip2_crc_error_count; + u16 t_refresh_count; + u16 t_refresh_total; + u16 wd_xres_count; + u32 watchdog_count; + u32 watchdog_irq_stuck_count; + u32 watchdog_failed_access_count; + u32 bus_transmit_error_count; + u32 irq_count; + u32 bl_retry_packet_count; + u32 file_erase_timeout_count; + unsigned long t_refresh_time; + u16 err_gpio; + u16 err_gpio_type; + bool show_tt_data; + bool bridge_mode; + bool hw_detect_enabled; +#endif + bool quick_boot; + bool drv_debug_suspend; + bool touch_offload; +}; + +struct gd_sensor { + int32_t cm_min; + int32_t cm_max; + int32_t cm_ave; + int32_t cm_min_exclude_edge; + int32_t cm_max_exclude_edge; + int32_t cm_ave_exclude_edge; + int32_t gradient_val; +}; + +#ifdef TTHE_TUNER_SUPPORT +#define PT_CMD_RET_PANEL_IN_DATA_OFFSET 0 +#define PT_CMD_RET_PANEL_ELMNT_SZ_MASK 0x07 +#define PT_CMD_RET_PANEL_HDR 0x0A +#define PT_CMD_RET_PANEL_ELMNT_SZ_MAX 0x2 + +enum scan_data_type_list { + PT_MUT_RAW, + PT_MUT_BASE, + PT_MUT_DIFF, + PT_SELF_RAW, + PT_SELF_BASE, + PT_SELF_DIFF, + PT_BAL_RAW, + PT_BAL_BASE, + PT_BAL_DIFF, +}; +#endif + +static inline int pt_adap_read_default(struct pt_core_data *cd, + void *buf, int size) +{ + return cd->bus_ops->read_default(cd->dev, buf, size); +} + +static inline int pt_adap_read_default_nosize(struct pt_core_data *cd, + void *buf, int max) +{ + return cd->bus_ops->read_default_nosize(cd->dev, buf, max); +} + +static inline int pt_adap_write_read_specific(struct pt_core_data *cd, + u16 write_len, u8 *write_buf, u8 *read_buf) +{ + return cd->bus_ops->write_read_specific(cd->dev, write_len, write_buf, + read_buf); +} + +static inline void *pt_get_dynamic_data(struct device *dev, int id) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + return cd->pt_dynamic_data[id]; +} + +int request_exclusive(struct pt_core_data *cd, void *ownptr, + int timeout_ms); +int release_exclusive(struct pt_core_data *cd, void *ownptr); +int _pt_request_pip_get_param(struct device *dev, + int protect, u8 param_id, u32 *value); +int _pt_request_pip_set_param(struct device *dev, + int protect, u8 param_id, u32 value, u8 size); + +static inline int pt_request_exclusive(struct device *dev, int timeout_ms) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + return request_exclusive(cd, dev, timeout_ms); +} + +static inline int pt_release_exclusive(struct device *dev) +{ + struct pt_core_data *cd = dev_get_drvdata(dev); + + return release_exclusive(cd, dev); +} + +static inline int pt_request_nonhid_get_param(struct device *dev, + int protect, u8 param_id, u32 *value) +{ + return _pt_request_pip_get_param(dev, protect, param_id, + value); +} + +static inline int pt_request_nonhid_set_param(struct device *dev, + int protect, u8 param_id, u32 value, u8 size) +{ + return _pt_request_pip_set_param(dev, protect, param_id, + value, size); +} + +void pt_pr_buf(struct device *dev, u8 debug_level, u8 *buf, + u16 buf_len, const char *data_name); + +#ifdef CONFIG_TOUCHSCREEN_PARADE_DEVICETREE_SUPPORT +int pt_devtree_create_and_get_pdata(struct device *adap_dev); +int pt_devtree_clean_pdata(struct device *adap_dev); +#else +static inline int pt_devtree_create_and_get_pdata(struct device *adap_dev) +{ + return 0; +} + +static inline int pt_devtree_clean_pdata(struct device *adap_dev) +{ + return 0; +} +#endif + +int pt_probe(const struct pt_bus_ops *ops, struct device *dev, + u16 irq, size_t xfer_buf_size); +int pt_release(struct pt_core_data *cd); + +struct pt_core_commands *pt_get_commands(void); +struct pt_core_data *pt_get_core_data(char *id); + + +int pt_mt_release(struct device *dev); +int pt_mt_probe(struct device *dev); + +#ifdef CONFIG_TOUCHSCREEN_PARADE_BUTTON +int pt_btn_probe(struct device *dev); +int pt_btn_release(struct device *dev); +#else +static inline int pt_btn_probe(struct device *dev) { return 0; } +static inline int pt_btn_release(struct device *dev) { return 0; } +#endif + +#ifdef CONFIG_TOUCHSCREEN_PARADE_PROXIMITY +int pt_proximity_probe(struct device *dev); +int pt_proximity_release(struct device *dev); +#else +static inline int pt_proximity_probe(struct device *dev) { return 0; } +static inline int pt_proximity_release(struct device *dev) { return 0; } +#endif + +static inline unsigned int pt_get_time_stamp(void) +{ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0)) + struct timespec64 ts; + + ktime_get_real_ts64(&ts); + return (ts.tv_sec*1000 + ts.tv_nsec/1000000); +#else + struct timeval tv; + + do_gettimeofday(&tv); + return (tv.tv_sec*1000 + tv.tv_usec/1000); +#endif +} + +void pt_init_function_ptrs(struct pt_mt_data *md); +int _pt_subscribe_attention(struct device *dev, + enum pt_atten_type type, char *id, int (*func)(struct device *), + int mode); +int _pt_unsubscribe_attention(struct device *dev, + enum pt_atten_type type, char *id, int (*func)(struct device *), + int mode); +struct pt_sysinfo *_pt_request_sysinfo(struct device *dev); + +extern const struct dev_pm_ops pt_pm_ops; + +int pt_register_module(struct pt_module *module); +void pt_unregister_module(struct pt_module *module); + +void *pt_get_module_data(struct device *dev, + struct pt_module *module); + +#endif /* _PT_REGS_H */ diff --git a/qcom/opensource/touch-drivers/pt/pt_spi.c b/qcom/opensource/touch-drivers/pt/pt_spi.c new file mode 100644 index 0000000000..d8db405282 --- /dev/null +++ b/qcom/opensource/touch-drivers/pt/pt_spi.c @@ -0,0 +1,429 @@ +/* + * pt_spi.c + * Parade TrueTouch(TM) Standard Product SPI Module. + * For use with Parade touchscreen controllers. + * Supported parts include: + * TMA5XX + * TMA448 + * TMA445A + * TT21XXX + * TT31XXX + * TT4XXXX + * TT7XXX + * TC3XXX + * + * Copyright (C) 2015-2020 Parade Technologies + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * 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. + * + * Contact Parade Technologies at www.paradetech.com + */ + +#include "pt_regs.h" + +#include +#include + +/* TC3315 - DUT Address (0x24 & 0x07) << 1 = 0x08 for write and 0x09 for read */ +#define PT_SPI_WR_OP 0x08 /* r/~w */ +#define PT_SPI_RD_OP 0x09 +#define PT_SPI_BITS_PER_WORD 8 +#define PT_SPI_SYNC_ACK 0x62 + +#define PT_SPI_CMD_BYTES 0 +#define PT_SPI_DATA_SIZE (2 * 256) +#define PT_SPI_DATA_BUF_SIZE (PT_SPI_CMD_BYTES + PT_SPI_DATA_SIZE) + +#define PT_SPI_OP_SIZE (1) +#define PT_SPI_DUMMY_READ (1) +#define PT_SPI_BUFFER_SIZE \ + (PT_MAX_PIP2_MSG_SIZE + PT_SPI_OP_SIZE + PT_SPI_DUMMY_READ) + +static u8 *tmp_rbuf; +static u8 *tmp_wbuf; +DEFINE_MUTEX(pt_spi_bus_lock); + + +/******************************************************************************* + * FUNCTION: pt_spi_xfer + * + * SUMMARY: Read or write date for SPI device. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to device structure + * op - flag to write or read data + * *buf - pointer to data buffer + * length - data length + ******************************************************************************/ +static int pt_spi_xfer(struct device *dev, u8 op, u8 *buf, int length) +{ + struct spi_device *spi = to_spi_device(dev); + struct spi_message msg; + struct spi_transfer xfer; + int rc; + + memset(&xfer, 0, sizeof(xfer)); + spi_message_init(&msg); + + switch (op) { + case PT_SPI_RD_OP: + /* Clear tmp_wbuf with additional OP Code and dummy byte */ + memset(tmp_wbuf, 0, length + 2); + tmp_wbuf[0] = op; + /* Total read/write = Read length + Op code + dummy byte */ + xfer.tx_buf = tmp_wbuf; + xfer.rx_buf = tmp_rbuf; + xfer.len = length + 2; + break; + case PT_SPI_WR_OP: + memcpy(&tmp_wbuf[1], buf, length); + tmp_wbuf[0] = op; + /* Write length + size of Op code */ + xfer.tx_buf = tmp_wbuf; + xfer.len = length + 1; + break; + default: + rc = -EIO; + goto exit; + } + + spi_message_add_tail(&xfer, &msg); + rc = spi_sync(spi, &msg); + + /* On reads copy only the data content back into the passed in buf */ + if (op == PT_SPI_RD_OP) + memcpy(buf, &tmp_rbuf[2], length); +exit: + if (rc < 0) + pt_debug(dev, DL_ERROR, "%s: spi_sync() error %d\n", + __func__, rc); + +#if 0 /* TODO TC3315 - need to verify the ACK byte */ + if (tmp_rbuf[0] != PT_SPI_SYNC_ACK) { + pt_debug(dev, DL_ERROR, "%s: r_header = 0x%02X\n", __func__, + r_header[0]); + return -EIO; + } +#endif + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_spi_read_default + * + * SUMMARY: Read a certain number of bytes from the SPI bus + * NOTE: For TC3315 every response includes a "dummy" prefix byte that + * needs to be stipped off before returning buf. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to Device structure + * *buf - pointer to buffer where the data read will be stored + * size - size to be read + ******************************************************************************/ +static int pt_spi_read_default(struct device *dev, void *buf, int size) +{ + int rc = 0; + + if (!buf || !size || size > PT_MAX_PIP2_MSG_SIZE) + return -EINVAL; + + mutex_lock(&pt_spi_bus_lock); + rc = pt_spi_xfer(dev, PT_SPI_RD_OP, buf, size); + mutex_unlock(&pt_spi_bus_lock); + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_spi_read_default_nosize + * + * SUMMARY: Read from the SPI bus in two transactions first reading the HID + * packet size (2 bytes) followed by reading the rest of the packet based + * on the size initially read. + * NOTE: The empty buffer 'size' was redefined in PIP version 1.7. + * NOTE: For TC3315 every response includes a "dummy" prefix byte that + * needs to be stipped off before returning buf. + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to Device structure + * *buf - pointer to buffer where the data read will be stored + * max - max size that can be read + ******************************************************************************/ +static int pt_spi_read_default_nosize(struct device *dev, u8 *buf, u32 max) +{ + u32 size; + int rc = 0; + + if (!buf) + return 0; + + mutex_lock(&pt_spi_bus_lock); + + /* Separate transaction to retrieve only the length to read */ + rc = pt_spi_xfer(dev, PT_SPI_RD_OP, buf, 2); + if (rc < 0) { + pt_debug(dev, DL_ERROR, "%s: SPI transfer error rc = %d\n", + __func__, rc); + goto exit; + } + + size = get_unaligned_le16(&buf[0]); + if (!size || size == 2 || size >= PT_PIP_1P7_EMPTY_BUF) + goto exit; + + if (size > max || size > PT_MAX_PIP2_MSG_SIZE) { + pt_debug(dev, DL_ERROR, "%s: Invalid size %d !\n", __func__, + size); + rc = -EINVAL; + goto exit; + } + + rc = pt_spi_xfer(dev, PT_SPI_RD_OP, buf, size); + if (rc) + pt_debug(dev, DL_ERROR, "%s: SPI transfer error rc = %d\n", + __func__, rc); + +exit: + mutex_unlock(&pt_spi_bus_lock); + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_spi_write_read_specific + * + * SUMMARY: Write the contents of write_buf to the SPI device and then read + * the response using pt_spi_read_default_nosize() + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *dev - pointer to Device structure + * write_len - length of data buffer write_buf + * *write_buf - pointer to buffer to write + * *read_buf - pointer to buffer to read response into + ******************************************************************************/ +static int pt_spi_write_read_specific(struct device *dev, u16 write_len, + u8 *write_buf, u8 *read_buf) +{ + int rc = 0; + + /* Ensure no packet larger than what the PIP spec allows */ + if (write_len > PT_MAX_PIP2_MSG_SIZE) + return -EINVAL; + + if (!write_buf || !write_len) { + if (!write_buf) + pt_debug(dev, DL_ERROR, + "%s write_buf is NULL", __func__); + if (!write_len) + pt_debug(dev, DL_ERROR, + "%s write_len is NULL", __func__); + return -EINVAL; + } + + mutex_lock(&pt_spi_bus_lock); + rc = pt_spi_xfer(dev, PT_SPI_WR_OP, write_buf, write_len); + if (rc < 0) + goto error; + mutex_unlock(&pt_spi_bus_lock); + + if (read_buf) + rc = pt_spi_read_default_nosize(dev, read_buf, + PT_SPI_DATA_SIZE); + return rc; + +error: + mutex_unlock(&pt_spi_bus_lock); + return rc; +} + +static struct pt_bus_ops pt_spi_bus_ops = { + .bustype = BUS_SPI, + .read_default = pt_spi_read_default, + .read_default_nosize = pt_spi_read_default_nosize, + .write_read_specific = pt_spi_write_read_specific, +}; + +#ifdef CONFIG_TOUCHSCREEN_PARADE_DEVICETREE_SUPPORT +static const struct of_device_id pt_spi_of_match[] = { + { .compatible = "parade,pt_spi_adapter", }, + { } +}; +MODULE_DEVICE_TABLE(of, pt_spi_of_match); +#endif + +/******************************************************************************* + * FUNCTION: pt_spi_probe + * + * SUMMARY: Probe functon for the SPI module + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *spi - pointer to spi device structure + ******************************************************************************/ +static int pt_spi_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; +#ifdef CONFIG_TOUCHSCREEN_PARADE_DEVICETREE_SUPPORT + const struct of_device_id *match; +#endif + int rc; + + /* Set up SPI*/ + spi->bits_per_word = PT_SPI_BITS_PER_WORD; + spi->mode = SPI_MODE_0; + rc = spi_setup(spi); + if (rc < 0) { + pt_debug(dev, DL_ERROR, "%s: SPI setup error %d\n", + __func__, rc); + return rc; + } + +#ifdef CONFIG_TOUCHSCREEN_PARADE_DEVICETREE_SUPPORT + match = of_match_device(of_match_ptr(pt_spi_of_match), dev); + if (match) { + rc = pt_devtree_create_and_get_pdata(dev); + if (rc < 0) + return rc; + } +#endif + + /* Add 2 to the length for the 'OP Code' & 'Dummy' prefix bytes */ + tmp_wbuf = kzalloc(PT_SPI_BUFFER_SIZE, GFP_KERNEL); + tmp_rbuf = kzalloc(PT_SPI_BUFFER_SIZE, GFP_KERNEL); + if (!tmp_wbuf || !tmp_rbuf) + return -ENOMEM; + + + rc = pt_probe(&pt_spi_bus_ops, &spi->dev, spi->irq, + PT_SPI_DATA_BUF_SIZE); + +#ifdef CONFIG_TOUCHSCREEN_PARADE_DEVICETREE_SUPPORT + if (rc && match) + pt_devtree_clean_pdata(dev); +#endif + + + return rc; +} + +/******************************************************************************* + * FUNCTION: pt_spi_remove + * + * SUMMARY: Remove functon for the SPI module + * + * RETURN: + * 0 = success + * !0 = failure + * + * PARAMETERS: + * *spi - pointer to spi device structure + ******************************************************************************/ +static int pt_spi_remove(struct spi_device *spi) +{ +#ifdef CONFIG_TOUCHSCREEN_PARADE_DEVICETREE_SUPPORT + const struct of_device_id *match; +#endif + struct device *dev = &spi->dev; + struct pt_core_data *cd = dev_get_drvdata(dev); + + kfree(tmp_rbuf); + kfree(tmp_wbuf); + + + pt_release(cd); + +#ifdef CONFIG_TOUCHSCREEN_PARADE_DEVICETREE_SUPPORT + match = of_match_device(of_match_ptr(pt_spi_of_match), dev); + if (match) + pt_devtree_clean_pdata(dev); +#endif + + return 0; +} + +static const struct spi_device_id pt_spi_id[] = { + { PT_SPI_NAME, 0 }, + { } +}; +MODULE_DEVICE_TABLE(spi, pt_spi_id); + +static struct spi_driver pt_spi_driver = { + .driver = { + .name = PT_SPI_NAME, + .bus = &spi_bus_type, + .owner = THIS_MODULE, + .pm = &pt_pm_ops, +#ifdef CONFIG_TOUCHSCREEN_PARADE_DEVICETREE_SUPPORT + .of_match_table = pt_spi_of_match, +#endif + }, + .probe = pt_spi_probe, + .remove = (pt_spi_remove), + .id_table = pt_spi_id, +}; + +#if (KERNEL_VERSION(3, 3, 0) <= LINUX_VERSION_CODE) +module_spi_driver(pt_spi_driver); +#else +/******************************************************************************* + * FUNCTION: pt_spi_init + * + * SUMMARY: Initialize function to register spi module to kernel. + * + * RETURN: + * 0 = success + * !0 = failure + ******************************************************************************/ +static int __init pt_spi_init(void) +{ + int err = spi_register_driver(&pt_spi_driver); + + pr_info("%s: Parade TTDL SPI Driver (Build %s) rc=%d\n", + __func__, PT_DRIVER_VERSION, err); + return err; +} +module_init(pt_spi_init); + +/******************************************************************************* + * FUNCTION: pt_spi_exit + * + * SUMMARY: Exit function to unregister spi module from kernel. + * + ******************************************************************************/ +static void __exit pt_spi_exit(void) +{ + spi_unregister_driver(&pt_spi_driver); +} +module_exit(pt_spi_exit); +#endif + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Parade TrueTouch(R) Standard Product SPI Driver"); +MODULE_AUTHOR("Parade Technologies "); diff --git a/qcom/opensource/touch-drivers/qts/qts_core.c b/qcom/opensource/touch-drivers/qts/qts_core.c new file mode 100644 index 0000000000..5f3df38d2b --- /dev/null +++ b/qcom/opensource/touch-drivers/qts/qts_core.c @@ -0,0 +1,1814 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2022-2023, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "qts_core.h" + +static struct qts_data_entries *qts_data_entries; + +struct drm_panel *active_panel; + +static void qts_trusted_touch_abort_handler(struct qts_data *qts_data, int error); + +static struct gh_acl_desc *qts_vm_get_acl(enum gh_vm_names vm_name) +{ + struct gh_acl_desc *acl_desc; + gh_vmid_t vmid; + +#if (KERNEL_VERSION(6, 1, 0) <= LINUX_VERSION_CODE) + ghd_rm_get_vmid(vm_name, &vmid); +#else + gh_rm_get_vmid(vm_name, &vmid); +#endif + + acl_desc = kzalloc(offsetof(struct gh_acl_desc, acl_entries[1]), + GFP_KERNEL); + if (!acl_desc) + return ERR_PTR(ENOMEM); + + acl_desc->n_acl_entries = 1; + acl_desc->acl_entries[0].vmid = vmid; + acl_desc->acl_entries[0].perms = GH_RM_ACL_R | GH_RM_ACL_W; + + return acl_desc; +} + +static struct gh_sgl_desc *qts_vm_get_sgl(struct trusted_touch_vm_info *vm_info) +{ + struct gh_sgl_desc *sgl_desc; + int i; + + sgl_desc = kzalloc(offsetof(struct gh_sgl_desc, + sgl_entries[vm_info->iomem_list_size]), GFP_KERNEL); + if (!sgl_desc) + return ERR_PTR(ENOMEM); + + sgl_desc->n_sgl_entries = vm_info->iomem_list_size; + + for (i = 0; i < vm_info->iomem_list_size; i++) { + sgl_desc->sgl_entries[i].ipa_base = vm_info->iomem_bases[i]; + sgl_desc->sgl_entries[i].size = vm_info->iomem_sizes[i]; + } + + return sgl_desc; +} + +static int qts_populate_vm_info_iomem(struct qts_data *qts_data) +{ + int i, gpio, rc = 0; + int num_regs, num_sizes, num_gpios, list_size; + struct resource res; + struct device_node *np = qts_data->dev->of_node; + struct trusted_touch_vm_info *vm_info = qts_data->vm_info; + + num_regs = of_property_count_u32_elems(np, "qts,trusted-touch-io-bases"); + if (num_regs < 0) { + pr_err("Invalid number of IO regions specified\n"); + return -EINVAL; + } + + num_sizes = of_property_count_u32_elems(np, "qts,trusted-touch-io-sizes"); + if (num_sizes < 0) { + pr_err("Invalid number of IO regions specified\n"); + return -EINVAL; + } + + if (num_regs != num_sizes) { + pr_err("IO bases and sizes array lengths mismatch\n"); + return -EINVAL; + } + + num_gpios = of_gpio_named_count(np, "qts,trusted-touch-vm-gpio-list"); + if (num_gpios < 0) { + pr_warn("Ignoring invalid trusted gpio list: %d\n", num_gpios); + num_gpios = 0; + } + + list_size = num_regs + num_gpios; + vm_info->iomem_list_size = list_size; + vm_info->iomem_bases = devm_kcalloc(qts_data->dev, list_size, sizeof(*vm_info->iomem_bases), + GFP_KERNEL); + if (!vm_info->iomem_bases) + return -ENOMEM; + + vm_info->iomem_sizes = devm_kcalloc(qts_data->dev, list_size, sizeof(*vm_info->iomem_sizes), + GFP_KERNEL); + if (!vm_info->iomem_sizes) + return -ENOMEM; + + for (i = 0; i < num_gpios; ++i) { + gpio = of_get_named_gpio(np, "qts,trusted-touch-vm-gpio-list", i); + if (gpio < 0 || !gpio_is_valid(gpio)) { + pr_err("Invalid gpio %d at position %d\n", gpio, i); + return gpio; + } + + if (!msm_gpio_get_pin_address(gpio, &res)) { + pr_err("Failed to retrieve gpio-%d resource\n", gpio); + return -ENODATA; + } + + vm_info->iomem_bases[i] = res.start; + vm_info->iomem_sizes[i] = resource_size(&res); + } + + rc = of_property_read_u32_array(np, "qts,trusted-touch-io-bases", + &vm_info->iomem_bases[i], list_size - i); + if (rc) { + pr_err("Failed to read trusted touch io bases:%d\n", rc); + return rc; + } + + rc = of_property_read_u32_array(np, "qts,trusted-touch-io-sizes", + &vm_info->iomem_sizes[i], list_size - i); + if (rc) { + pr_err("Failed to read trusted touch io sizes:%d\n", rc); + return rc; + } + + return 0; +} + +static int qts_populate_vm_info(struct qts_data *qts_data) +{ + int rc; + struct trusted_touch_vm_info *vm_info; + struct device_node *np = qts_data->dev->of_node; + + vm_info = devm_kzalloc(qts_data->dev, sizeof(struct trusted_touch_vm_info), GFP_KERNEL); + if (!vm_info) + return -ENOMEM; + + qts_data->vm_info = vm_info; + vm_info->vm_name = GH_TRUSTED_VM; + rc = of_property_read_u32(np, "qts,trusted-touch-spi-irq", &vm_info->hw_irq); + if (rc) { + pr_err("Failed to read trusted touch SPI irq:%d\n", rc); + return rc; + } + + rc = qts_populate_vm_info_iomem(qts_data); + if (rc) { + pr_err("Failed to read trusted touch mmio ranges:%d\n", rc); + return rc; + } + + rc = of_property_read_string(np, "qts,trusted-touch-type", + &vm_info->trusted_touch_type); + if (rc) { + pr_warn("No trusted touch type selection mode\n"); + vm_info->mem_tag = GH_MEM_NOTIFIER_TAG_TOUCH_PRIMARY; + vm_info->irq_label = GH_IRQ_LABEL_TRUSTED_TOUCH_PRIMARY; + rc = 0; + } else if (!strcmp(vm_info->trusted_touch_type, "primary")) { + vm_info->mem_tag = GH_MEM_NOTIFIER_TAG_TOUCH_PRIMARY; + vm_info->irq_label = GH_IRQ_LABEL_TRUSTED_TOUCH_PRIMARY; + } else if (!strcmp(vm_info->trusted_touch_type, "secondary")) { + vm_info->mem_tag = GH_MEM_NOTIFIER_TAG_TOUCH_SECONDARY; + vm_info->irq_label = GH_IRQ_LABEL_TRUSTED_TOUCH_SECONDARY; + } + + return 0; +} + +static void qts_destroy_vm_info(struct qts_data *qts_data) +{ + kfree(qts_data->vm_info->iomem_sizes); + kfree(qts_data->vm_info->iomem_bases); + kfree(qts_data->vm_info); +} + +static void qts_vm_deinit(struct qts_data *qts_data) +{ + if (qts_data->vm_info->mem_cookie) + gh_mem_notifier_unregister(qts_data->vm_info->mem_cookie); + qts_destroy_vm_info(qts_data); +} + +static int qts_trusted_touch_get_vm_state(struct qts_data *qts_data) +{ + return atomic_read(&qts_data->vm_info->vm_state); +} + +static void qts_trusted_touch_set_vm_state(struct qts_data *qts_data, + int state) +{ + pr_debug("state %d\n", state); + atomic_set(&qts_data->vm_info->vm_state, state); +} + +#ifdef CONFIG_ARCH_QTI_VM +static int qts_vm_mem_release(struct qts_data *qts_data); +static void qts_trusted_touch_tvm_vm_mode_disable(struct qts_data *qts_data); +static void qts_trusted_touch_abort_tvm(struct qts_data *qts_data); +static void qts_trusted_touch_event_notify(struct qts_data *qts_data, int event); + +static void qts_irq_enable(struct qts_data *qts_data, bool en) +{ + if (en) { + if (qts_data->irq_disabled) { + pr_debug("qts irq enable\n"); + enable_irq(qts_data->irq); + qts_data->irq_disabled = false; + } + } else { + if (!qts_data->irq_disabled) { + pr_debug("qts irq disable\n"); + disable_irq_nosync(qts_data->irq); + qts_data->irq_disabled = true; + } + } +} + +static irqreturn_t qts_irq_handler(int irq, void *data) +{ + struct qts_data *qts_data = data; + + if (!mutex_trylock(&qts_data->transition_lock)) + return IRQ_HANDLED; + + qts_data->vendor_ops.irq_handler(irq, qts_data->vendor_data); + mutex_unlock(&qts_data->transition_lock); + return IRQ_HANDLED; +} + +static int qts_irq_registration(struct qts_data *qts_data) +{ + int ret = 0; + + qts_data->irq_gpio_flags = IRQF_TRIGGER_RISING; + pr_debug("irq:%d, flag:%x\n", qts_data->irq, qts_data->irq_gpio_flags); + ret = request_threaded_irq(qts_data->irq, NULL, qts_irq_handler, + qts_data->irq_gpio_flags | IRQF_ONESHOT, + QTS_NAME, qts_data); + if (ret != 0) + pr_err("request_threaded_irq failed\n"); + if (ret == 0) + qts_irq_enable(qts_data, false); + + return ret; +} + +void qts_trusted_touch_tvm_i2c_failure_report(struct qts_data *qts_data) +{ + pr_warn("initiating trusted touch abort due to i2c failure\n"); + qts_trusted_touch_abort_handler(qts_data, TRUSTED_TOUCH_EVENT_I2C_FAILURE); +} + +static void qts_trusted_touch_reset_gpio_toggle(struct qts_data *qts_data) +{ + void __iomem *base; + + if (qts_data->bus_type != QTS_BUS_TYPE_I2C) + return; + + base = ioremap(TOUCH_RESET_GPIO_BASE, TOUCH_RESET_GPIO_SIZE); + writel_relaxed(0x1, base + TOUCH_RESET_GPIO_OFFSET); + /* wait until toggle to finish*/ + wmb(); + writel_relaxed(0x0, base + TOUCH_RESET_GPIO_OFFSET); + /* wait until toggle to finish*/ + wmb(); + iounmap(base); +} + +static void qts_trusted_touch_intr_gpio_toggle(struct qts_data *qts_data, + bool enable) +{ + void __iomem *base; + u32 val; + + if (qts_data->bus_type != QTS_BUS_TYPE_I2C) + return; + + base = ioremap(TOUCH_INTR_GPIO_BASE, TOUCH_INTR_GPIO_SIZE); + val = readl_relaxed(base + TOUCH_RESET_GPIO_OFFSET); + if (enable) { + val |= BIT(0); + writel_relaxed(val, base + TOUCH_INTR_GPIO_OFFSET); + /* wait until toggle to finish*/ + wmb(); + } else { + val &= ~BIT(0); + writel_relaxed(val, base + TOUCH_INTR_GPIO_OFFSET); + /* wait until toggle to finish*/ + wmb(); + } + iounmap(base); +} + +static int qts_sgl_cmp(const void *a, const void *b) +{ + struct gh_sgl_entry *left = (struct gh_sgl_entry *)a; + struct gh_sgl_entry *right = (struct gh_sgl_entry *)b; + + return (left->ipa_base - right->ipa_base); +} + +static int qts_vm_compare_sgl_desc(struct gh_sgl_desc *expected, + struct gh_sgl_desc *received) +{ + int idx; + + if (expected->n_sgl_entries != received->n_sgl_entries) + return -E2BIG; + sort(received->sgl_entries, received->n_sgl_entries, + sizeof(received->sgl_entries[0]), qts_sgl_cmp, NULL); + sort(expected->sgl_entries, expected->n_sgl_entries, + sizeof(expected->sgl_entries[0]), qts_sgl_cmp, NULL); + + for (idx = 0; idx < expected->n_sgl_entries; idx++) { + struct gh_sgl_entry *left = &expected->sgl_entries[idx]; + struct gh_sgl_entry *right = &received->sgl_entries[idx]; + + if ((left->ipa_base != right->ipa_base) || + (left->size != right->size)) { + pr_err("sgl mismatch: left_base:%d right base:%d left size:%d right size:%d\n", + left->ipa_base, right->ipa_base, left->size, right->size); + + return -EINVAL; + } + } + return 0; +} + +static int qts_vm_handle_vm_hardware(struct qts_data *qts_data) +{ + int rc = 0; + + if (atomic_read(&qts_data->delayed_tvm_probe_pending)) { + rc = qts_irq_registration(qts_data); + if (rc) { + pr_err("irq registration failure on TVM!\n"); + return rc; + } + atomic_set(&qts_data->delayed_tvm_probe_pending, 0); + } + + qts_irq_enable(qts_data, true); + qts_trusted_touch_set_vm_state(qts_data, TVM_INTERRUPT_ENABLED); + return rc; +} + +static void qts_trusted_touch_tvm_vm_mode_enable(struct qts_data *qts_data) +{ + + struct gh_sgl_desc *sgl_desc, *expected_sgl_desc; + struct gh_acl_desc *acl_desc; + struct irq_data *irq_data; + int rc = 0; + int irq = 0; + + mutex_lock(&qts_data->transition_lock); + if (qts_trusted_touch_get_vm_state(qts_data) != TVM_ALL_RESOURCES_LENT_NOTIFIED) { + pr_info("All lend notifications not received\n"); + qts_trusted_touch_event_notify(qts_data, + TRUSTED_TOUCH_EVENT_NOTIFICATIONS_PENDING); + mutex_unlock(&qts_data->transition_lock); + return; + } + + if (qts_data->vendor_ops.pre_le_tui_enable) + qts_data->vendor_ops.pre_le_tui_enable(qts_data->vendor_data); + + acl_desc = qts_vm_get_acl(GH_TRUSTED_VM); + if (IS_ERR(acl_desc)) { + pr_err("failed to populated acl data:rc=%d\n", PTR_ERR(acl_desc)); + goto accept_fail; + } + + sgl_desc = gh_rm_mem_accept(qts_data->vm_info->vm_mem_handle, + GH_RM_MEM_TYPE_IO, + GH_RM_TRANS_TYPE_LEND, + GH_RM_MEM_ACCEPT_VALIDATE_ACL_ATTRS | + GH_RM_MEM_ACCEPT_VALIDATE_LABEL | + GH_RM_MEM_ACCEPT_DONE, TRUSTED_TOUCH_MEM_LABEL, + acl_desc, NULL, NULL, 0); + if (IS_ERR_OR_NULL(sgl_desc)) { + pr_err("failed to do mem accept :rc=%d\n", PTR_ERR(sgl_desc)); + goto acl_fail; + } + qts_trusted_touch_set_vm_state(qts_data, TVM_IOMEM_ACCEPTED); + + /* Initiate session on tvm */ + if (qts_data->bus_type == QTS_BUS_TYPE_I2C) + rc = pm_runtime_get_sync(qts_data->client->adapter->dev.parent); + else + rc = pm_runtime_get_sync(qts_data->spi->master->dev.parent); + + if (rc < 0) { + pr_err("failed to get sync rc:%d\n", rc); + goto sgl_fail; + } + qts_trusted_touch_set_vm_state(qts_data, TVM_I2C_SESSION_ACQUIRED); + + expected_sgl_desc = qts_vm_get_sgl(qts_data->vm_info); + if (qts_vm_compare_sgl_desc(expected_sgl_desc, sgl_desc)) { + pr_err("IO sg list does not match\n"); + goto sgl_cmp_fail; + } + + kfree(expected_sgl_desc); + kfree(acl_desc); + kfree(sgl_desc); + + irq = gh_irq_accept(qts_data->vm_info->irq_label, -1, IRQ_TYPE_EDGE_RISING); + qts_trusted_touch_intr_gpio_toggle(qts_data, false); + if (irq < 0) { + pr_err("failed to accept irq\n"); + goto accept_fail; + } + qts_trusted_touch_set_vm_state(qts_data, TVM_IRQ_ACCEPTED); + + + irq_data = irq_get_irq_data(irq); + if (!irq_data) { + pr_err("Invalid irq data for trusted touch\n"); + goto accept_fail; + } + if (!irq_data->hwirq) { + pr_err("Invalid irq in irq data\n"); + goto accept_fail; + } + if (irq_data->hwirq != qts_data->vm_info->hw_irq) { + pr_err("Invalid irq lent\n"); + goto accept_fail; + } + + pr_debug("irq:returned from accept:%d\n", irq); + qts_data->irq = irq; + + rc = qts_vm_handle_vm_hardware(qts_data); + if (rc) { + pr_err("Delayed probe failure on TVM!\n"); + goto accept_fail; + } + atomic_set(&qts_data->trusted_touch_enabled, 1); + + if (qts_data->vendor_ops.post_le_tui_enable) + qts_data->vendor_ops.post_le_tui_enable(qts_data->vendor_data); + + pr_info("Irq, iomem are accepted and trusted touch enabled\n"); + + mutex_unlock(&qts_data->transition_lock); + return; +sgl_cmp_fail: + kfree(expected_sgl_desc); +sgl_fail: + kfree(sgl_desc); +acl_fail: + kfree(acl_desc); +accept_fail: + qts_trusted_touch_abort_handler(qts_data, + TRUSTED_TOUCH_EVENT_ACCEPT_FAILURE); + mutex_unlock(&qts_data->transition_lock); +} + +static void qts_vm_irq_on_lend_callback(void *data, + unsigned long notif_type, + enum gh_irq_label label) +{ + struct qts_data *qts_data = data; + + pr_debug("received irq lend request for label:%d\n", label); + if (qts_trusted_touch_get_vm_state(qts_data) == TVM_IOMEM_LENT_NOTIFIED) + qts_trusted_touch_set_vm_state(qts_data, TVM_ALL_RESOURCES_LENT_NOTIFIED); + else + qts_trusted_touch_set_vm_state(qts_data, TVM_IRQ_LENT_NOTIFIED); +} + +static void qts_vm_mem_on_lend_handler(enum gh_mem_notifier_tag tag, + unsigned long notif_type, void *entry_data, void *notif_msg) +{ + struct gh_rm_notif_mem_shared_payload *payload; + struct trusted_touch_vm_info *vm_info; + struct qts_data *qts_data; + + qts_data = (struct qts_data *)entry_data; + vm_info = qts_data->vm_info; + if (!vm_info) { + pr_err("Invalid vm_info\n"); + return; + } + + if (notif_type != GH_RM_NOTIF_MEM_SHARED || + tag != vm_info->mem_tag) { + pr_err("Invalid command passed from rm\n"); + return; + } + + if (!entry_data || !notif_msg) { + pr_err("Invalid entry data passed from rm\n"); + return; + } + + payload = (struct gh_rm_notif_mem_shared_payload *)notif_msg; + if (payload->trans_type != GH_RM_TRANS_TYPE_LEND || + payload->label != TRUSTED_TOUCH_MEM_LABEL) { + pr_err("Invalid label or transaction type\n"); + return; + } + + vm_info->vm_mem_handle = payload->mem_handle; + pr_debug("received mem lend request with handle:%d\n", vm_info->vm_mem_handle); + + if (qts_trusted_touch_get_vm_state(qts_data) == TVM_IRQ_LENT_NOTIFIED) + qts_trusted_touch_set_vm_state(qts_data, TVM_ALL_RESOURCES_LENT_NOTIFIED); + else + qts_trusted_touch_set_vm_state(qts_data, TVM_IOMEM_LENT_NOTIFIED); +} + +static int qts_vm_mem_release(struct qts_data *qts_data) +{ + int rc = 0; + + if (!qts_data->vm_info->vm_mem_handle) { + pr_err("Invalid memory handle\n"); + return -EINVAL; + } + + rc = gh_rm_mem_release(qts_data->vm_info->vm_mem_handle, 0); + if (rc) + pr_err("VM mem release failed: rc=%d\n", rc); + + rc = gh_rm_mem_notify(qts_data->vm_info->vm_mem_handle, + GH_RM_MEM_NOTIFY_OWNER_RELEASED, + qts_data->vm_info->mem_tag, 0); + if (rc) + pr_err("Failed to notify mem release to PVM: rc=%d\n", rc); + + pr_debug("vm mem release success\n"); + + qts_data->vm_info->vm_mem_handle = 0; + return rc; +} + +static void qts_trusted_touch_tvm_vm_mode_disable(struct qts_data *qts_data) +{ + int rc = 0; + + mutex_lock(&qts_data->transition_lock); + if (atomic_read(&qts_data->trusted_touch_abort_status)) { + qts_trusted_touch_abort_tvm(qts_data); + mutex_unlock(&qts_data->transition_lock); + return; + } + + if (qts_data->vendor_ops.pre_le_tui_disable) + qts_data->vendor_ops.pre_le_tui_disable(qts_data->vendor_data); + + qts_irq_enable(qts_data, false); + qts_trusted_touch_set_vm_state(qts_data, TVM_INTERRUPT_DISABLED); + + rc = gh_irq_release(qts_data->vm_info->irq_label); + if (rc) { + pr_err("Failed to release irq rc:%d\n", rc); + goto error; + } else { + qts_trusted_touch_set_vm_state(qts_data, TVM_IRQ_RELEASED); + } + rc = gh_irq_release_notify(qts_data->vm_info->irq_label); + if (rc) + pr_err("Failed to notify release irq rc:%d\n", rc); + + pr_debug("vm irq release success\n"); + + if (qts_data->bus_type == QTS_BUS_TYPE_I2C) + pm_runtime_put_sync(qts_data->client->adapter->dev.parent); + else + pm_runtime_put_sync(qts_data->spi->master->dev.parent); + + qts_trusted_touch_set_vm_state(qts_data, TVM_I2C_SESSION_RELEASED); + rc = qts_vm_mem_release(qts_data); + if (rc) { + pr_err("Failed to release mem rc:%d\n", rc); + goto error; + } else { + qts_trusted_touch_set_vm_state(qts_data, TVM_IOMEM_RELEASED); + } + qts_trusted_touch_set_vm_state(qts_data, TRUSTED_TOUCH_TVM_INIT); + atomic_set(&qts_data->trusted_touch_enabled, 0); + + if (qts_data->vendor_ops.post_le_tui_disable) + qts_data->vendor_ops.post_le_tui_disable(qts_data->vendor_data); + + pr_info("Irq, iomem are released and trusted touch disabled\n"); + mutex_unlock(&qts_data->transition_lock); + return; +error: + qts_trusted_touch_abort_handler(qts_data, + TRUSTED_TOUCH_EVENT_RELEASE_FAILURE); + mutex_unlock(&qts_data->transition_lock); +} + +static int qts_handle_trusted_touch_tvm(struct qts_data *qts_data, int value) +{ + int err = 0; + + switch (value) { + case 0: + if ((atomic_read(&qts_data->trusted_touch_enabled) == 0) && + (atomic_read(&qts_data->trusted_touch_abort_status) == 0)) { + pr_err("Trusted touch is already disabled\n"); + break; + } + if (atomic_read(&qts_data->trusted_touch_mode) == + TRUSTED_TOUCH_VM_MODE) { + qts_trusted_touch_tvm_vm_mode_disable(qts_data); + } else { + pr_err("Unsupported trusted touch mode\n"); + } + break; + + case 1: + if (atomic_read(&qts_data->trusted_touch_enabled)) { + pr_err("Trusted touch usecase underway\n"); + err = -EBUSY; + break; + } + if (atomic_read(&qts_data->trusted_touch_mode) == + TRUSTED_TOUCH_VM_MODE) { + qts_trusted_touch_tvm_vm_mode_enable(qts_data); + } else { + pr_err("Unsupported trusted touch mode\n"); + } + break; + + default: + pr_err("unsupported value: %lu\n", value); + err = -EINVAL; + break; + } + + return err; +} + +static void qts_trusted_touch_abort_tvm(struct qts_data *qts_data) +{ + int rc = 0; + int vm_state = qts_trusted_touch_get_vm_state(qts_data); + + if (vm_state >= TRUSTED_TOUCH_TVM_STATE_MAX) { + pr_err("invalid tvm driver state: %d\n", vm_state); + return; + } + + switch (vm_state) { + case TVM_INTERRUPT_ENABLED: + qts_irq_enable(qts_data, false); + fallthrough; + case TVM_IRQ_ACCEPTED: + case TVM_INTERRUPT_DISABLED: + rc = gh_irq_release(qts_data->vm_info->irq_label); + if (rc) + pr_err("Failed to release irq rc:%d\n", rc); + rc = gh_irq_release_notify(qts_data->vm_info->irq_label); + if (rc) + pr_err("Failed to notify irq release rc:%d\n", rc); + fallthrough; + case TVM_I2C_SESSION_ACQUIRED: + case TVM_IOMEM_ACCEPTED: + case TVM_IRQ_RELEASED: + if (qts_data->bus_type == QTS_BUS_TYPE_I2C) + pm_runtime_put_sync(qts_data->client->adapter->dev.parent); + else + pm_runtime_put_sync(qts_data->spi->master->dev.parent); + fallthrough; + case TVM_I2C_SESSION_RELEASED: + rc = qts_vm_mem_release(qts_data); + if (rc) + pr_err("Failed to release mem rc:%d\n", rc); + fallthrough; + case TVM_IOMEM_RELEASED: + case TVM_ALL_RESOURCES_LENT_NOTIFIED: + case TRUSTED_TOUCH_TVM_INIT: + case TVM_IRQ_LENT_NOTIFIED: + case TVM_IOMEM_LENT_NOTIFIED: + atomic_set(&qts_data->trusted_touch_enabled, 0); + } + + atomic_set(&qts_data->trusted_touch_abort_status, 0); + qts_trusted_touch_set_vm_state(qts_data, TRUSTED_TOUCH_TVM_INIT); +} + +#else + +static void qts_bus_put(struct qts_data *qts_data); +static int qts_enable_reg(struct qts_data *qts_data, bool enable); + +static void qts_trusted_touch_abort_pvm(struct qts_data *qts_data) +{ + int rc = 0; + int vm_state = qts_trusted_touch_get_vm_state(qts_data); + + if (vm_state >= TRUSTED_TOUCH_PVM_STATE_MAX) { + pr_err("Invalid driver state: %d\n", vm_state); + return; + } + + switch (vm_state) { + case PVM_IRQ_RELEASE_NOTIFIED: + case PVM_ALL_RESOURCES_RELEASE_NOTIFIED: + case PVM_IRQ_LENT: + case PVM_IRQ_LENT_NOTIFIED: + rc = gh_irq_reclaim(qts_data->vm_info->irq_label); + if (rc) { + pr_err("failed to reclaim irq on pvm rc:%d\n", rc); + return; + } + fallthrough; + case PVM_IRQ_RECLAIMED: + case PVM_IOMEM_LENT: + case PVM_IOMEM_LENT_NOTIFIED: + case PVM_IOMEM_RELEASE_NOTIFIED: +#if (KERNEL_VERSION(6, 1, 0) <= LINUX_VERSION_CODE) + rc = ghd_rm_mem_reclaim(qts_data->vm_info->vm_mem_handle, 0); +#else + rc = gh_rm_mem_reclaim(qts_data->vm_info->vm_mem_handle, 0); +#endif + + if (rc) { + pr_err("failed to reclaim iomem on pvm rc:%d\n", rc); + qts_trusted_touch_set_vm_state(qts_data, PVM_IOMEM_RELEASE_NOTIFIED); + return; + } + qts_data->vm_info->vm_mem_handle = 0; + fallthrough; + case PVM_IOMEM_RECLAIMED: + case PVM_INTERRUPT_DISABLED: + if (qts_data->vendor_ops.enable_touch_irq) + qts_data->vendor_ops.enable_touch_irq(qts_data->vendor_data, true); + fallthrough; + case PVM_I2C_RESOURCE_ACQUIRED: + case PVM_INTERRUPT_ENABLED: + qts_bus_put(qts_data); + fallthrough; + case TRUSTED_TOUCH_PVM_INIT: + case PVM_I2C_RESOURCE_RELEASED: + atomic_set(&qts_data->trusted_touch_enabled, 0); + atomic_set(&qts_data->trusted_touch_transition, 0); + } + + atomic_set(&qts_data->trusted_touch_abort_status, 0); + + qts_trusted_touch_set_vm_state(qts_data, TRUSTED_TOUCH_PVM_INIT); +} + +static int qts_clk_prepare_enable(struct qts_data *qts_data) +{ + int ret; + + ret = clk_prepare_enable(qts_data->iface_clk); + if (ret) { + pr_err("error on clk_prepare_enable(iface_clk):%d\n", ret); + return ret; + } + + ret = clk_prepare_enable(qts_data->core_clk); + if (ret) { + clk_disable_unprepare(qts_data->iface_clk); + pr_err("error clk_prepare_enable(core_clk):%d\n", ret); + } + return ret; +} + +static void qts_clk_disable_unprepare(struct qts_data *qts_data) +{ + clk_disable_unprepare(qts_data->core_clk); + clk_disable_unprepare(qts_data->iface_clk); +} + +static int qts_bus_get(struct qts_data *qts_data) +{ + int rc = 0; + struct device *dev = NULL; + + if (qts_data->schedule_suspend) + cancel_work_sync(&qts_data->suspend_work); + if (qts_data->schedule_resume) + cancel_work_sync(&qts_data->resume_work); + + reinit_completion(&qts_data->trusted_touch_powerdown); + + qts_enable_reg(qts_data, true); + + if (qts_data->bus_type == QTS_BUS_TYPE_I2C) + dev = qts_data->client->adapter->dev.parent; + else + dev = qts_data->spi->master->dev.parent; + + mutex_lock(&qts_data->qts_clk_io_ctrl_mutex); + rc = pm_runtime_get_sync(dev); + if (rc >= 0 && qts_data->core_clk != NULL && + qts_data->iface_clk != NULL) { + rc = qts_clk_prepare_enable(qts_data); + if (rc) + pm_runtime_put_sync(dev); + } + + mutex_unlock(&qts_data->qts_clk_io_ctrl_mutex); + return rc; +} + +static void qts_bus_put(struct qts_data *qts_data) +{ + struct device *dev = NULL; + + if (qts_data->bus_type == QTS_BUS_TYPE_I2C) + dev = qts_data->client->adapter->dev.parent; + else + dev = qts_data->spi->master->dev.parent; + + mutex_lock(&qts_data->qts_clk_io_ctrl_mutex); + if (qts_data->core_clk != NULL && qts_data->iface_clk != NULL) + qts_clk_disable_unprepare(qts_data); + pm_runtime_put_sync(dev); + mutex_unlock(&qts_data->qts_clk_io_ctrl_mutex); + complete(&qts_data->trusted_touch_powerdown); + qts_enable_reg(qts_data, false); +} + +static struct gh_notify_vmid_desc *qts_vm_get_vmid(gh_vmid_t vmid) +{ + struct gh_notify_vmid_desc *vmid_desc; + + vmid_desc = kzalloc(offsetof(struct gh_notify_vmid_desc, + vmid_entries[1]), GFP_KERNEL); + if (!vmid_desc) + return ERR_PTR(ENOMEM); + + vmid_desc->n_vmid_entries = 1; + vmid_desc->vmid_entries[0].vmid = vmid; + return vmid_desc; +} + +static void qts_trusted_touch_pvm_vm_mode_disable(struct qts_data *qts_data) +{ + int rc = 0; + + atomic_set(&qts_data->trusted_touch_transition, 1); + + if (atomic_read(&qts_data->trusted_touch_abort_status)) { + qts_trusted_touch_abort_pvm(qts_data); + return; + } + + if (qts_trusted_touch_get_vm_state(qts_data) != PVM_ALL_RESOURCES_RELEASE_NOTIFIED) + pr_info("all release notifications are not received yet\n"); + + if (qts_data->vendor_ops.pre_la_tui_disable) + qts_data->vendor_ops.pre_la_tui_disable(qts_data->vendor_data); + +#if (KERNEL_VERSION(6, 1, 0) <= LINUX_VERSION_CODE) + rc = ghd_rm_mem_reclaim(qts_data->vm_info->vm_mem_handle, 0); +#else + rc = gh_rm_mem_reclaim(qts_data->vm_info->vm_mem_handle, 0); +#endif + + if (rc) { + pr_err("Trusted touch VM mem reclaim failed rc:%d\n", rc); + goto error; + } + qts_trusted_touch_set_vm_state(qts_data, PVM_IOMEM_RECLAIMED); + qts_data->vm_info->vm_mem_handle = 0; + pr_debug("vm mem reclaim success!\n"); + + rc = gh_irq_reclaim(qts_data->vm_info->irq_label); + if (rc) { + pr_err("failed to reclaim irq on pvm rc:%d\n", rc); + goto error; + } + qts_trusted_touch_set_vm_state(qts_data, PVM_IRQ_RECLAIMED); + pr_debug("vm irq reclaim success!\n"); + + if (qts_data->vendor_ops.enable_touch_irq) + qts_data->vendor_ops.enable_touch_irq(qts_data->vendor_data, true); + + qts_trusted_touch_set_vm_state(qts_data, PVM_INTERRUPT_ENABLED); + qts_bus_put(qts_data); + atomic_set(&qts_data->trusted_touch_transition, 0); + qts_trusted_touch_set_vm_state(qts_data, PVM_I2C_RESOURCE_RELEASED); + qts_trusted_touch_set_vm_state(qts_data, TRUSTED_TOUCH_PVM_INIT); + atomic_set(&qts_data->trusted_touch_enabled, 0); + + if (qts_data->vendor_ops.post_la_tui_disable) + qts_data->vendor_ops.post_la_tui_disable(qts_data->vendor_data); + + pr_info("Irq, iomem are reclaimed and trusted touch disabled\n"); + return; +error: + qts_trusted_touch_abort_handler(qts_data, + TRUSTED_TOUCH_EVENT_RECLAIM_FAILURE); +} + +static void qts_vm_irq_on_release_callback(void *data, + unsigned long notif_type, + enum gh_irq_label label) +{ + struct qts_data *qts_data = data; + + if (notif_type != GH_RM_NOTIF_VM_IRQ_RELEASED) { + pr_err("invalid notification type\n"); + return; + } + + if (qts_trusted_touch_get_vm_state(qts_data) == PVM_IOMEM_RELEASE_NOTIFIED) + qts_trusted_touch_set_vm_state(qts_data, PVM_ALL_RESOURCES_RELEASE_NOTIFIED); + else + qts_trusted_touch_set_vm_state(qts_data, PVM_IRQ_RELEASE_NOTIFIED); +} + +static void qts_vm_mem_on_release_handler(enum gh_mem_notifier_tag tag, + unsigned long notif_type, void *entry_data, void *notif_msg) +{ + struct gh_rm_notif_mem_released_payload *release_payload; + struct trusted_touch_vm_info *vm_info; + struct qts_data *qts_data; + + qts_data = (struct qts_data *)entry_data; + vm_info = qts_data->vm_info; + if (!vm_info) { + pr_err("Invalid vm_info\n"); + return; + } + + if (notif_type != GH_RM_NOTIF_MEM_RELEASED) { + pr_err("Invalid notification type\n"); + return; + } + + if (tag != vm_info->mem_tag) { + pr_err("Invalid tag\n"); + return; + } + + if (!entry_data || !notif_msg) { + pr_err("Invalid data or notification message\n"); + return; + } + + release_payload = (struct gh_rm_notif_mem_released_payload *)notif_msg; + if (release_payload->mem_handle != vm_info->vm_mem_handle) { + pr_err("Invalid mem handle detected\n"); + return; + } + + if (qts_trusted_touch_get_vm_state(qts_data) == PVM_IRQ_RELEASE_NOTIFIED) + qts_trusted_touch_set_vm_state(qts_data, PVM_ALL_RESOURCES_RELEASE_NOTIFIED); + else + qts_trusted_touch_set_vm_state(qts_data, PVM_IOMEM_RELEASE_NOTIFIED); +} + +static int qts_vm_mem_lend(struct qts_data *qts_data) +{ + struct gh_acl_desc *acl_desc; + struct gh_sgl_desc *sgl_desc; + struct gh_notify_vmid_desc *vmid_desc; + gh_memparcel_handle_t mem_handle; + gh_vmid_t trusted_vmid; + int rc = 0; + + acl_desc = qts_vm_get_acl(GH_TRUSTED_VM); + if (IS_ERR(acl_desc)) { + pr_err("Failed to get acl of IO memories for Trusted touch\n"); + rc = PTR_ERR(acl_desc); + return rc; + } + + sgl_desc = qts_vm_get_sgl(qts_data->vm_info); + if (IS_ERR(sgl_desc)) { + pr_err("Failed to get sgl of IO memories for Trusted touch\n"); + rc = PTR_ERR(sgl_desc); + goto sgl_error; + } + +#if (KERNEL_VERSION(6, 1, 0) <= LINUX_VERSION_CODE) + rc = ghd_rm_mem_lend(GH_RM_MEM_TYPE_IO, 0, TRUSTED_TOUCH_MEM_LABEL, + acl_desc, sgl_desc, NULL, &mem_handle); +#else + rc = gh_rm_mem_lend(GH_RM_MEM_TYPE_IO, 0, TRUSTED_TOUCH_MEM_LABEL, + acl_desc, sgl_desc, NULL, &mem_handle); +#endif + + if (rc) { + pr_err("Failed to lend IO memories for Trusted touch rc:%d\n", rc); + goto error; + } + + pr_debug("vm mem lend success\n"); + + qts_trusted_touch_set_vm_state(qts_data, PVM_IOMEM_LENT); + +#if (KERNEL_VERSION(6, 1, 0) <= LINUX_VERSION_CODE) + ghd_rm_get_vmid(GH_TRUSTED_VM, &trusted_vmid); +#else + gh_rm_get_vmid(GH_TRUSTED_VM, &trusted_vmid); +#endif + + vmid_desc = qts_vm_get_vmid(trusted_vmid); + + rc = gh_rm_mem_notify(mem_handle, GH_RM_MEM_NOTIFY_RECIPIENT_SHARED, + qts_data->vm_info->mem_tag, vmid_desc); + if (rc) { + pr_err("Failed to notify mem lend to hypervisor rc:%d\n", rc); + goto vmid_error; + } + + qts_trusted_touch_set_vm_state(qts_data, PVM_IOMEM_LENT_NOTIFIED); + + qts_data->vm_info->vm_mem_handle = mem_handle; +vmid_error: + kfree(vmid_desc); +error: + kfree(sgl_desc); +sgl_error: + kfree(acl_desc); + + return rc; +} + +static int qts_trusted_touch_pvm_vm_mode_enable(struct qts_data *qts_data) +{ + int rc = 0; + struct trusted_touch_vm_info *vm_info = qts_data->vm_info; + + atomic_set(&qts_data->trusted_touch_transition, 1); + mutex_lock(&qts_data->transition_lock); + + if (qts_data->suspended) { + pr_err("Invalid power state for operation\n"); + atomic_set(&qts_data->trusted_touch_transition, 0); + rc = -EPERM; + goto error; + } + + if (qts_data->vendor_ops.pre_la_tui_enable) + qts_data->vendor_ops.pre_la_tui_enable(qts_data->vendor_data); + + /* i2c session start and resource acquire */ + if (qts_bus_get(qts_data) < 0) { + pr_err("qts_bus_get failed\n"); + rc = -EIO; + goto error; + } + + qts_trusted_touch_set_vm_state(qts_data, PVM_I2C_RESOURCE_ACQUIRED); + if (qts_data->vendor_ops.enable_touch_irq) + qts_data->vendor_ops.enable_touch_irq(qts_data->vendor_data, false); + qts_trusted_touch_set_vm_state(qts_data, PVM_INTERRUPT_DISABLED); + + rc = qts_vm_mem_lend(qts_data); + if (rc) { + pr_err("Failed to lend memory\n"); + goto abort_handler; + } + pr_debug("vm mem lend success\n"); + + if (atomic_read(&qts_data->delayed_pvm_probe_pending)) { + if (qts_data->vendor_ops.get_irq_num) + qts_data->irq = qts_data->vendor_ops.get_irq_num(qts_data->vendor_data); + + atomic_set(&qts_data->delayed_tvm_probe_pending, 0); + } + + rc = gh_irq_lend_v2(vm_info->irq_label, vm_info->vm_name, + qts_data->irq, &qts_vm_irq_on_release_callback, qts_data); + if (rc) { + pr_err("Failed to lend irq\n"); + goto abort_handler; + } + + pr_debug("vm irq lend success for irq:%d\n", qts_data->irq); + qts_trusted_touch_set_vm_state(qts_data, PVM_IRQ_LENT); + + rc = gh_irq_lend_notify(vm_info->irq_label); + if (rc) { + pr_err("Failed to notify irq\n"); + goto abort_handler; + } + qts_trusted_touch_set_vm_state(qts_data, PVM_IRQ_LENT_NOTIFIED); + + if (qts_data->vendor_ops.post_la_tui_enable) + qts_data->vendor_ops.post_la_tui_enable(qts_data->vendor_data); + + mutex_unlock(&qts_data->transition_lock); + atomic_set(&qts_data->trusted_touch_transition, 0); + atomic_set(&qts_data->trusted_touch_enabled, 1); + pr_info("Irq, iomem are lent and trusted touch enabled\n"); + return rc; + +abort_handler: + qts_trusted_touch_abort_handler(qts_data, TRUSTED_TOUCH_EVENT_LEND_FAILURE); + +error: + mutex_unlock(&qts_data->transition_lock); + return rc; +} + +static int qts_handle_trusted_touch_pvm(struct qts_data *qts_data, int value) +{ + int err = 0; + + switch (value) { + case 0: + if (atomic_read(&qts_data->trusted_touch_enabled) == 0 && + (atomic_read(&qts_data->trusted_touch_abort_status) == 0)) { + pr_err("Trusted touch is already disabled\n"); + break; + } + if (atomic_read(&qts_data->trusted_touch_mode) == + TRUSTED_TOUCH_VM_MODE) { + qts_trusted_touch_pvm_vm_mode_disable(qts_data); + } else { + pr_err("Unsupported trusted touch mode\n"); + } + break; + + case 1: + if (atomic_read(&qts_data->trusted_touch_enabled)) { + pr_err("Trusted touch usecase underway\n"); + err = -EBUSY; + break; + } + if (atomic_read(&qts_data->trusted_touch_mode) == + TRUSTED_TOUCH_VM_MODE) { + err = qts_trusted_touch_pvm_vm_mode_enable(qts_data); + } else { + pr_err("Unsupported trusted touch mode\n"); + } + break; + + default: + pr_err("unsupported value: %lu\n", value); + err = -EINVAL; + break; + } + return err; +} + +#endif + +static void qts_trusted_touch_event_notify(struct qts_data *qts_data, int event) +{ + atomic_set(&qts_data->trusted_touch_event, event); + sysfs_notify(&qts_data->dev->kobj, NULL, "trusted_touch_event"); +} + +static void qts_trusted_touch_abort_handler(struct qts_data *qts_data, int error) +{ + atomic_set(&qts_data->trusted_touch_abort_status, error); + pr_info("TUI session aborted with failure:%d\n", error); + qts_trusted_touch_event_notify(qts_data, error); +#ifdef CONFIG_ARCH_QTI_VM + pr_info("Resetting touch controller\n"); + if (qts_trusted_touch_get_vm_state(qts_data) >= TVM_IOMEM_ACCEPTED && + error == TRUSTED_TOUCH_EVENT_I2C_FAILURE) { + pr_info("Resetting touch controller\n"); + qts_trusted_touch_reset_gpio_toggle(qts_data); + } +#endif +} + +static int qts_vm_init(struct qts_data *qts_data) +{ + int rc = 0; + struct trusted_touch_vm_info *vm_info; + void *mem_cookie; + + rc = qts_populate_vm_info(qts_data); + if (rc) { + pr_err("Cannot setup vm pipeline\n"); + rc = -EINVAL; + goto fail; + } + + vm_info = qts_data->vm_info; +#ifdef CONFIG_ARCH_QTI_VM + mem_cookie = gh_mem_notifier_register(vm_info->mem_tag, + qts_vm_mem_on_lend_handler, qts_data); + if (!mem_cookie) { + pr_err("Failed to register on lend mem notifier\n"); + rc = -EINVAL; + goto init_fail; + } + vm_info->mem_cookie = mem_cookie; + rc = gh_irq_wait_for_lend_v2(vm_info->irq_label, GH_PRIMARY_VM, + &qts_vm_irq_on_lend_callback, qts_data); + qts_trusted_touch_set_vm_state(qts_data, TRUSTED_TOUCH_TVM_INIT); +#else + mem_cookie = gh_mem_notifier_register(vm_info->mem_tag, + qts_vm_mem_on_release_handler, qts_data); + if (!mem_cookie) { + pr_err("Failed to register on release mem notifier\n"); + rc = -EINVAL; + goto init_fail; + } + vm_info->mem_cookie = mem_cookie; + qts_trusted_touch_set_vm_state(qts_data, TRUSTED_TOUCH_PVM_INIT); +#endif + return rc; +init_fail: + qts_vm_deinit(qts_data); +fail: + return rc; +} + +static void qts_dt_parse_trusted_touch_info(struct qts_data *qts_data) +{ + struct device_node *np = qts_data->dev->of_node; + int rc = 0; + const char *selection; + const char *environment; + + rc = of_property_read_string(np, "qts,trusted-touch-mode", &selection); + if (rc) { + pr_err("No trusted touch mode selection made\n"); + atomic_set(&qts_data->trusted_touch_mode, + TRUSTED_TOUCH_MODE_NONE); + return; + } + + if (!strcmp(selection, "vm_mode")) { + atomic_set(&qts_data->trusted_touch_mode, + TRUSTED_TOUCH_VM_MODE); + pr_debug("Selected trusted touch mode to VM mode\n"); + } else { + atomic_set(&qts_data->trusted_touch_mode, + TRUSTED_TOUCH_MODE_NONE); + pr_err("Invalid trusted_touch mode\n"); + } + + rc = of_property_read_string(np, "qts,touch-environment", + &environment); + if (rc) + pr_err("No trusted touch mode environment\n"); + + qts_data->touch_environment = environment; + qts_data->tui_supported = true; + pr_debug("Trusted touch environment:%s\n", qts_data->touch_environment); +} + +static void qts_trusted_touch_init(struct qts_data *qts_data) +{ + int rc = 0; + + atomic_set(&qts_data->trusted_touch_initialized, 0); + qts_dt_parse_trusted_touch_info(qts_data); + + if (atomic_read(&qts_data->trusted_touch_mode) == TRUSTED_TOUCH_MODE_NONE) + return; + + init_completion(&qts_data->trusted_touch_powerdown); + + /* Get clocks */ + qts_data->core_clk = devm_clk_get(qts_data->dev->parent, "m-ahb"); + + if (IS_ERR(qts_data->core_clk)) { + qts_data->core_clk = NULL; + pr_err("core_clk is not defined\n"); + } + + qts_data->iface_clk = devm_clk_get(qts_data->dev->parent, "se-clk"); + + if (IS_ERR(qts_data->iface_clk)) { + qts_data->iface_clk = NULL; + pr_err("iface_clk is not defined\n"); + } + + if (atomic_read(&qts_data->trusted_touch_mode) == + TRUSTED_TOUCH_VM_MODE) { + rc = qts_vm_init(qts_data); + if (rc) + pr_err("Failed to init VM\n"); + } + atomic_set(&qts_data->trusted_touch_initialized, 1); +} + +static bool qts_ts_is_primary(struct kobject *kobj) +{ + char *path = NULL; + + if (!kobj) + return true; + + path = kobject_get_path(kobj, GFP_KERNEL); + + if (strstr(path, "primary")) + return true; + else + return false; +} + +static ssize_t trusted_touch_enable_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + struct qts_data *qts_data; + u32 idx = qts_ts_is_primary(kobj) ? 0 : 1; + + qts_data = &qts_data_entries->info[idx]; + + return scnprintf(buf, PAGE_SIZE, "%d", + atomic_read(&qts_data->trusted_touch_enabled)); +} + +static ssize_t trusted_touch_enable_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct qts_data *qts_data; + unsigned long value; + int err = 0; + u32 idx = qts_ts_is_primary(kobj) ? 0 : 1; + + if (count > 2) + return -EINVAL; + + err = kstrtoul(buf, 10, &value); + if (err != 0) + return err; + + qts_data = &qts_data_entries->info[idx]; + + if (!atomic_read(&qts_data->trusted_touch_initialized)) + return -EIO; + + pr_info("TUI trusted_touch_enable:%d\n", value); + +#ifdef CONFIG_ARCH_QTI_VM + err = qts_handle_trusted_touch_tvm(qts_data, value); + if (err) { + pr_err("Failed to handle trusted touch in tvm\n"); + return -EINVAL; + } +#else + err = qts_handle_trusted_touch_pvm(qts_data, value); + if (err) { + pr_err("Failed to handle trusted touch in pvm\n"); + return -EINVAL; + } +#endif + err = count; + return err; +} + +static ssize_t trusted_touch_event_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + struct qts_data *qts_data; + u32 idx = qts_ts_is_primary(kobj) ? 0 : 1; + + qts_data = &qts_data_entries->info[idx]; + + return scnprintf(buf, PAGE_SIZE, "%d", + atomic_read(&qts_data->trusted_touch_event)); +} + +static ssize_t trusted_touch_event_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct qts_data *qts_data; + unsigned long value; + int err = 0; + u32 idx = qts_ts_is_primary(kobj) ? 0 : 1; + + if (count > 2) + return -EINVAL; + + err = kstrtoul(buf, 10, &value); + if (err != 0) + return err; + + qts_data = &qts_data_entries->info[idx]; + + if (!atomic_read(&qts_data->trusted_touch_initialized)) + return -EIO; + + if (value) + return -EIO; + + atomic_set(&qts_data->trusted_touch_event, value); + + return count; +} + +static ssize_t trusted_touch_type_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + struct qts_data *qts_data; + u32 idx = qts_ts_is_primary(kobj) ? 0 : 1; + + qts_data = &qts_data_entries->info[idx]; + + return scnprintf(buf, PAGE_SIZE, "%s", qts_data->vm_info->trusted_touch_type); +} + +static ssize_t trusted_touch_device_path_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct qts_data *qts_data; + char *path = NULL; + u32 idx = qts_ts_is_primary(kobj) ? 0 : 1; + + qts_data = &qts_data_entries->info[idx]; + + if (qts_data && qts_data->dev) + path = kobject_get_path(&qts_data->dev->kobj, GFP_KERNEL); + + return scnprintf(buf, PAGE_SIZE, "%s", path ? path : ""); +} + +static struct kobj_attribute trusted_touch_enable_attr = + __ATTR(trusted_touch_enable, 0664, trusted_touch_enable_show, trusted_touch_enable_store); + +static struct kobj_attribute trusted_touch_event_attr = + __ATTR(trusted_touch_event, 0664, trusted_touch_event_show, trusted_touch_event_store); + +static struct kobj_attribute trusted_touch_type_attr = + __ATTR(trusted_touch_type, 0664, trusted_touch_type_show, NULL); + +static struct kobj_attribute trusted_touch_device_path_attr = + __ATTR(trusted_touch_device_path, 0444, trusted_touch_device_path_show, NULL); + +static struct attribute *qts_attributes[] = { + &trusted_touch_enable_attr.attr, + &trusted_touch_event_attr.attr, + &trusted_touch_type_attr.attr, + &trusted_touch_device_path_attr.attr, + NULL, +}; + +static struct attribute_group qts_attribute_group = { + .attrs = qts_attributes, +}; + +static int qts_create_sysfs(struct qts_data *qts_data) +{ + int ret = 0; + struct kobject *qts_kobj; + struct kobject *client_kobj; + + qts_kobj = &qts_data_entries->qts_kset->kobj; + + if (qts_data->client_type == QTS_CLIENT_PRIMARY_TOUCH) { + + client_kobj = kobject_create_and_add("primary", qts_kobj); + if (!client_kobj) { + pr_err("primary kobject_create_and_add failed\n"); + return -ENOMEM; + } + + ret = sysfs_create_group(client_kobj, &qts_attribute_group); + if (ret) { + pr_err("[EX]: sysfs_create_group() failed!!\n"); + sysfs_remove_group(client_kobj, &qts_attribute_group); + return -ENOMEM; + } + } else if (qts_data->client_type == QTS_CLIENT_SECONDARY_TOUCH) { + + client_kobj = kobject_create_and_add("secondary", qts_kobj); + if (!client_kobj) { + pr_err("secondary kobject_create_and_add failed\n"); + return -ENOMEM; + } + + ret = sysfs_create_group(client_kobj, &qts_attribute_group); + if (ret) { + pr_err("[EX]: sysfs_create_group() failed!!\n"); + sysfs_remove_group(client_kobj, &qts_attribute_group); + return -ENOMEM; + } + } + + pr_debug("sysfs_create_group() succeeded\n"); + return ret; +} + +static int qts_ts_check_dt(struct device_node *np) +{ + int i; + int count; + struct device_node *node; + struct drm_panel *panel; + + count = of_count_phandle_with_args(np, "panel", NULL); + if (count <= 0) + return 0; + + for (i = 0; i < count; i++) { + node = of_parse_phandle(np, "panel", i); + panel = of_drm_find_panel(node); + of_node_put(node); + if (!IS_ERR(panel)) { + active_panel = panel; + return 0; + } + } + return PTR_ERR(panel); +} + +static void qts_power_source_init(struct qts_data *qts_data) +{ + qts_data->vdd = regulator_get(qts_data->dev, "vdd"); + if (IS_ERR_OR_NULL(qts_data->vdd)) + pr_debug("get vdd regulator failed\n"); + + qts_data->avdd = regulator_get(qts_data->dev, "avdd"); + if (IS_ERR_OR_NULL(qts_data->avdd)) + pr_debug("get avdd regulator failed\n"); +} + +#ifndef CONFIG_ARCH_QTI_VM +static int qts_enable_reg(struct qts_data *qts_data, bool enable) +{ + int ret = 0; + + if (IS_ERR_OR_NULL(qts_data->vdd)) { + pr_err("vdd is invalid\n"); + return ret; + } + + if (enable) { + ret = regulator_enable(qts_data->vdd); + if (ret) { + pr_err("enable vdd regulator failed,ret=%d\n", ret); + goto error; + } + + if (!IS_ERR_OR_NULL(qts_data->avdd)) { + ret = regulator_enable(qts_data->avdd); + if (ret) { + pr_err("enable avdd regulator failed,ret=%d\n", ret); + goto error_avdd_en; + } + } + } else { + ret = regulator_disable(qts_data->vdd); + if (ret) + pr_err("disable vdd regulator failed,ret=%d\n", ret); + + if (!IS_ERR_OR_NULL(qts_data->avdd)) { + ret = regulator_disable(qts_data->avdd); + if (ret) + pr_err("disable avdd regulator failed,ret=%d\n", ret); + } + } + + pr_debug("enable %d completed\n", enable); + return ret; + +error_avdd_en: + (void)regulator_disable(qts_data->vdd); +error: + return ret; +} + +#endif + +static void qts_ts_suspend(struct qts_data *qts_data) +{ + int rc = 0; + + if (qts_data->suspended) { + pr_warn("already in suspend state\n"); + return; + } + + if (qts_data->tui_supported) { + if (atomic_read(&qts_data->trusted_touch_transition) + || atomic_read(&qts_data->trusted_touch_enabled)) + wait_for_completion_interruptible(&qts_data->trusted_touch_powerdown); + } + mutex_lock(&qts_data->transition_lock); + + rc = qts_data->vendor_ops.suspend(qts_data->vendor_data); + if (rc) + pr_err("suspend failed, rc = %d\n", rc); + + qts_data->suspended = true; + mutex_unlock(&qts_data->transition_lock); +} + +static void qts_ts_resume(struct qts_data *qts_data) +{ + int rc = 0; + + if (!qts_data->suspended) { + pr_warn("Already in awake state\n"); + return; + } + + if (qts_data->tui_supported) + if (atomic_read(&qts_data->trusted_touch_transition)) + wait_for_completion_interruptible(&qts_data->trusted_touch_powerdown); + + mutex_lock(&qts_data->transition_lock); + + rc = qts_data->vendor_ops.resume(qts_data->vendor_data); + if (rc) + pr_err("resume failed, rc = %d\n", rc); + + qts_data->suspended = false; + mutex_unlock(&qts_data->transition_lock); +} + +static void qts_resume_work(struct work_struct *work) +{ + struct qts_data *qts_data = container_of(work, struct qts_data, + resume_work); + + qts_ts_resume(qts_data); +} + +static void qts_suspend_work(struct work_struct *work) +{ + struct qts_data *qts_data = container_of(work, struct qts_data, + suspend_work); + + qts_ts_suspend(qts_data); +} + +static void qts_panel_notifier_callback(enum panel_event_notifier_tag tag, + struct panel_event_notification *notification, void *client_data) +{ + struct qts_data *qts_data = client_data; + + if (!notification) { + pr_err("Invalid notification\n"); + return; + } + + pr_debug("Notification type:%d, early_trigger:%d\n", + notification->notif_type, notification->notif_data.early_trigger); + + switch (notification->notif_type) { + case DRM_PANEL_EVENT_UNBLANK: + if (notification->notif_data.early_trigger) { + pr_debug("resume notification pre commit\n"); + } else { + if (qts_data->schedule_resume) + queue_work(qts_data->ts_workqueue, &qts_data->resume_work); + else + qts_ts_resume(qts_data); + } + break; + case DRM_PANEL_EVENT_BLANK: + if (notification->notif_data.early_trigger) { + if (qts_data->schedule_resume) + cancel_work_sync(&qts_data->resume_work); + if (qts_data->schedule_suspend) + queue_work(qts_data->ts_workqueue, &qts_data->suspend_work); + else + qts_ts_suspend(qts_data); + } else { + pr_debug("suspend notification post commit\n"); + } + break; + case DRM_PANEL_EVENT_BLANK_LP: + pr_debug("received lp event\n"); + break; + case DRM_PANEL_EVENT_FPS_CHANGE: + pr_debug("Received fps change old fps:%d new fps:%d\n", + notification->notif_data.old_fps, + notification->notif_data.new_fps); + break; + default: + pr_debug("notification serviced :%d\n", + notification->notif_type); + break; + } +} + +static void qts_ts_register_for_panel_events(struct qts_data *qts_data) +{ + void *cookie = NULL; + + if (qts_data->client_type != QTS_CLIENT_PRIMARY_TOUCH) { + pr_err("Invalid touch type\n"); + return; + } + + cookie = panel_event_notifier_register(PANEL_EVENT_NOTIFICATION_PRIMARY, + PANEL_EVENT_NOTIFIER_CLIENT_PRIMARY_TOUCH, qts_data->panel, + &qts_panel_notifier_callback, qts_data); + if (!cookie) { + pr_err("Failed to register for panel events\n"); + return; + } + + pr_debug("registered for panel notifications panel: 0x%x\n", qts_data->panel); + + qts_data->notifier_cookie = cookie; +} + +int qts_client_register(struct qts_vendor_data qts_vendor_data) +{ + struct qts_data *qts_data; + struct device_node *dp; + int rc = 0; + + if (!qts_data_entries) { + pr_debug("QTS client register\n"); + qts_data_entries = kzalloc(sizeof(*qts_data_entries), GFP_KERNEL); + if (!qts_data_entries) { + pr_err("mem allocation failed\n"); + return -EPROBE_DEFER; + } + mutex_init(&qts_data_entries->qts_data_entries_lock); + qts_data_entries->qts_kset = kset_create_and_add("qts", NULL, kernel_kobj); + if (!qts_data_entries->qts_kset) { + pr_err("qts kset create failed\n"); + return -ENOMEM; + } + } + + mutex_lock(&qts_data_entries->qts_data_entries_lock); + + if (qts_vendor_data.bus_type == QTS_BUS_TYPE_I2C) + dp = qts_vendor_data.client->dev.of_node; + else + dp = qts_vendor_data.spi->dev.of_node; + + rc = qts_ts_check_dt(dp); + if (rc) { + pr_debug("qts_ts_check_dt failed, rc = %d\n", rc); + goto qts_register_end; + } + + pr_debug("QTS client register starts\n"); + qts_data = &qts_data_entries->info[qts_vendor_data.client_type]; + + qts_data->client = qts_vendor_data.client; + qts_data->spi = qts_vendor_data.spi; + + if (qts_vendor_data.bus_type == QTS_BUS_TYPE_I2C) + qts_data->dev = &qts_data->client->dev; + else + qts_data->dev = &qts_data->spi->dev; + + qts_data->bus_type = qts_vendor_data.bus_type; + qts_data->client_type = qts_vendor_data.client_type; + qts_data->dp = dp; + qts_data->vendor_data = qts_vendor_data.vendor_data; + qts_data->panel = active_panel; + qts_data->vendor_ops = qts_vendor_data.qts_vendor_ops; + qts_data->schedule_suspend = qts_vendor_data.schedule_suspend; + qts_data->schedule_resume = qts_vendor_data.schedule_resume; + + qts_trusted_touch_init(qts_data); + + mutex_init(&(qts_data->qts_clk_io_ctrl_mutex)); + if (qts_data->tui_supported) + qts_create_sysfs(qts_data); + + mutex_init(&qts_data->transition_lock); + +#ifdef CONFIG_ARCH_QTI_VM + atomic_set(&qts_data->delayed_tvm_probe_pending, 1); + goto qts_register_end; +#else + atomic_set(&qts_data->delayed_pvm_probe_pending, 1); +#endif + + qts_power_source_init(qts_data); + + qts_data->ts_workqueue = create_singlethread_workqueue("qts_wq"); + if (!qts_data->ts_workqueue) + pr_err("create qts workqueue fail\n"); + + if (qts_data->ts_workqueue && qts_data->schedule_resume) + INIT_WORK(&qts_data->resume_work, qts_resume_work); + + if (qts_data->ts_workqueue && qts_data->schedule_suspend) + INIT_WORK(&qts_data->suspend_work, qts_suspend_work); + + qts_ts_register_for_panel_events(qts_data); + +qts_register_end: + pr_debug("client register end\n"); + mutex_unlock(&qts_data_entries->qts_data_entries_lock); + return rc; +} +EXPORT_SYMBOL(qts_client_register); diff --git a/qcom/opensource/touch-drivers/qts/qts_core.h b/qcom/opensource/touch-drivers/qts/qts_core.h new file mode 100644 index 0000000000..6ed9bd0d98 --- /dev/null +++ b/qcom/opensource/touch-drivers/qts/qts_core.h @@ -0,0 +1,169 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2022, Qualcomm Innovation Center, Inc. All rights reserved. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "linux/gunyah/gh_msgq.h" +#include "linux/gunyah/gh_rm_drv.h" +#include +#include +#include "qts_core_common.h" +#include +#include + +#define QTS_NAME "qti-ts" + +enum trusted_touch_mode_config { + TRUSTED_TOUCH_VM_MODE, + TRUSTED_TOUCH_MODE_NONE +}; + +enum trusted_touch_pvm_states { + TRUSTED_TOUCH_PVM_INIT, + PVM_I2C_RESOURCE_ACQUIRED, + PVM_INTERRUPT_DISABLED, + PVM_IOMEM_LENT, + PVM_IOMEM_LENT_NOTIFIED, + PVM_IRQ_LENT, + PVM_IRQ_LENT_NOTIFIED, + PVM_IOMEM_RELEASE_NOTIFIED, + PVM_IRQ_RELEASE_NOTIFIED, + PVM_ALL_RESOURCES_RELEASE_NOTIFIED, + PVM_IRQ_RECLAIMED, + PVM_IOMEM_RECLAIMED, + PVM_INTERRUPT_ENABLED, + PVM_I2C_RESOURCE_RELEASED, + TRUSTED_TOUCH_PVM_STATE_MAX +}; + +enum trusted_touch_tvm_states { + TRUSTED_TOUCH_TVM_INIT, + TVM_IOMEM_LENT_NOTIFIED, + TVM_IRQ_LENT_NOTIFIED, + TVM_ALL_RESOURCES_LENT_NOTIFIED, + TVM_IOMEM_ACCEPTED, + TVM_I2C_SESSION_ACQUIRED, + TVM_IRQ_ACCEPTED, + TVM_INTERRUPT_ENABLED, + TVM_INTERRUPT_DISABLED, + TVM_IRQ_RELEASED, + TVM_I2C_SESSION_RELEASED, + TVM_IOMEM_RELEASED, + TRUSTED_TOUCH_TVM_STATE_MAX +}; + +#define TRUSTED_TOUCH_MEM_LABEL 0x7 + +#define TOUCH_RESET_GPIO_BASE 0xf118000 +#define TOUCH_RESET_GPIO_SIZE 0x1000 +#define TOUCH_RESET_GPIO_OFFSET 0x4 +#define TOUCH_INTR_GPIO_BASE 0xf119000 +#define TOUCH_INTR_GPIO_SIZE 0x1000 +#define TOUCH_INTR_GPIO_OFFSET 0x8 + +#define TRUSTED_TOUCH_EVENT_LEND_FAILURE -1 +#define TRUSTED_TOUCH_EVENT_LEND_NOTIFICATION_FAILURE -2 +#define TRUSTED_TOUCH_EVENT_ACCEPT_FAILURE -3 +#define TRUSTED_TOUCH_EVENT_FUNCTIONAL_FAILURE -4 +#define TRUSTED_TOUCH_EVENT_RELEASE_FAILURE -5 +#define TRUSTED_TOUCH_EVENT_RECLAIM_FAILURE -6 +#define TRUSTED_TOUCH_EVENT_I2C_FAILURE -7 +#define TRUSTED_TOUCH_EVENT_NOTIFICATIONS_PENDING 5 + +struct trusted_touch_vm_info { + enum gh_irq_label irq_label; + enum gh_mem_notifier_tag mem_tag; + enum gh_vm_names vm_name; + const char *trusted_touch_type; + u32 hw_irq; + gh_memparcel_handle_t vm_mem_handle; + u32 *iomem_bases; + u32 *iomem_sizes; + u32 iomem_list_size; + void *mem_cookie; + atomic_t vm_state; +}; + +struct qts_data; + +struct qts_data { + struct i2c_client *client; + struct spi_device *spi; + struct device *dev; + struct device_node *dp; + void *vendor_data; /* vendor touch driver data */ + u32 bus_type; /*i2c or spi*/ + u32 client_type; /* primary or secondary */ + struct drm_panel *panel; + struct workqueue_struct *ts_workqueue; + struct work_struct resume_work; + struct work_struct suspend_work; + bool schedule_suspend; + bool schedule_resume; + void *notifier_cookie; + + /* Resources */ + struct regulator *vdd; + struct regulator *avdd; + u32 irq_gpio; + u32 irq_gpio_flags; + u32 reset_gpio; + u32 reset_gpio_flags; + int irq; + bool irq_disabled; + bool power_disabled; + + struct mutex transition_lock; + bool suspended; + + /* vendor callback ops */ + struct qts_vendor_callback_ops vendor_ops; + + /* TUI */ + bool tui_supported; + struct trusted_touch_vm_info *vm_info; + struct mutex qts_clk_io_ctrl_mutex; + const char *touch_environment; + struct completion trusted_touch_powerdown; + struct clk *core_clk; + struct clk *iface_clk; + atomic_t trusted_touch_initialized; + atomic_t trusted_touch_enabled; + atomic_t trusted_touch_transition; + atomic_t trusted_touch_event; + atomic_t trusted_touch_abort_status; + atomic_t delayed_tvm_probe_pending; + atomic_t delayed_pvm_probe_pending; + atomic_t trusted_touch_mode; +}; + +struct qts_data_entries; + +struct qts_data_entries { + struct kset *qts_kset; + struct qts_data info[QTS_CLIENT_MAX]; + struct mutex qts_data_entries_lock; +}; + +#ifdef CONFIG_ARCH_QTI_VM +void qts_trusted_touch_tvm_i2c_failure_report(struct qts_data *qts_data); +#endif diff --git a/qcom/opensource/touch-drivers/qts/qts_core_common.h b/qcom/opensource/touch-drivers/qts/qts_core_common.h new file mode 100644 index 0000000000..08de7c1c75 --- /dev/null +++ b/qcom/opensource/touch-drivers/qts/qts_core_common.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2022, Qualcomm Innovation Center, Inc. All rights reserved. + */ +enum qts_client { + QTS_CLIENT_PRIMARY_TOUCH, + QTS_CLIENT_SECONDARY_TOUCH, + QTS_CLIENT_MAX +}; + +enum qts_bus_type { + QTS_BUS_TYPE_NONE, + QTS_BUS_TYPE_I2C, + QTS_BUS_TYPE_SPI, + QTS_BUS_TYPE_SPI_V2, + QTS_BUS_TYPE_MAX +}; + +struct qts_vendor_callback_ops { + int (*suspend)(void *data); + int (*resume)(void *data); + int (*enable_touch_irq)(void *data, bool en); + irqreturn_t (*irq_handler)(int irq, void *data); + int (*get_irq_num)(void *data); + int (*pre_la_tui_enable)(void *data); + int (*post_la_tui_enable)(void *data); + int (*pre_la_tui_disable)(void *data); + int (*post_la_tui_disable)(void *data); + int (*pre_le_tui_enable)(void *data); + int (*post_le_tui_enable)(void *data); + int (*pre_le_tui_disable)(void *data); + int (*post_le_tui_disable)(void *data); +}; + +struct qts_vendor_data { + struct i2c_client *client; + struct spi_device *spi; + void *vendor_data; + struct qts_vendor_callback_ops qts_vendor_ops; + u32 client_type; + u32 bus_type; + bool schedule_suspend; + bool schedule_resume; +}; + +int qts_client_register(struct qts_vendor_data qts_vendor_data); diff --git a/qcom/opensource/touch-drivers/raydium/Config.h b/qcom/opensource/touch-drivers/raydium/Config.h new file mode 100644 index 0000000000..46357eec69 --- /dev/null +++ b/qcom/opensource/touch-drivers/raydium/Config.h @@ -0,0 +1,54 @@ +/* config.h + * + * Raydium TouchScreen driver. + * + * Copyright (c) 2021 Raydium tech Ltd. + * + * 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. + * + */ + +#ifndef __CONFIG_H +#define __CONFIG_H + + +/***************************************************************************** +** GLOBAL MARCO DEFINITION +******************************************************************************/ +#define MINIMUM(x, y) (((x) < (y)) ? (x) : (y)) // Compares two parameters, return minimum. +#define MAXIMUM(x, y) (((x) > (y)) ? (x) : (y)) // Compares two parameters, return maximum. +#define ABS(x) ((x) >= 0 ? (x) : -(x)) // return absolute value + +/***************************************************************************** +** GLOBAL FUNCTIONAL DEFINITION +******************************************************************************/ + +//Bootloader +#define BOOTLOADER 1 //1: with bootloader, 0: without bootloader +#define SELFTEST 1 //1: For System Selftest, 0:For Dongle Open/Short Tool +#if SELFTEST +#define SELFTEST_3X 1 +#define SELFTEST_2X 0 +#else +#define SELFTEST_3X 0 +#define SELFTEST_2X 0 +#endif +#define ENABLE_TEST_TIME_MEASURMENT 1 +#define ENABLE_TEST_TIME_MEASURMENT_CC 0 +#define ENABLE_WDT 0 +#define ENABLE_TEST_RSU_DATA_SHOW 0 +#define ENABLE_AUO_VERIFY_LOG 0 +#define ENABLE_CONTROL_OPENSHORT_WDT 0 +#define ENABLE_TEST_GPIO_MEASURMENT 0 +#endif /* end __CONFIG_H */ +/***************************************************************************** +** End Of File +******************************************************************************/ diff --git a/qcom/opensource/touch-drivers/raydium/Makefile b/qcom/opensource/touch-drivers/raydium/Makefile new file mode 100644 index 0000000000..f8be53f2de --- /dev/null +++ b/qcom/opensource/touch-drivers/raydium/Makefile @@ -0,0 +1,4 @@ +# +# Makefile for the touchscreen raydium drivers. +# +obj-$(CONFIG_TOUCHSCREEN_RM_TS) += raydium_driver.o raydium_sysfs.o raydium_fw_update.o drv_interface.o raydium_selftest.o chip_raydium/ic_drv_global.o chip_raydium/ic_drv_interface.o chip_raydium/f303_ic_control.o chip_raydium/f303_ic_test.o diff --git a/qcom/opensource/touch-drivers/raydium/chip_raydium/f303_ic_control.c b/qcom/opensource/touch-drivers/raydium/chip_raydium/f303_ic_control.c new file mode 100644 index 0000000000..2b1ae81cfa --- /dev/null +++ b/qcom/opensource/touch-drivers/raydium/chip_raydium/f303_ic_control.c @@ -0,0 +1,516 @@ +/* f303_ic_control.c + * + * Raydium TouchScreen driver. + * + * Copyright (c) 2021 Raydium tech Ltd. + * + * 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. + * + */ + +#ifdef __KERNEL__ +#include +#include +#include +#include +#include +#include +#include +#include +#else +#include +#include +#include +#endif + +#include "../drv_interface.h" +#include "ic_drv_global.h" +#include "ic_drv_interface.h" +#include "f303_ic_control.h" +#include "f303_ic_reg.h" + +unsigned char check_dev_id_3x(unsigned short u16_dev_id) +{ + unsigned int u32_read; + + if (handle_ic_read(REG_FLASHCTL_DEVID_ADDR, 4, (unsigned char *)(&u32_read), g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + if (((u32_read & 0xFFFF0000) >> 16) == u16_dev_id) { + g_u16_dev_id = DEVICE_ID_3X; + return SUCCESS; + } + DEBUGOUT("Device ID NG! 0x%x:0x%x\r\n", ((u32_read & 0xFFFF0000) >> 16), u16_dev_id); + return ERROR; +} + +unsigned char check_dev_sub_version_3x(unsigned char u8_version) +{ + unsigned int u32_read; + + if (handle_ic_read(REG_FLASHCTL_DEVID_ADDR, 4, (unsigned char *)(&u32_read), g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + DEBUGOUT("Device Sub Version 0x%x\r\n", u32_read); + if ((u32_read & 0x000000FF) == u8_version) + return SUCCESS; + DEBUGOUT("Device Sub Version NG! 0x%x:0x%x\r\n", (u32_read & 0x000000FF), u8_version); + return ERROR; +} + +unsigned char enable_ic_block_3x(void) +{ + unsigned int u32_read; + + if (handle_ic_read(REG_SYSCON_BLKEN_ADDR, 4, (unsigned char *)(&u32_read), g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + u32_read |= 0xc8000000; + /*u32_read |= (BLKEN_FIC_RB_EN | BLKEN_GPIO_RB_EN | BLKEN_SYS_RB_EN);*/ + if (handle_ic_write(REG_SYSCON_BLKEN_ADDR, 4, (unsigned char *)&u32_read, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + if (handle_ic_read(REG_SYSCON_MISCIER_ADDR, 4, (unsigned char *)(&u32_read), g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + u32_read |= 0x00000404; + /*u32_read |= (MISCIER_RB_MCU_INTO | MISCIER_RB_MCU_INT_EN);*/ + if (handle_ic_write(REG_SYSCON_MISCIER_ADDR, 4, (unsigned char *)&u32_read, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + return SUCCESS; +} + +unsigned char disable_i2c_deglitch_3x(void) +{ + unsigned int u32_buf = 0; + unsigned char u8_retry = 3, u8_comfirm_time = 3; + unsigned char u8_check = 0, u8_i = 0; + unsigned int u32_i2c_deglitch = I2CENG_AUTO_I2C_DGF_MODE2 | I2CENG_DGF_OSC_AUTOSEL | I2CENG_AUTO_I2C_DGF_MODE | I2CENG_I2CS_DGFEN_DLY_NUM(6); + + /*check I2C mode*/ + while (u8_retry--) { + u32_buf = 0; + handle_ic_read(REG_FLASHCTL_DEVID_ADDR, 4, (unsigned char *)(&u32_buf), g_u8_drv_interface, I2C_WORD_MODE); + if ((u32_buf & 0xFFFF0000) == 0xF3030000) + u8_check++; + else + break; + } + if (u8_check == 3) { + if (!g_u8_mute_i2c_err_log) + DEBUGOUT("PDA2 OK\r\n"); + return SUCCESS; + } + + u8_retry = 100; + while (u8_retry--) { + if (handle_ic_write(REG_I2CENG_ADDR, 4, (unsigned char *)(&u32_i2c_deglitch), I2C_PDA_MODE, I2C_WORD_MODE) == ERROR) { + /*DEBUGOUT("[disable_i2c_deglitch_3x] handle_ic_write I2C NG!\r\n");*/ + if (!g_u8_mute_i2c_err_log) + DEBUGOUT("[DI2CDG]-W"); + continue; + } + + u8_check = 0; + for (u8_i = 0; u8_i < u8_comfirm_time; u8_i++) { + /*check I2C*/ + u32_buf = 0; + if (handle_ic_read(REG_I2CENG_ADDR, 4, (unsigned char *)(&u32_buf), I2C_PDA_MODE, I2C_WORD_MODE) == ERROR) { + /*DEBUGOUT("[disable_i2c_deglitch_3x] 2.handle_ic_read I2C NG!\r\n");*/ + if (!g_u8_mute_i2c_err_log) + DEBUGOUT("[DI2CDG]-R"); + break; + } + + if (u32_buf == u32_i2c_deglitch) + u8_check++; + else + break; + } + + if (u8_check == u8_comfirm_time) + break; + } + if (!g_u8_mute_i2c_err_log) + DEBUGOUT("\r\n"); + + if (u8_retry == 0) + return ERROR; + + u32_buf = GPIO_DEGLITCH_EN(3); + if (handle_ic_write(REG_GPIO_DEGLITCH_ENABLE, 4, (unsigned char *)(&u32_buf), I2C_PDA_MODE, I2C_WORD_MODE) == ERROR) { + DEBUGOUT("[DI2CDG_3x] 3.handle_ic_write I2C NG!\r\n"); + return ERROR; + } + + /*Enable PDA2*/ + u32_buf = PDA2CTL_PDA2_EN | PDA2CTL_SIE2; + if (handle_ic_write(REG_PDA2CTL_ADDR, 4, (unsigned char *)(&u32_buf), I2C_PDA_MODE, I2C_WORD_MODE) == ERROR) { + DEBUGOUT("[DI2CDG_3x] 4.i2c_write_pda I2C NG!\r\n"); + return ERROR; + } + + /*Disable PDA*/ +#if SELFTEST +#ifdef __KERNEL__ + raydium_i2c_pda_set_address(RAYDIUM_PDA_I2CREG, DISABLE); +#else + sysfs_mode_control(ENABLE_PDA2); +#endif +#else + i2c_set_pda_address(I2C_EID, 0x500006, I2C_PDA2_WORD_MODE); +#endif + return SUCCESS; +} + +unsigned char stop_mcu_3x(unsigned char u8_is_tp_reset) +{ + unsigned short u16_time_out = 100; + unsigned int u32_read_data; + unsigned int u32_write = 0; + + g_u8_mute_i2c_err_log = TRUE; + if (u8_is_tp_reset) { + gpio_touch_hw_reset(); + delay_ms(35); + if (disable_i2c_deglitch_3x() == ERROR) { + DEBUGOUT("[%s] 2.DI2CDG NG!\r\n", __func__); + goto EXIT_ERROR; + } + } + + /*DEBUGOUT("[stop_mcu_3x] 1\r\n");*/ + + /*Stop MCU*/ + /*memset(wData, 0, sizeof(wData));*/ + u32_write = (MCU_HOLD | SKIP_LOAD); + if (handle_ic_write(REG_FLASHCTL_FLASH_STATE_REG_ADDR, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + goto EXIT_ERROR; + + u32_write = BLKRST_SW_RST; + if (handle_ic_write(REG_SYSCON_BLKRST_ADDR, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + goto EXIT_ERROR; + delay_ms(20); + + if (disable_i2c_deglitch_3x() == ERROR) { + /*DEBUGOUT("[stop_mcu_3x] 3.disable_i2c_deglitch_3x NG!\r\n");*/ + DEBUGOUT("[%s] 3.DI2CDG NG!\r\n", __func__); + goto EXIT_ERROR; + } + /*DEBUGOUT("[stop_mcu_3x] 2\r\n");*/ + + if (handle_ic_read(REG_FLASHCTL_FLASH_STATE_REG_ADDR, 4, (unsigned char *)(&u32_read_data), g_u8_drv_interface, I2C_WORD_MODE) == ERROR) { + DEBUGOUT("[%s] 4.Flash State NG!\r\n", __func__); + goto EXIT_ERROR; + } + + while ((u32_read_data & MCU_HOLD_STATUS) == 0 && u16_time_out-- > 0) { + delay_ms(10); + if (handle_ic_read(REG_FLASHCTL_FLASH_STATE_REG_ADDR, 4, (unsigned char *)(&u32_read_data), g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + goto EXIT_ERROR; + +#if ENABLE_WDT + Chip_WWDT_Feed(LPC_WWDT);/* reload the Watchdog timer*/ +#endif + } + + DEBUGOUT("Stop MCU=0x%X(0x%x)(%d)!!\r\n", u32_read_data, (u32_read_data & MCU_HOLD_STATUS), u16_time_out); + + if ((u32_read_data & MCU_HOLD_STATUS) == 0) { + DEBUGOUT("[%s] 4.STOP MCU NG!\r\n", __func__); + goto EXIT_ERROR; + } + + g_u8_mute_i2c_err_log = FALSE; + return SUCCESS; + +EXIT_ERROR: + g_u8_mute_i2c_err_log = FALSE; + return ERROR; +} + +unsigned char hardware_reset_3x(unsigned char u8_enable_ic_block) +{ + unsigned char u8_time_out = 200; + + DEBUGOUT("HW Reseting...\r\n"); + + gpio_touch_hw_reset(); + delay_ms(100); + + g_u8_mute_i2c_err_log = TRUE; + if (disable_i2c_deglitch_3x() == ERROR) { + /*DEBUGOUT("[hardware_reset_3x] disable_i2c_deglitch_3x NG!\r\n");*/ + DEBUGOUT("[%s] DI2CDG NG!\r\n, __func__"); + g_u8_mute_i2c_err_log = FALSE; + return ERROR; + } + g_u8_mute_i2c_err_log = FALSE; + + if (u8_enable_ic_block) { + if (enable_ic_block_3x() == ERROR) { + DEBUGOUT("HW Reset NG!!\r\n"); + return ERROR; + } + } + while (u8_time_out--) { + if (gpio_touch_int_pin_state_access()) + break; + if (u8_time_out == 0) + return ERROR; + delay_ms(1); + } + return SUCCESS; +} + +unsigned char set_fw_system_cmd_3x(unsigned int u32_sysm_cmd) +{ + unsigned char u8_time_out = 100, u8_value; + + if (handle_ic_write(FW_SYS_CMD_ADDR, 4, (unsigned char *)&u32_sysm_cmd, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + /* Wait Test Command ready*/ + while (--u8_time_out) { + delay_ms(1); + if (handle_ic_read(FW_SYS_CMD_ADDR, 1, &u8_value, g_u8_drv_interface, I2C_BYTE_MODE) == ERROR) + return ERROR; + else if (u8_value == 0) + break; + } + + if (u8_time_out == 0) + return ERROR; + + return SUCCESS; +} + +unsigned char wait_fw_state_3x(unsigned int u32_addr, unsigned int u32_state, unsigned short u16_delay, unsigned short u16_retry) +{ + unsigned int u32_read_data = 0; + + do { + if (handle_ic_read(u32_addr, 4, (unsigned char *)(&u32_read_data), g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + delay_ms(u16_delay); + u16_retry--; + +#if ENABLE_WDT + /* reload the Watchdog timer*/ + Chip_WWDT_Feed(LPC_WWDT); +#endif + } while ((u32_read_data != u32_state) && (u16_retry != 0)); + + if (u32_read_data != u32_state) + return ERROR; + return SUCCESS; +} + +unsigned char wait_T2D_done_state_3x(unsigned int u32_addr, unsigned short u16_delay, unsigned short u16_retry) +{ + unsigned int u32_read_data = 0; + + do { + if (handle_ic_read(u32_addr, 4, (unsigned char *)(&u32_read_data), g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + /*DEBUGOUT("[T2DW] ready 0x%x\r\n", u32_read_data);*/ + + delay_ms(u16_delay); + u16_retry--; + } while (((u32_read_data & 0x01) == 0) && (u16_retry != 0)); + + if ((u32_read_data & 0x01) == 0) + return ERROR; + return SUCCESS; +} + +unsigned char WriteToDriver_3x(unsigned char *p_u8_data, unsigned char u8_data_length) +{ + unsigned int u32_write = 0; + + + if (p_u8_data != NULL) { + + if (p_u8_data[0] == 0xFE) { + g_u8_PAGE_ADDR = p_u8_data[1]; + return SUCCESS; + } + + if (p_u8_data[0] == 0x35) { + u32_write = ((0x00 << 24) | (0x00 << 16) | (p_u8_data[0] << 8) | g_u8_PAGE_ADDR); + if (handle_ic_write(REG_T2D_CONFIG_2, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + u32_write = 0x0000015A; + if (handle_ic_write(REG_T2D_CONFIG_3, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + } + + if (u8_data_length == 1) /*cmd mode*/ + u32_write = ((0x00 << 24) | (p_u8_data[1] << 16) | (p_u8_data[0] << 8) | g_u8_PAGE_ADDR); + else if (u8_data_length == 2) + u32_write = ((0x01 << 24) | (p_u8_data[1] << 16) | (p_u8_data[0] << 8) | g_u8_PAGE_ADDR); + else if (u8_data_length == 5) { + /* 2A 2B*/ + + u32_write = ((0x01 << 24) | (p_u8_data[1] << 16) | (p_u8_data[0] << 8) | 0x00); + if (handle_ic_write(REG_T2D_CONFIG_2, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + u32_write = 0x0000015A; + if (handle_ic_write(REG_T2D_CONFIG_3, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + u32_write = ((0x01 << 24) | (p_u8_data[2] << 16) | (p_u8_data[0] << 8) | 0x01); + if (handle_ic_write(REG_T2D_CONFIG_2, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + u32_write = 0x0000015A; + if (handle_ic_write(REG_T2D_CONFIG_3, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + u32_write = ((0x01 << 24) | (p_u8_data[3] << 16) | (p_u8_data[0] << 8) | 0x02); + if (handle_ic_write(REG_T2D_CONFIG_2, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + u32_write = 0x0000015A; + if (handle_ic_write(REG_T2D_CONFIG_3, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + u32_write = ((0x01 << 24) | (p_u8_data[4] << 16) | (p_u8_data[0] << 8) | 0x03); + if (handle_ic_write(REG_T2D_CONFIG_2, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + u32_write = 0x0000015A; + if (handle_ic_write(REG_T2D_CONFIG_3, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + return SUCCESS; + } + + if (handle_ic_write(REG_T2D_CONFIG_2, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + u32_write = 0x0000015A; + if (handle_ic_write(REG_T2D_CONFIG_3, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + } + return SUCCESS; +} + +unsigned char ReadFromDriver_3x(unsigned char *p_u8_addr, unsigned char u8_read_len, unsigned char *p_u8_output_buf) +{ + unsigned int u32_write = 0; + + if ((p_u8_addr[0] == 0xFE) || (p_u8_addr[0] == 0xFF) || (u8_read_len > 1)) { + DEBUGOUT("[%s] no use\r\n", __func__); + return FALSE; + } + + u32_write = ((p_u8_addr[0] << 24) | (g_u8_PAGE_ADDR << 16) | (0x01 << 8) | 0x82); + DEBUGOUT("read address 0x%x\r\n", u32_write); + if (handle_ic_write(REG_T2D_R_CONFIG_1, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + if (handle_ic_read(REG_T2D_R_CONFIG_2, 4, p_u8_output_buf, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + DEBUGOUT("read data 0x%x\r\n", p_u8_output_buf[0]); + return TRUE; + +} + +unsigned char WriteDriverByTouchMode(const unsigned char *p_u8_data, uint16_t u16DataLength) +{ + unsigned short TotalDataLen = (p_u8_data[3] << 8) + p_u8_data[4] - 7;/*Valid data not include 7 byte header*/ + unsigned short u16_i = 0/*, u8_j = 0*/; + unsigned char u8_CurCmdLen = 0; + unsigned int u32_write = 0; + +/* + * unsigned char u8_read_buf[4]; + * signed char i8_retry_cnt = 3; + * unsigned char u8_error_flag = 0; + */ + + for (u16_i = 0; u16_i < (u16DataLength - 7) && u16_i < sizeof(g_u8_data_buf); u16_i++) + g_u8_data_buf[u16_i] = p_u8_data[u16_i + 7]; + if (p_u8_data[0] == 0xF3 && p_u8_data[1] == 0x01) { + /*Check Command type*/ + DEBUGOUT("Write Key\r\n"); + u32_write = 0x015AFABC; + if (handle_ic_write(REG_T2D_CONFIG_2, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + u32_write = 0x0000015A; + if (handle_ic_write(REG_T2D_CONFIG_3, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + if (p_u8_data[2] == 0x01 || + p_u8_data[2] == 0x10) { /*SPI short multiple command*/ + for (u16_i = 0; u16_i < TotalDataLen; u16_i += (u8_CurCmdLen + 1)) { + u8_CurCmdLen = g_u8_data_buf[u16_i]; + +/* + * DEBUGOUT(" i = %d\r\n", u16_i); + * if (u8_CurCmdLen == 2) + * DEBUGOUT("-Before Parse Data[%d] = 0x%02X, 0x%02X, 0x%02X, %d\r\n", u16_i, g_u8_data_buf[u16_i], g_u8_data_buf[u16_i + 1], g_u8_data_buf[u16_i + 2], u8_CurCmdLen); + * else if (u8_CurCmdLen == 3) + * DEBUGOUT("-Before Parse Data[%d] = 0x%02X, 0x%02X, 0x%02X, 0x%02X, %d\r\n", u16_i, g_u8_data_buf[u16_i], g_u8_data_buf[u16_i + 1], g_u8_data_buf[u16_i + 2], g_u8_data_b uf[u16_i + 3], u8_CurCmdLen); + * else + * DEBUGOUT("-Before Parse Data[%d] = 0x%02X, 0x%02X, %d\r\n", u16_i, g_u8_data_buf[u16_i], g_u8_data_buf[u16_i + 1], u8_CurCmdLen); + */ + + /*Check is Delay command*/ + if (g_u8_data_buf[u16_i + 1] != 0xFF) { /*Check Command ID*/ + + if (wait_T2D_done_state_3x(0x50001220, 1, 100) == ERROR) { + DEBUGOUT("[T2DW] Check T2D idle Fail\r\n"); + return ERROR; + } + + WriteToDriver_3x((unsigned char *)&g_u8_data_buf[u16_i + 1], u8_CurCmdLen); + /*delay_ms(1);*/ +/* + * i8_retry_cnt = 3; + * while (i8_retry_cnt) { + * u8_error_flag = 0; + * if (ReadFromDriver_3x((unsigned char *)&g_u8_data_buf[u16_i + 1], u8_CurCmdLen - 1 , u8_read_buf)) { + * for (u8_j = 0 ; u8_j < u8_CurCmdLen - 1 ; u8_j++) { + * if (g_u8_data_buf[u16_i + 2 + u8_j] != u8_read_buf[u8_j]) { + * u8_error_flag = 1; + * DEBUGOUT("write address:%x\r\n", g_u8_data_buf[u16_i + 1]); + * DEBUGOUT("Data:%d\r\n", g_u8_data_buf[u16_i + 2 + u8_j]); + * DEBUGOUT("g_u8_spi_cmd_read_buf[%d]:%x\r\n", u8_j, u8_read_buf[u8_j]); + * } + * } + * if (u8_error_flag == 0) + * break; + * if (u8_error_flag == 1) + * i8_retry_cnt--; + * if (i8_retry_cnt == 0) { + * DEBUGOUT("i8_retry_cnt error!\r\n"); + * return ERROR; + * } + * } else + * break; + * } + */ + } else { + DEBUGOUT("Delay\r\n"); + delay_ms(g_u8_data_buf[u16_i + 2]); + } + } + } else { + DEBUGOUT("[%s] command type not support\r\n", __func__); + return ERROR; + } + u32_write = 0x0100FABC; + if (handle_ic_write(REG_T2D_CONFIG_2, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + u32_write = 0x0000015A; + if (handle_ic_write(REG_T2D_CONFIG_3, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + } + return SUCCESS; +} + diff --git a/qcom/opensource/touch-drivers/raydium/chip_raydium/f303_ic_control.h b/qcom/opensource/touch-drivers/raydium/chip_raydium/f303_ic_control.h new file mode 100644 index 0000000000..834122a2a7 --- /dev/null +++ b/qcom/opensource/touch-drivers/raydium/chip_raydium/f303_ic_control.h @@ -0,0 +1,31 @@ +/* f303_ic_control.h + * + * Raydium TouchScreen driver. + * + * Copyright (c) 2021 Raydium tech Ltd. + * + * 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. + * + */ + +#include "ic_drv_global.h" + +#define HEADER_LENGTH (4)//length + checksum + +extern unsigned char enable_ic_block_3x(void); +extern unsigned char stop_mcu_3x(unsigned char u8_is_tp_reset); +extern unsigned char hardware_reset_3x(unsigned char u8_enable_ic_block); +extern unsigned char check_dev_id_3x(unsigned short u16_dev_id); +extern unsigned char check_dev_sub_version_3x(unsigned char u8_version); +extern unsigned char set_fw_system_cmd_3x(unsigned int u32_sysm_cmd); +extern unsigned char wait_fw_state_3x(unsigned int u32_addr, unsigned int u32_state, unsigned short u16_delay, unsigned short u16_retry); +extern unsigned char disable_i2c_deglitch_3x(void); +extern unsigned char WriteDriverByTouchMode(const unsigned char *p_u8_data, uint16_t u16DataLength); diff --git a/qcom/opensource/touch-drivers/raydium/chip_raydium/f303_ic_reg.h b/qcom/opensource/touch-drivers/raydium/chip_raydium/f303_ic_reg.h new file mode 100644 index 0000000000..9c4ca329f8 --- /dev/null +++ b/qcom/opensource/touch-drivers/raydium/chip_raydium/f303_ic_reg.h @@ -0,0 +1,281 @@ +/* f303_ic_reg.h + * + * Raydium TouchScreen driver. + * + * Copyright (c) 2021 Raydium tech Ltd. + * + * 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. + * + */ + + // ['h5000_0900], [32'hFFFF_FFFF], PRAM_LOCK +#define I2CTB_LOCK (0x00000001<<6) // [0] (R/W) I2C Table lock +#define BOTLR_LOCK (0x00000001<<5) // [0] (R/W) Boot loader lock +#define USEFW_LOCK (0x00000001<<4) // [0] (R/W) User fw lock +#define CONFIG_LOCK (0x00000001<<3) // [0] (R/W) Configuration lock +#define COMP_LOCK (0x00000001<<2) // [0] (R/W) compensation lock +#define BASEL_LOCK (0x00000001<<1) // [0] (R/W) baseline lock +#define INICO_LOCK (0x00000001<<0) // [0] (R/W) Initial code lock + +// ['h5000_0904], [32'h0000_0000], Program RAM store type, PRAM_STORE_TYPE +#define BOTLR_AREA (0x00000001<<5) // [0] (R/W) Boot loader area +#define USEFW_AREA (0x00000001<<4) // [0] (R/W) User fw area +#define CONFIG_AREA (0x00000001<<3) // [0] (R/W) Configuration area +#define COMP_AREA (0x00000001<<2) // [0] (R/W) compensation area +#define BASEL_AREA (0x00000001<<1) // [0] (R/W) baseline area +#define INICO_AREA (0x00000001<<0) // [0] (R/W) Initial code area + +// ['h5000_0918], [32'h0000_0000],Flash state control register ,FLASH_STATE_REG +#define BLDR_FINISH (0x00000001<<14) // [0] (R) MCU_HOLD status +#define MCU_HOLD_STATUS (0x00000001<<13) // [0] (R) MCU_HOLD status +#define BOOT_REGION (0x00000001<<12) // [0] (R) boot region index +#define BL_CRC_CHK (0x00000001<<11) // [0] (R/W) boot-loader area CRC check +#define FW_CRC_CHK (0x00000001<<10) // [0] (R/W) user FW CRC check +#define PARA_CRC_CHK (0x00000001<<9) // [0] (R/W) parameter area CRC check +#define COMP_CRC_CHK (0x00000001<<8) // [0] (R/W) compensation area CRC check +#define BASELINE_CRC_CHK (0x00000001<<7) // [0] (R/W) baseline area CRC check +#define INITIAL_CRC_CHK (0x00000001<<6) // [0] (R/W) initial code CRC check +#define MCU_HOLD (0x00000001<<5) // [0] (R/W) MCU hold +#define SKIP_LOAD (0x00000001<<4) // [0] (R/W) Skip all load flash action +#define FW_INICO_ERR (0x00000001<<3) // [0] (R) FW_INICO_ERR +#define FW_CG_ERR (0x00000001<<2) // [0] (R) cc bl CRC error +#define FW_CRC_ERR (0x00000001<<1) // [0] (R) FW_CRC_ERR +#define BL0_CRC_ERR (0x00000001<<0) // [0] (R) BL0_CRC_ERR + +// ['h5000_0934], [32'h0000_0000], FLASH Lock and Key Register (main) (FLKEY1) +#define FLKEY1_LOCK (0x00000000<<0) // [7:0] (R/W) FLASH Lock and Key1 Register +#define FLKEY1_KEY (0x000000A5<<0) // [7:0] (R/W) FLASH Unlock and Key1 Register:0xA5 + +// ['h5000_0938], [32'h0000_0000], FLASH Lock and Key Register (information) (FLKEY2) +#define FLKEY2_LOCK (0x00000000<<0) // [7:0] (R/W) FLASH Lock and Key3 Register +#define FLKEY2_KEY (0x000000D7<<0) // [7:0] (R/W) FLASH Unlock and Key3 Register:0xD7 + +//#define MAX_SENSING_PIN_NUM 30 + +// ['h5000_0610], [32'h0106_0300], I2C eng Register (I2CENG) +#define REG_I2CENG_ADDR 0x50000610 +#define I2CENG_AUTO_I2C_DGF_MODE2 (0x00000001<<26) // [26:26] (R/W) auto_i2c_dgf_mode2,, 1: switch def_en after next posedge SCL_dgf, 0: switch def_en control auto_i2c_dgf_mode (bit[24]). +#define I2CENG_DGF_OSC_AUTOSEL (0x00000001<<25) // [25:25] (R/W) dgf_osc_autosel,, 0: TP OSC control, 1: TP OSC/DRIVER OSC control +#define I2CENG_AUTO_I2C_DGF_MODE (0x00000001<<24) // [24:24] (R/W) dgf_en signal delay timing select 0: switch def_en after STOP signal 1: switch def_en after next posedge SCL. +#define I2CENG_I2CS_DGFEN_DLY_NUM(u7x) ((u7x&0x0000007F)<<16) // [22:16] (R/W) dgf_en signal delay number (clock by system clock ). +#define I2CENG_I2CS_DEGFIR_NUM(u7x) ((u7x&0x0000007F)<<8) // [14:8] (R/W) I2C Pad (SCL/SDA) deglitch filter number. +#define I2CENG_RB_MANUAL_I2C_DGF (0x00000001<<7) // [7:7] (R/W) I2C Pad (SCL/SDA) deglitch filter manual mode control 1: Enable 0: Disable. +#define I2CENG_RB_I2C_DGF_EN (0x00000001<<6) // [6:6] (R/W) I2C Pad (SCL/SDA) deglitch filter enable control register when rb_manual_i2c_dgf =1, 1: Enable 0: Disable. +#define I2CENG_FIRST_DAT_SEL(u2x) ((u2x&0x00000003)<<0) // [1:0] (R/W) First data request select for PDA2 read. 0: at 2nd SCL; 1: at 3th SCL; 2:at 4th SCL; 3:at 5th SCL. + +#define REG_I2C_I2CFLASHPRO 0x50000624 + +//['h5000_0628], [32'h0000_0000], PDA2 Control Register (PDA2CTL) +#define REG_PDA2CTL_ADDR 0x50000628 +#define PDA2CTL_PDA2_EN (0x00000001UL<<2) // [2:2] (R/W) PDA2 enable bit. 1: enable 0: disable. +#define PDA2CTL_SIE2 (0x00000001<<1) // [1:1] (R/W) SIE2 enable register . 1: enable 0: mask. +#define PDA2CTL_SI2 (0x00000001<<0) // [0:0] (R/W) SI2 interrupt flag (write 1 to clear). + + +//['h5000_0E1C], [32'h0000_0000], GPIO deglitch enable(GPIO_DEGLITCH) +#define REG_GPIO_DEGLITCH_ENABLE 0x50000E1C +#define GPIO_PULLH_EN(u2x) ((u2x&0x00000003)<<0) // [3:2] (R/W) 1: enable pull-high of GPIO, 0: disable pull-high of GPIO +#define GPIO_DEGLITCH_EN(u2x) ((u2x&0x00000003)<<0) // [1:0] (R/W) 1: enable deglitch function of GPIO, 0: disable deglitch function of GPIO + +#define REG_SYSCON_BLKEN_ADDR 0x40000000 +#define REG_SYSCON_BLKRST_ADDR 0x40000004 +//#define REG_SYSCON_MISCIER_ADDR 0x40000014 + +#define REG_T2D_CONFIG_1 0x5000145c +#define REG_T2D_CONFIG_2 0x50001460 +#define REG_T2D_CONFIG_3 0x50001464 +#define REG_T2D_R_CONFIG_1 0x50001468 +#define REG_T2D_R_CONFIG_2 0x5000146C + +#define MCU_HOLD (0x00000001<<5) // [0] (R/W) MCU hold +#define SKIP_LOAD (0x00000001<<4) // [0] (R/W) Skip all load flash action +#define BLKRST_SW_RST (0x00000001<<0) // [0] (R/W) 1: Software reset, all digital block will be reset +#define MCU_HOLD_STATUS (0x00000001<<13) // [0] (R) MCU_HOLD status +#define FLH_RELEASE_PD (0x00000001<<5) // [0] (R/W) Release from deep power down mode +#define BL_CRC_CHK (0x00000001<<11) // [0] (R/W) boot-loader area CRC check + +/* Base addresses */ +#define RM_PRAM_BASE (0x00000000UL) // Program, AHB +#define RM_RAM_BASE (0x20000000UL) // SRAM, AHB +#define RM_AHB_BASE (0x40000000UL) // Peripheral, AHB +#define RM_APB_BASE (0x50000000UL) // Peripheral, APB + +//#define FW_SYS_CMD_ADDR 0x20000288 +#define FW_FT_CMD_ADDR 0x20000289 +#define FW_FT_ARG0_ADDR 0x2000028A +#define FW_FT_ARG1_ADDR 0x2000028C +//#define FW_FT_IMG_ADDR 0x2000019C +//#define FW_TP_SEQ_NUM_ADDR 0x20000290 + +#define SYS_CMD_FUNC_DIS_BS_UPDATE 0x20 +#define DIS_BASELINE_UPDATE 0x00010000 + +#define SYS_CMD_DO_BL_CAL 0x5A +#define SYS_CMD_READ_CAL_FLAG 0x5B +#define SYS_CMD_DO_CC_CAL 0x5C +#define SYS_CMD_CAL_WAIT 0x5D + +#define SYS_CMD_WAKEUP_GESTURE_ENABLE 0x40 +#define SYS_CMD_WAKEUP_GESTURE_DISABLE 0x41 + +#define SYS_CMD_FT_GET_DSP_NS_PARAM 0x60 +#define SYS_CMD_FT_FUN_FLAG 0x62 +#define SYS_CMD_FT_DC_DISABLE 0x010000 +#define SYS_CMD_FT_DIG_GAIN_ENABLE 0x020000 +#define SYS_CMD_FT_TEST_LOG_EN 0x800000 + +#define FW_FT_CHANNEL_X_ADDR (PRAM_PARA_START + 24) +#define FW_FT_CHANNEL_Y_ADDR (PRAM_PARA_START + 25) +#define FW_FT_PIN_ADDR (PRAM_PARA_START + 27) +#define FW_FT_PWR_MODE_ADDR (PRAM_PARA_START + 26) +#define FW_FT_FW_VERSION (PRAM_PARA_START + 4) +#define FW_FT_SRAM_FW_VERSION 0x200006E0 + + +#define BOOT_SYNC_DATA_ADDR 0x20000200 +#define BOOT_MAIN_STATE_ADDR 0x20000204 +#define BOOT_NORMAL_STATE_ADDR 0x20000208 +#define BOOT_BURNING_STATE_ADDR 0x2000020C +#define BOOT_CMD_TYPE_ADDR 0x20000210 +#define BOOT_RET_DATA_ADDR 0x20000214 +#define BOOT_TEST_MODE_ADDR 0x20000218 + +#define FLASH_OFFSET (0x7800) +#define PRAM_BASELINE_LENGTH (0x130) +#define PRAM_COMP_LENGTH (0x304) +#define PRAM_DIS_INIT_LENGTH (0x80) +#define PRAM_PARA_LENGTH (0x174) +#define PRAM_FW_LENGTH (0x7300) +#define PRAM_BOOT_LENGTH (0x800) + +#define PRAM_BOOT_START (0x0000) +#define PRAM_DIS_INIT_START (0x7F80) +#define PRAM_COMP_START (0x7C78) +#define PRAM_PARA_START (0x7B00) +#define PRAM_FW_START (0x0800) +#define PRAM_RESERVE_START (0x0800) +#define PRAM_PARA_DC_THD_ADDR (PRAM_PARA_START + 136) +#define PRAM_CC_TABLE_ADDR (0x7F78) + +#define PRAM_BASEINE_START (0x6CCC) +#define PRAM_BOOT_CRC_LENGTH (PRAM_BOOT_LENGTH - HEADER_LENGTH) +#define PRAM_FW_CRC_START (0x6B5C) +#define PRAM_FW_CRC_LENGTH (PRAM_FW_LENGTH + PRAM_PARA_LENGTH) //0x7474 +#define PRAM_CB_CRC_START (0x6DFC) +#define PRAM_CB_CRC_LENGTH (PRAM_COMP_LENGTH + PRAM_BASELINE_LENGTH) + + +#define FT_RAWDATA1_SHORT_BUF_ADDR 0x200002E4 //((at(0x200002E4))); +#define FT_RAWDATA2_OPEN_BUF_ADDR (FT_RAWDATA1_SHORT_BUF_ADDR+100) //((at(0x20000348))); +#define FT_RAWDATA3_CC_BUF_ADDR (FT_RAWDATA2_OPEN_BUF_ADDR+100) //((at(0x200003AC))); +#define FT_UC_BUF_ADDR (FT_RAWDATA3_CC_BUF_ADDR+100) //((at(0x20000410))); + +#define FT_OPEN_BL_BUF_ADDR (FT_UC_BUF_ADDR+100) //((at(0x20000474))); +#define FT_TEST_RESULT_BUF_ADDR (FT_OPEN_BL_BUF_ADDR+100) //((at(0x200004D8))); +#define FT_TEST_ITEM_RESULT (FT_TEST_RESULT_BUF_ADDR+50+2) //((at(0x2000050C))); +#define FT_TEST_INFO_ADDR 0x20000674//FT_IMG2PIN_BUF_ADDR+72//((at(0x20000674))); +#define FT_TEST_THD_ADDR (FT_TEST_INFO_ADDR+16) //((at(0x20000684))); +#define FT_TEST_PARA_ADDR (FT_TEST_THD_ADDR+36) //((at(0x200006A8))); + + +#define SRAM_FT_RAWDATA_3_CC_ADDR (RM_RAM_BASE + 0x0000074C) //0x2000074C +#define SRAM_FT_UC_CC_ADDR (RM_RAM_BASE + 0x000006EC) //0x200006EC + +#define FLASH_NORMAL_FW_FW_VERSION_ADDR (FW_FT_FW_VERSION) +#define FLASH_NORMAL_FW_CUST_VERSION_ADDR (PRAM_PARA_START + 10) +#define FLASH_TEST_FW_FW_VERSION_ADDR (FW_FT_FW_VERSION + FLASH_OFFSET) +#define FLASH_NORMAL_FW_CC_TABLE_ADDR 0x9300 + +#define REG_FLASHCTL_FLASH_PRAM_LOCK 0X50000900 +#define REG_FLASHCTL_FLASH_PRAM_STORE_TYPE 0X50000904 +#define REG_FLASHCTL_FLASH_PRAM_ADDR 0X50000908 +#define REG_FLASHCTL_FLASH_PRAM_LENGTH 0X5000090C +#define REG_FLASHCTL_FLASH_ADDR 0X50000910 +#define REG_FLASHCTL_FLASH_ISPCTL 0X50000914 +#define REG_FLASHCTL_FLASH_STATE_REG_ADDR 0x50000918 +#define REG_FLASHCTL_FLASH_FLKEY1 0x50000934 +#define REG_FLASHCTL_FLASH_FLKEY2 0x50000938 +#define REG_FLASHCTL_FLASH_DATA 0x5000093C +#define REG_FLASHCTL_FLASH_ENG3 0x5000094C +#define REG_FLASHCTL_FLASH_PRGCHKSUM_ADDR 0x50000974 +#define REG_FLASHCTL_FLASH_PRGCHKSUM_RESULT 0x50000978 +#define REG_FLASHCTL_DEVID_ADDR 0x500009BC + +#define REG_SPI_SLAVE_SPIFLASHPRO 0x50000524 + +#define PRAM_ADDR_CC_INFO 0x00007F78 + +#define RAM_WRITE_TEST_ADDR1 0x50000950 +#define RAM_WRITE_TEST_ADDR2 0x50000B10 +#define RAM_WRITE_TEST_ADDR3 0x50000B00 +#define RAM_READ_TEST_ADDR1 0x50000954 +#define RAM_READ_TEST_ADDR2 0x50000B04 +#define RAM_READ_TEST_ADDR3 0x50000B08 + +#define FT_UPDATE 0x01 +#define FT_BASELINE_SF 0x02 +#define FT_BASELINE_PS 0x42 +#define FT_COMPENSATION_SF 0x04 +#define FT_COMPENSATION_PS 0x44 +#define FT_RAWDATA_W_BL_SF 0x08 +#define FT_RAWDATA_W_BL_PS 0x48 +#define FT_RAWDATA_WO_BL_SF 0x10 +#define FT_RAWDATA_WO_BL_PS 0x50 +#define FT_ALG_RAWDATA 0x20 +#define FT_PS_SEL 0x40 +#define FT_DEBUG_MESSAGE 0x80 +#define FT_UPDATE_CASE 0xFE + +#define FT_STATUS_PURE_RAW 0x01 +#define FT_STATUS_PURE_AF_DC 0x02 + +typedef enum { + CRC_CHECK_FAIL = 0x80, + CRC_CHECK_PASS = 0x81, + WAIT_TEST_MODE = 0x82, + PARTITION_CRC = 0xA0, + SET_ADDR_READY = 0xA1, + SET_ADDR_FAIL = 0xA2, + WRT_PRAM_DATA = 0xA3, + WRT_PRAM_FAIL = 0xA4, + WAIT_WRT_ACK = 0xA5, + WAIT_ACK_FAIL = 0xA6, + GET_WRT_ACK = 0xA7, + GET_WRT_UNLOCK = 0xA8, + GET_UNLOCK_FAIL = 0xA9, +} I2C_SYNC_CMD; + +typedef enum { + MAIN_STATE_NORMAL_MODE = 0, + MAIN_STATE_BURNING_MODE, + MAIN_STATE_FIRWARE_MODE, +} TCH_BOOTLOADER_STATE; + +typedef enum { + BURNING_STATE_INIT = 0, + BURNING_CHECK_ADDR, + BURNING_UNLOCK_PRAM, + BURNING_WRT_PRAM, + BURNING_WRT_FLASH_PREPARE, + BURNING_WRT_FLASH_EXCUTE, + BURNING_WRT_FLASH_FINISH, + BURNING_STATE_HALT, +} BL_BURNING_STATE; + +typedef enum { + NORMAL_STATE_CHECK = 0, + NORMAL_CRC_CALC, + NORMAL_CRC_NOTIFY, + NORMAL_FW_CRC, + NORMAL_MODE_CHANGE, + NORMAL_MODE_IDLE, +} BL_NORMAL_STATE; + diff --git a/qcom/opensource/touch-drivers/raydium/chip_raydium/f303_ic_test.c b/qcom/opensource/touch-drivers/raydium/chip_raydium/f303_ic_test.c new file mode 100644 index 0000000000..00f0b0ca86 --- /dev/null +++ b/qcom/opensource/touch-drivers/raydium/chip_raydium/f303_ic_test.c @@ -0,0 +1,2277 @@ +/* f303_ic_test.c + * + * Raydium TouchScreen driver. + * + * Copyright (c) 2021 Raydium tech Ltd. + * + * 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. + * + */ +#ifdef __KERNEL__ +#include +#include +#include +#include +#include +#include +#include +#include +#else +#include +#include +#include +#endif + +#define RM_F303_MAX_STR_LENGTH 8 +#include "../drv_interface.h" +#include "ic_drv_global.h" +#include "ic_drv_interface.h" +#include "f303_ic_control.h" +#include "f303_ic_test.h" +#include "f303_ic_reg.h" +#if !SELFTEST_3X +#include "ic_drv_global_ft.h" +#include "f303_ic_test_ft.h" +#include "ic_drv_interface_ft.h" +#endif + +STATUS ft_test_panel_model_check_3x(unsigned short u16_version); + +#if !SELFTEST_3X +STATUS burn_data_log_to_flash_3x(void) +{ + unsigned short u16_pram_start_addr = 0x1000; + unsigned char u8_read_buf[4], u8_fw_version[4]; + unsigned short u16_index = 0, u16_retry = 0; + unsigned int u32_read = 0, u32_flash_header_start_addr = 0xA000, u32_header_size = 0x400; + unsigned int u32_write = 0; + unsigned char *p_data = (unsigned char *)g_i16_raw_data_frame_buffer; + + if (g_u8_drv_interface == SPI_INTERFACE || g_u8_test_log_burn_times > 3) { + /*flashless*/ + DEBUGOUT("return burn test log\r\n"); + return SUCCESS; + } + + memset(g_i16_raw_data_frame_buffer, 0x0, 1024); + memset(u8_fw_version, 0x0, sizeof(u8_fw_version)); + hardware_reset_3x(false); + /*read info*/ + /*wearable_ic_test_read_info();*/ + + /*read FW Version*/ + if (handle_ic_read(FW_FT_SRAM_FW_VERSION, 4, u8_fw_version, g_u8_drv_interface, I2C_BYTE_MODE) == ERROR) + return ERROR; + + /* -------------*/ + /*read Header*/ + if (handle_ic_read(REG_SYSCON_BLKEN_ADDR, 4, (unsigned char *)(&u32_read), g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + u32_read |= 0x40; + if (handle_ic_write(REG_SYSCON_BLKEN_ADDR, 4, (unsigned char *)&u32_read, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + u32_write = 0x00000000; + if (handle_ic_write(REG_I2C_I2CFLASHPRO, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + /*Release flash power down mode*/ + u32_write = 0x28; + if (handle_ic_write(REG_FLASHCTL_FLASH_ISPCTL, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + + /*----write data-----*/ + memset(p_data, 0xFF, 1024); + + if (!stop_mcu_3x(0)) { + DEBUGOUT("Stop MCU NG\r\n"); + return ERROR; + } + + u32_write = 0x00000000; + if (handle_ic_write(REG_I2C_I2CFLASHPRO, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + /*Unlock PRAM*/ + u32_write = 0x00000000; + if (handle_ic_write(REG_FLASHCTL_FLASH_PRAM_LOCK, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + /* unlock key*/ + u32_write = FLKEY2_KEY; + if (handle_ic_write(REG_FLASHCTL_FLASH_FLKEY2, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + u32_write = FLKEY1_KEY; + if (handle_ic_write(REG_FLASHCTL_FLASH_FLKEY1, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + u32_write = FLKEY1_LOCK; + if (handle_ic_write(REG_FLASHCTL_FLASH_FLKEY1, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + u32_write = FLKEY1_KEY; + if (handle_ic_write(REG_FLASHCTL_FLASH_FLKEY1, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + u32_write = FLKEY2_LOCK; + if (handle_ic_write(REG_FLASHCTL_FLASH_FLKEY2, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + /*clear Pram*/ + u16_index = 0; + while (u16_index < 1024) { + if (handle_ic_write(u16_pram_start_addr + u16_index, 64, p_data, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + u16_index += 64; + } + + + /*DEBUGOUT("[u8_fw_version]:%X\r\n", u8_fw_version);*/ + /*DEBUGOUT("[g_u32_wearable_test_result]:%X\r\n", g_u32_wearable_test_result);*/ + + /*fill data*/ + + memset(g_u16_raw_data_tmp, 0, sizeof(g_u16_raw_data_tmp)); + test_log_raw_data_remap_3x(g_i16_raw_data_1_short_buf, (short *)g_u16_raw_data_tmp, DATA_REMAP_TO_SENSOR_PAD); + memcpy(p_data, g_u16_raw_data_tmp, 96); + memset(g_u16_raw_data_tmp, 0, sizeof(g_u16_raw_data_tmp)); + test_log_raw_data_remap_3x(g_i16_raw_data_2_open_buf, (short *)g_u16_raw_data_tmp, DATA_REMAP_TO_SENSOR_PAD); + memcpy((p_data + 96), g_u16_raw_data_tmp, 96); + memset(g_u16_raw_data_tmp, 0, sizeof(g_u16_raw_data_tmp)); + test_log_raw_data_remap_3x((short *)g_u16_raw_data3_cc_buf, (short *)g_u16_raw_data_tmp, DATA_REMAP_TO_SENSOR_PAD); + memcpy((p_data + 192), g_u16_raw_data_tmp, 96); + memset(g_u16_raw_data_tmp, 0, sizeof(g_u16_raw_data_tmp)); + test_log_raw_data_remap_3x(g_i16_noise_peek_raw_data, (short *)g_u16_raw_data_tmp, DATA_REMAP_TO_SENSOR_PAD); + memcpy((p_data + 288), g_u16_raw_data_tmp, 96); + memset(g_u16_raw_data_tmp, 0, sizeof(g_u16_raw_data_tmp)); + test_log_raw_data_remap_3x(g_i16_display_pattern_avg_raw_data, (short *)g_u16_raw_data_tmp, DATA_REMAP_TO_SENSOR_PAD); + memcpy((p_data + 384), g_u16_raw_data_tmp, 96); + memset(g_u16_raw_data_tmp, 0, sizeof(g_u16_raw_data_tmp)); + test_log_raw_data_remap_3x(g_i16_display_pattern_no_dc_avg_raw_data, (short *)g_u16_raw_data_tmp, DATA_REMAP_TO_SENSOR_PAD); + memcpy((p_data + 480), g_u16_raw_data_tmp, 96); + memset(g_u16_raw_data_tmp, 0, sizeof(g_u16_raw_data_tmp)); + test_log_raw_data_remap_3x(g_i16_display_pattern_avg_raw_data_s2, (short *)g_u16_raw_data_tmp, DATA_REMAP_TO_SENSOR_PAD); + memcpy((p_data + 576), g_u16_raw_data_tmp, 96); + memset(g_u16_raw_data_tmp, 0, sizeof(g_u16_raw_data_tmp)); + test_log_raw_data_remap_3x(g_i16_display_pattern_no_dc_avg_raw_data_s2, (short *)g_u16_raw_data_tmp, DATA_REMAP_TO_SENSOR_PAD); + memcpy((p_data + 672), g_u16_raw_data_tmp, 96); + memset(g_u16_raw_data_tmp, 0, sizeof(g_u16_raw_data_tmp)); + test_log_raw_data_remap_3x((short *)g_u16_uc_buf, (short *)g_u16_raw_data_tmp, DATA_REMAP_TO_SENSOR_PAD); + memcpy((p_data + 768), g_u16_raw_data_tmp, 96); + + /*---write data section---*/ + u16_index = 0; + while (u16_index < 1024) { + if (handle_ic_write(u16_pram_start_addr + u16_index, 64, (p_data + u16_index), g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + u16_index += 64; + } + + + u32_write = u16_pram_start_addr; + if (handle_ic_write(REG_FLASHCTL_FLASH_PRAM_ADDR, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + u32_write = 0x300; + if (handle_ic_write(REG_FLASHCTL_FLASH_PRAM_LENGTH, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + + DEBUGOUT("g_st_test_info.u8_station_id:%x\r\n", g_st_test_info.u8_station_id); + DEBUGOUT("u8_burn_times:%d\r\n", g_u8_test_log_burn_times); + + DEBUGOUT("ADDR:%x\r\n", u32_flash_header_start_addr + u32_header_size + (g_st_test_info.u8_station_id - 1) * 4096 + + (g_u8_test_log_burn_times - 1) * 0x400); + + /*flash addr , wrtie data section*/ + u32_write = u32_flash_header_start_addr + u32_header_size + (g_st_test_info.u8_station_id - 1) * 4096 + + (g_u8_test_log_burn_times - 1) * 0x400; + if (handle_ic_write(REG_FLASHCTL_FLASH_ADDR, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + /*write trigger*/ + u32_write = 0x4; + if (handle_ic_write(REG_FLASHCTL_FLASH_ISPCTL, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + /* check read trigger finished*/ + u16_retry = 1000; + memset(u8_read_buf, 0, sizeof(u8_read_buf)); + if (handle_ic_read(REG_FLASHCTL_FLASH_ISPCTL, 4, u8_read_buf, g_u8_drv_interface, I2C_BYTE_MODE) == ERROR) + return ERROR; + + while ((u8_read_buf[0] & 0x08) == 0 && u16_retry > 0) { + if (handle_ic_read(REG_FLASHCTL_FLASH_ISPCTL, 4, u8_read_buf, g_u8_drv_interface, I2C_BYTE_MODE) == ERROR) + return ERROR; + delay_ms(1); + u16_retry--; + } + if (u16_retry == 0) { + DEBUGOUT("Write Trigger Error\r\n"); + return ERROR; + } + + /*---end write data section---*/ + + hardware_reset_3x(TRUE); + + return SUCCESS; +} + +STATUS burn_header_log_to_flash_3x(bool is_test_finish, bool is_in_header_data_page) +{ + + unsigned short u16_pram_start_addr = 0x1000; + unsigned char *p_header_data = (unsigned char *)g_u32_save_config; + unsigned char u8_write_buf[64], u8_fw_version[4], u8_read_buf[4], u8_burn_full_times = 20, u8_count_1 = 0, u8_test_info[16]; + unsigned short u16_index = 0, u16_retry = 0; + unsigned int u32_read = 0, u32_flash_header_start_addr = 0xA000 + (g_st_test_info.u8_station_id - 1) * 4096 + g_u8_test_log_burn_times / 5 * 0x100; + unsigned int u32_write = 0, u32_temp = 0; + bool b_is_first_read = true; + + DEBUGOUT("[u32_flash_header_start_addr ]:%d\r\n", u32_flash_header_start_addr); + /*DEBUGOUT("[is_in_header_data_page ]:%d\r\n", is_in_header_data_page);*/ + if (!is_test_finish && is_in_header_data_page) + memset(g_u32_save_config, 0, sizeof(g_u32_save_config)); + + if (is_in_header_data_page) + u32_flash_header_start_addr = 0xA000 + (g_st_test_info.u8_station_id - 1) * 4096 + g_u8_test_log_burn_times / 5 * 0x100; + else + u32_flash_header_start_addr = 0xA000 + (g_st_test_info.u8_station_id - 1) * 4096; + + if (g_u8_drv_interface == SPI_INTERFACE || g_u8_test_log_burn_times > 20) + /*flashless*/ + return SUCCESS; + + + + memset(u8_write_buf, 0x0, sizeof(u8_write_buf)); + /*memset(g_u8_header_buf, 0x0 , sizeof(g_u8_header_buf));*/ + memset(u8_fw_version, 0x0, sizeof(u8_fw_version)); + + hardware_reset_3x(false); + /*read info*/ + + + + + + if (read_flash_data(g_u32_dongle_flash_ini_addr, 16) != ERROR) + memcpy(&u8_test_info, g_u8_data_buf, sizeof(u8_test_info)); + + + + + + /*read FW Version*/ + if (handle_ic_read(FW_FT_SRAM_FW_VERSION, 4, u8_fw_version, g_u8_drv_interface, I2C_BYTE_MODE) == ERROR) + return ERROR; + + /* -------------*/ + /*read Header*/ + if (handle_ic_read(REG_SYSCON_BLKEN_ADDR, 4, (unsigned char *)(&u32_read), g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + u32_read |= 0x40; + if (handle_ic_write(REG_SYSCON_BLKEN_ADDR, 4, (unsigned char *)&u32_read, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + + u32_write = 0x00000000; + if (handle_ic_write(REG_I2C_I2CFLASHPRO, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + /*Release flash power down mode*/ + u32_write = 0x28; + if (handle_ic_write(REG_FLASHCTL_FLASH_ISPCTL, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + DEBUGOUT("[header addr!]:%X\r\n", u32_flash_header_start_addr); + + while (u16_index < 256) { + /*write flash addr*/ + + + u32_write = u32_flash_header_start_addr + u16_index; + if (handle_ic_write(REG_FLASHCTL_FLASH_ADDR, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + + /*Set Read trigger, Flh_read_trg[bit 6]*/ + u32_write = 0x40; + if (handle_ic_write(REG_FLASHCTL_FLASH_ISPCTL, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + /* check read trigger finished*/ + u16_retry = 1000; + memset(u8_read_buf, 0, sizeof(u8_read_buf)); + + while ((u8_read_buf[0] & 0x40) != 0 && u16_retry > 0) { + if (handle_ic_read(REG_FLASHCTL_FLASH_ISPCTL, 4, u8_read_buf, g_u8_drv_interface, I2C_BYTE_MODE) == ERROR) + return ERROR; + delay_ms(1); + u16_retry--; + } + if (u16_retry == 0) + return ERROR; + + /*Read Flash Data*/ + if (handle_ic_read(REG_FLASHCTL_FLASH_DATA, 4, (unsigned char *)(&u32_read), g_u8_drv_interface, I2C_BYTE_MODE) == ERROR) + return ERROR; + /*memcpy(&g_u8_header_buf[0] , &u32_read , 4);*/ + + + if (b_is_first_read && !is_in_header_data_page) { + + u32_temp = u32_read; + while (u32_temp) { + u8_count_1++; + u32_temp = (u32_temp - 1) & u32_temp; + } + /*DEBUGOUT("u32_temp:%d\r\n", 32 - u8_count_1);*/ + + + if (32 - u8_count_1 >= u8_burn_full_times) { + g_u8_test_log_burn_times = 32 - u8_count_1 + 1; + DEBUGOUT("Burn Full times\r\n"); + hardware_reset_3x(TRUE); + return SUCCESS; + } + g_u8_test_log_burn_times = 32 - u8_count_1 + 1; + /*count how many times has been tested , each test would right shift one bit.*/ + u32_read = u32_read >> 1; + DEBUGOUT("Burn times: %d\r\n", g_u8_test_log_burn_times); + memcpy(&p_header_data[0], &u32_read, 4); + b_is_first_read = false; + + } else + memcpy(&p_header_data[u16_index], &u32_read, 4); + u16_index += 4; + + + } + + + + + +/* u16_auo_jig_cmd = g_u16_panel_jig_set_test_items;*/ +/* fill data*/ + if (g_u8_test_log_burn_times <= 5 || is_in_header_data_page) { + memcpy(&p_header_data[8 + ((g_u8_test_log_burn_times - 1) % 5) * 48], &u8_test_info, 8); + /*if ((g_st_test_info.u16_ft_eng_item & IC_TEST_ENG_ITEMS_PANEL_TEST_JIG))*/ + /* memcpy(&p_header_data[8 + ((g_u8_test_log_burn_times - 1) % 5) * 48 + 4] , &u16_auo_jig_cmd , 2);*/ + memcpy(&p_header_data[8 + ((g_u8_test_log_burn_times - 1) % 5) * 48 + 8], &g_st_test_thd.i16_ft_test_open_lower_thd, 2); + memcpy(&p_header_data[8 + ((g_u8_test_log_burn_times - 1) % 5) * 48 + 10], &g_st_test_thd.i16_ft_test_short_upper_thd, 2); + memcpy(&p_header_data[8 + ((g_u8_test_log_burn_times - 1) % 5) * 48 + 12], &g_st_test_thd.i16_ft_test_single_cc_lower_thd, 2); + memcpy(&p_header_data[8 + ((g_u8_test_log_burn_times - 1) % 5) * 48 + 14], &g_st_test_thd.i16_ft_test_uniformity_cc_upper_thd, 2); + memcpy(&p_header_data[8 + ((g_u8_test_log_burn_times - 1) % 5) * 48 + 16], &g_st_test_thd.i16_ft_test_uniformity_cc_lower_thd, 2); + memcpy(&p_header_data[8 + ((g_u8_test_log_burn_times - 1) % 5) * 48 + 18], &g_st_test_thd.i16_ft_test_panel_test_1_thd, 2); + memcpy(&p_header_data[8 + ((g_u8_test_log_burn_times - 1) % 5) * 48 + 20], &g_st_test_thd.i16_ft_test_panel_test_3_thd, 2); + memcpy(&p_header_data[8 + ((g_u8_test_log_burn_times - 1) % 5) * 48 + 22], &g_st_test_thd.i16_ft_test_panel_test_2_thd, 2); + memcpy(&p_header_data[8 + ((g_u8_test_log_burn_times - 1) % 5) * 48 + 24], &g_st_test_thd.i16_ft_test_panel_test_2_s2_thd, 2); + memcpy(&p_header_data[8 + ((g_u8_test_log_burn_times - 1) % 5) * 48 + 26], &g_st_test_para_resv.u32_normal_fw_version, 4); + memcpy(&p_header_data[8 + ((g_u8_test_log_burn_times - 1) % 5) * 48 + 30], &g_st_test_para_resv.u32_test_fw_version, 4); + if (is_test_finish) { /*after testing, burn the result to flash*/ + DEBUGOUT("g_u8_channel_x: %d\r\n", g_u8_channel_x); + DEBUGOUT("g_u8_channel_y: %d\r\n", g_u8_channel_y); + memcpy(&p_header_data[8 + ((g_u8_test_log_burn_times - 1) % 5) * 48 + 34], &g_u32_wearable_test_result, 4); + p_header_data[8 + ((g_u8_test_log_burn_times - 1) % 5) * 48 + 37] = p_header_data[8 + ((g_u8_test_log_burn_times - 1) % 5) * 48 + 34] ^ p_header_data[8 + ((g_u8_test_log_burn_times - 1) % 5) * 48 + 37]; + p_header_data[8 + ((g_u8_test_log_burn_times - 1) % 5) * 48 + 34] = p_header_data[8 + ((g_u8_test_log_burn_times - 1) % 5) * 48 + 34] ^ p_header_data[8 + ((g_u8_test_log_burn_times - 1) % 5) * 48 + 37]; + p_header_data[8 + ((g_u8_test_log_burn_times - 1) % 5) * 48 + 37] = p_header_data[8 + ((g_u8_test_log_burn_times - 1) % 5) * 48 + 34] ^ p_header_data[8 + ((g_u8_test_log_burn_times - 1) % 5) * 48 + 37]; + p_header_data[8 + ((g_u8_test_log_burn_times - 1) % 5) * 48 + 36] = p_header_data[8 + ((g_u8_test_log_burn_times - 1) % 5) * 48 + 35] ^ p_header_data[8 + ((g_u8_test_log_burn_times - 1) % 5) * 48 + 36]; + p_header_data[8 + ((g_u8_test_log_burn_times - 1) % 5) * 48 + 35] = p_header_data[8 + ((g_u8_test_log_burn_times - 1) % 5) * 48 + 35] ^ p_header_data[8 + ((g_u8_test_log_burn_times - 1) % 5) * 48 + 36]; + p_header_data[8 + ((g_u8_test_log_burn_times - 1) % 5) * 48 + 36] = p_header_data[8 + ((g_u8_test_log_burn_times - 1) % 5) * 48 + 35] ^ p_header_data[8 + ((g_u8_test_log_burn_times - 1) % 5) * 48 + 36]; + + + memcpy(&p_header_data[8 + ((g_u8_test_log_burn_times - 1) % 5) * 48 + 38], u8_fw_version, 4); + memcpy(&p_header_data[8 + ((g_u8_test_log_burn_times - 1) % 5) * 48 + 42], &g_u8_channel_x, 1); + memcpy(&p_header_data[8 + ((g_u8_test_log_burn_times - 1) % 5) * 48 + 43], &g_u8_channel_y, 1); + } +/* DEBUGOUT("g_u8_test_date0: %d\r\n", g_u8_test_date[0]); + * DEBUGOUT("g_u8_test_date: %d\r\n", g_u8_test_date[1]); + * DEBUGOUT("g_u8_test_date: %d\r\n", g_u8_test_date[2]); + * DEBUGOUT("g_u8_test_date: %d\r\n", g_u8_test_date[3]); + */ + memcpy(&p_header_data[8 + ((g_u8_test_log_burn_times - 1) % 5) * 48 + 44], &g_u8_test_date, 4); + } + /*----write data-----*/ + + if (!stop_mcu_3x(0)) { + DEBUGOUT("Stop MCU NG\r\n"); + return ERROR; + } + + u32_write = 0x00000000; + if (handle_ic_write(REG_I2C_I2CFLASHPRO, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + /*Unlock PRAM*/ + u32_write = 0x00000000; + if (handle_ic_write(REG_FLASHCTL_FLASH_PRAM_LOCK, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + /* unlock key*/ + u32_write = FLKEY2_KEY; + if (handle_ic_write(REG_FLASHCTL_FLASH_FLKEY2, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + u32_write = FLKEY1_KEY; + if (handle_ic_write(REG_FLASHCTL_FLASH_FLKEY1, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + u32_write = FLKEY1_LOCK; + if (handle_ic_write(REG_FLASHCTL_FLASH_FLKEY1, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + u32_write = FLKEY1_KEY; + if (handle_ic_write(REG_FLASHCTL_FLASH_FLKEY1, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + u32_write = FLKEY2_LOCK; + if (handle_ic_write(REG_FLASHCTL_FLASH_FLKEY2, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + /*clear Pram*/ + u16_index = 0; + while (u16_index < 256) { /*totally 688(768) + 256 header bytes*/ + if (handle_ic_write(u16_pram_start_addr + u16_index, 64, u8_write_buf, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + u16_index += 64; + } + + /*---write header section---*/ + u16_index = 0; + while (u16_index < 256) { /*totally 256 bytes*/ + if (handle_ic_write(u16_pram_start_addr + u16_index, 64, &p_header_data[u16_index], g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + u16_index += 64; + } + + u32_write = u16_pram_start_addr; + if (handle_ic_write(REG_FLASHCTL_FLASH_PRAM_ADDR, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + u32_write = 0x00; + if (handle_ic_write(REG_FLASHCTL_FLASH_PRAM_LENGTH, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + /*flash addr , wrtie data section*/ + u32_write = u32_flash_header_start_addr; + if (handle_ic_write(REG_FLASHCTL_FLASH_ADDR, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + /*write trigger*/ + u32_write = 0x4; + if (handle_ic_write(REG_FLASHCTL_FLASH_ISPCTL, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + /* check read trigger finished*/ + u16_retry = 1000; + memset(u8_read_buf, 0, sizeof(u8_read_buf)); + if (handle_ic_read(REG_FLASHCTL_FLASH_ISPCTL, 4, u8_read_buf, g_u8_drv_interface, I2C_BYTE_MODE) == ERROR) + return ERROR; + while ((u8_read_buf[0] & 0x08) != 0 && u16_retry > 0) { + if (handle_ic_read(REG_FLASHCTL_FLASH_ISPCTL, 4, u8_read_buf, g_u8_drv_interface, I2C_BYTE_MODE) == ERROR) + return ERROR; + delay_ms(1); + u16_retry--; + } + if (u16_retry == 0) + return ERROR; + + /*memset(g_u8_test_date , 0 , sizeof(g_u8_test_date)); */ + /*clear date*/ + /*---end write header section---*/ + hardware_reset_3x(TRUE); + DEBUGOUT("End burn header\r\n"); + return SUCCESS; +} +#endif +STATUS turn_on_flash_3x(void) +{ + unsigned int u32_read = 0; + + /*Turn on Flash*/ + if (handle_ic_write(REG_I2C_I2CFLASHPRO, 4, (unsigned char *)&u32_read, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + u32_read = FLH_RELEASE_PD; + if (handle_ic_write(REG_FLASHCTL_FLASH_ISPCTL, 4, (unsigned char *)&u32_read, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + return SUCCESS; +} + +STATUS read_fpc_flash_3x(unsigned int u32_addr, unsigned int *p_u32_data) +{ + unsigned int u32_read; + + /*Turn on Flash*/ + if (turn_on_flash_3x() == ERROR) + return ERROR; + + if (handle_ic_read(REG_SYSCON_BLKEN_ADDR, 4, (unsigned char *)(&u32_read), g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + u32_read |= 0x40; + if (handle_ic_write(REG_SYSCON_BLKEN_ADDR, 4, (unsigned char *)&u32_read, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + u32_read = 0x00000000; + if (handle_ic_write(REG_I2C_I2CFLASHPRO, 4, (unsigned char *)&u32_read, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + if (handle_ic_write(REG_FLASHCTL_FLASH_ADDR, 4, (unsigned char *)&u32_addr, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + u32_read = 0x40; + if (handle_ic_write(REG_FLASHCTL_FLASH_ISPCTL, 4, (unsigned char *)&u32_read, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + delay_ms(1); + + if (handle_ic_read(REG_FLASHCTL_FLASH_DATA, 4, (unsigned char *)(&u32_read), g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + *p_u32_data = u32_read; + return SUCCESS; +} + +STATUS set_test_info_thd_para_3x(void) +{ + unsigned char u8_read_buf[96], u8_i; + unsigned char *p_u8_input_buf; + + if (handle_ic_write(FT_TEST_INFO_ADDR, sizeof(g_st_test_info), (unsigned char *)&g_st_test_info, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + if (handle_ic_read(FT_TEST_INFO_ADDR, sizeof(g_st_test_info), (unsigned char *)u8_read_buf, g_u8_drv_interface, I2C_BYTE_MODE) == ERROR) + return ERROR; + + p_u8_input_buf = (unsigned char *)&g_st_test_info; + for (u8_i = 0; u8_i < sizeof(g_st_test_info); u8_i++) { + if (p_u8_input_buf[u8_i] != u8_read_buf[u8_i]) { + DEBUGOUT("Set INFO NG:[%d]=%d->%d\r\n", u8_i, p_u8_input_buf[u8_i], u8_read_buf[u8_i]); + return ERROR; + } + } + + if (handle_ic_write(FT_TEST_THD_ADDR, sizeof(g_st_test_thd), (unsigned char *)&g_st_test_thd, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + if (handle_ic_read(FT_TEST_THD_ADDR, sizeof(g_st_test_thd), (unsigned char *)u8_read_buf, g_u8_drv_interface, I2C_BYTE_MODE) == ERROR) + return ERROR; + p_u8_input_buf = (unsigned char *)&g_st_test_thd; + for (u8_i = 0; u8_i < sizeof(g_st_test_thd); u8_i++) { + if (p_u8_input_buf[u8_i] != u8_read_buf[u8_i]) { + DEBUGOUT("Set THD NG:[%d]=%d->%d\r\n", u8_i, p_u8_input_buf[u8_i], u8_read_buf[u8_i]); + return ERROR; + } + } + + if (handle_ic_write(FT_TEST_PARA_ADDR, sizeof(g_st_test_para_resv), (unsigned char *)&g_st_test_para_resv, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + if (handle_ic_read(FT_TEST_PARA_ADDR, sizeof(g_st_test_para_resv), (unsigned char *)u8_read_buf, g_u8_drv_interface, I2C_BYTE_MODE) == ERROR) + return ERROR; + p_u8_input_buf = (unsigned char *)&g_st_test_para_resv; + for (u8_i = 0; u8_i < sizeof(g_st_test_para_resv); u8_i++) { + if (p_u8_input_buf[u8_i] != u8_read_buf[u8_i]) { + DEBUGOUT("Set Para NG:[%d]=%d->%d\r\n", u8_i, p_u8_input_buf[u8_i], u8_read_buf[u8_i]); + return ERROR; + } + } + + if (handle_ic_write(SRAM_FT_UC_CC_ADDR, 96, (unsigned char *)g_u16_uc_golden_cc_buf, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + if (handle_ic_read(SRAM_FT_UC_CC_ADDR, 96, (unsigned char *)u8_read_buf, g_u8_drv_interface, I2C_BYTE_MODE) == ERROR) + return ERROR; + p_u8_input_buf = (unsigned char *)g_u16_uc_golden_cc_buf; + for (u8_i = 0; u8_i < 96; u8_i++) { + if (p_u8_input_buf[u8_i] != u8_read_buf[u8_i]) { + DEBUGOUT("Set PRAM_FT_FW_GOLDEN_CC_ADDR NG:[%d]=%d->%d\r\n", u8_i, p_u8_input_buf[u8_i], u8_read_buf[u8_i]); + return ERROR; + } + } +/*DEBUGOUT("g_u16_uc_golden_cc_buf[0]=%X,g_u16_uc_golden_cc_buf[1]=%X,g_u16_uc_golden_cc_buf[2]=%X,g_u16_uc_golden_cc_buf[3]=%X,g_u16_uc_golden_cc_buf[4]=%X,g_u16_uc_golden_cc_buf[5]=%X\r\n",g_u16_uc_golden_cc_buf[0],g_u16_uc_golden_cc_buf[1],g_u16_uc_golden_cc_buf[2],g_u16_uc_golden_cc_buf[3],g_u16_uc_golden_cc_buf[4],g_u16_uc_golden_cc_buf[5]); + * DEBUGOUT("u8_read_buf[0]=%X,u8_read_buf[1]=%X,u8_read_buf[2]=%X,u8_read_buf[3]=%X,u8_read_buf[4]=%X,u8_read_buf[5]=%X\r\n",u8_read_buf[0],u8_read_buf[1],u8_read_buf[2],u8_read_buf[3],u8_read_buf[4],u8_read_buf[5]); + */ + + if (handle_ic_write(SRAM_FT_RAWDATA_3_CC_ADDR, 96, (unsigned char *)g_u16_raw_data3_golden_cc_buf, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + if (handle_ic_read(SRAM_FT_RAWDATA_3_CC_ADDR, 96, (unsigned char *)u8_read_buf, g_u8_drv_interface, I2C_BYTE_MODE) == ERROR) + return ERROR; + p_u8_input_buf = (unsigned char *)g_u16_raw_data3_golden_cc_buf; + for (u8_i = 0; u8_i < 96; u8_i++) { + if (p_u8_input_buf[u8_i] != u8_read_buf[u8_i]) { + DEBUGOUT("Set PRAM_FT_OPEN_GOLDEN_CC_ADDR NG:[%d]=%d->%d\r\n", u8_i, p_u8_input_buf[u8_i], u8_read_buf[u8_i]); + return ERROR; + } + } + + return SUCCESS; +} + +void dump_image_data_3x(short *p_image_buf, unsigned char u8_remap) +{ + short *p_i16_buf = (short *)g_u16_raw_data_tmp; + unsigned char u8_i, u8_j; + + memset(p_i16_buf, 0, MAX_IMAGE_BUFFER_SIZE * 2); + if (u8_remap) { + for (u8_i = 0; u8_i < MAX_IMAGE_BUFFER_SIZE; u8_i++) { + if (g_u8_wearable_pin_map[u8_i] != F303_NA_P) + p_i16_buf[g_u8_wearable_pin_map[u8_i]] = p_image_buf[u8_i]; + } + } else + memcpy(p_i16_buf, p_image_buf, MAX_IMAGE_BUFFER_SIZE << 1); + + for (u8_j = 0; u8_j < g_u8_channel_y; u8_j++) { + for (u8_i = 0; u8_i < g_u8_channel_x; u8_i++) + DEBUGOUT("%4d, ", p_i16_buf[u8_i + u8_j * g_u8_channel_x]); + DEBUGOUT("\r\n"); + } +} + +void dump_image_hex_data_3x(short *p_image_buf) +{ + + short *p_i16_buf = (short *)g_u16_raw_data_tmp; + unsigned char u8_i, u8_j; + + memset(p_i16_buf, 0, MAX_IMAGE_BUFFER_SIZE * 2); + for (u8_i = 0; u8_i < MAX_IMAGE_BUFFER_SIZE; u8_i++) { + if (g_u8_wearable_pin_map[u8_i] != F303_NA_P) + p_i16_buf[g_u8_wearable_pin_map[u8_i]] = p_image_buf[u8_i]; + } + + for (u8_j = 0; u8_j < g_u8_channel_y; u8_j++) { + for (u8_i = 0; u8_i < g_u8_channel_x; u8_i++) + DEBUGOUT("%4X, ", (p_i16_buf[u8_i + u8_j * g_u8_channel_x] & 0x7ff)); + DEBUGOUT("\r\n"); + } +} + +STATUS check_test_fw_status_3x(unsigned char u8_target_status, unsigned char *p_u8_result) +{ + unsigned int u32_read; + + if (handle_ic_read(FW_FT_ARG1_ADDR, 4, (unsigned char *)(&u32_read), g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + if ((u32_read & FT_TEST_STUATUS_PATTERN) == FT_TEST_STUATUS_PATTERN) { + if (((unsigned char)(u32_read & 0x00FF)) == u8_target_status) + *p_u8_result = 1; + else { + DEBUGOUT("[CTFS] Test STATUS NG!! (%x:%x)\r\n", u8_target_status, u32_read); + *p_u8_result = 0; + return ERROR; + } + DEBUGOUT("[CTFS] Test FW STATUS (%x:%x)\r\n", u8_target_status, u32_read); + } else { + DEBUGOUT("[CTFS] Test STATUS Not Ready!! (%x:%x)\r\n", u8_target_status, u32_read); + *p_u8_result = 0; + } + return SUCCESS; +} + +STATUS ft_test_do_fw_test_3x(unsigned short u16_test_items) +{ + + unsigned char u8_write_data[5], u8_result; + unsigned int u32_read_data; + short i16_time_out = 0; +#if ENABLE_TEST_TIME_MEASURMENT + unsigned int u32_fun_time = 0; + + u32_fun_time = get_system_time(); + DEBUGOUT("ft_test_do_fw_test Start Time= %d\r\n", u32_fun_time); +#endif + + g_u32_wearable_test_result &= ~WEARABLE_FT_TEST_RESULT_TEST_INIT_NG; + + if ((u16_test_items & (IC_TEST_ITEMS_OPEN | IC_TEST_ITEMS_SHORT | IC_TEST_ITEMS_UC | IC_TEST_ITEMS_UB)) == 0) + return SUCCESS; + memset(u8_write_data, 0, sizeof(u8_write_data)); + u8_write_data[1] = FT_CMD_DO_FT_TEST | 0x80; + u8_write_data[2] = (unsigned char) u16_test_items; + u8_write_data[3] = (unsigned char)(u16_test_items >> 8); + + DEBUGOUT("Star FW Test!!\r\n"); + + if (handle_ic_write(FW_SYS_CMD_ADDR, 4, u8_write_data, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + /*Wait test fw finish INT */ + /*Wait 20 Sec*/ + i16_time_out = 1000; + + while (i16_time_out--) { + delay_ms(20); +#if ENABLE_WDT + Chip_WWDT_Feed(LPC_WWDT);/* reload the Watchdog timer*/ +#endif + if (!gpio_touch_int_pin_state_access() || (i16_time_out < 10)) { + if (check_test_fw_status_3x(FT_CMD_DO_FT_TEST, &u8_result) == ERROR) { + DEBUGOUT("[FWT] Wait Test CMD NG (0x%x:%d)\r\n", FT_CMD_DO_FT_TEST, i16_time_out); + return ERROR; + } else if (u8_result == TRUE) { + break; + } + } + } + + if (handle_ic_read(FW_SYS_CMD_ADDR, 4, (unsigned char *)(&u32_read_data), g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + DEBUGOUT("[FWT]SysCMD1:0x%x (%d)\r\n", u32_read_data, i16_time_out); + + if (i16_time_out == IC_TEST_TIME_OUT) { + DEBUGOUT("[FWT] Wait Test CMD Timeout!! (0x%x)\r\n", FT_CMD_DO_FT_TEST); + return ERROR; + } + +#if ENABLE_TEST_TIME_MEASURMENT + DEBUGOUT("[FWT] FW Test Time(%d)\r\n", get_system_time() - u32_fun_time); +#endif + + return SUCCESS; +} +STATUS enter_fw_test_mode_3x(void) +{ + unsigned char u8_result = 0; + short i16_time_out = 0; + +#if ENABLE_TEST_TIME_MEASURMENT + unsigned int u32_fun_time = 0; + + u32_fun_time = get_system_time(); +#endif + + g_u32_wearable_test_result &= ~WEARABLE_FT_TEST_RESULT_TEST_INIT_NG; + + DEBUGOUT("[EFTM] Start\r\n"); + + if ((g_st_test_info.u16_ft_test_item & (IC_TEST_ITEMS_OPEN | IC_TEST_ITEMS_SHORT | + IC_TEST_ITEMS_UC | IC_TEST_ITEMS_UB)) == 0) { + return SUCCESS; + } + + g_u8_is_normal_fw = FALSE; + + i16_time_out = 300; + u8_result = 0; + while (i16_time_out--) { + if (!gpio_touch_int_pin_state_access() || (i16_time_out < 20)) { + if (check_test_fw_status_3x(FT_CMD_INIT, &u8_result) == ERROR) + goto IC_INIT_NG; + else if (u8_result == TRUE) + break; + } + delay_ms(5); + } + + if (i16_time_out == IC_TEST_TIME_OUT) { + DEBUGOUT("[EFTM] FW State Check NG\r\n"); + goto IC_INIT_NG; + } + + DEBUGOUT("[EFTM] Enter FT mode\r\n"); + + if (set_test_info_thd_para_3x() == ERROR) { + DEBUGOUT("[RUPI] Set test info, thd, para NG!\r\n"); + return ERROR; + } + +#if ENABLE_TEST_TIME_MEASURMENT + DEBUGOUT("[EFTM] (%d)\r\n", get_system_time() - u32_fun_time); +#endif + return SUCCESS; + +IC_INIT_NG: + g_u32_wearable_test_result = WEARABLE_FT_TEST_RESULT_TEST_INIT_NG; + return ERROR; + +} + +STATUS ft_test_read_used_pin_infor_3x(unsigned char *p_u8_infor) +{ + /*unsigned char u8_i;*/ + unsigned char u8_r_buf[2]; + + /*get Pin remap*/ + if (handle_ic_read(FW_FT_PIN_ADDR, 48, p_u8_infor, g_u8_drv_interface, I2C_BYTE_MODE) == ERROR) { + DEBUGOUT("[RUPI] NG!\r\n"); + return ERROR; + } +/* + * for (u8_i = 0; u8_i < 36; u8_i++) { + * DEBUGOUT("Pin[%d] remap=%d\r\n", u8_i, p_u8_infor[u8_i]); + * } + */ + + if (handle_ic_read(FW_FT_CHANNEL_X_ADDR, 2, u8_r_buf, g_u8_drv_interface, I2C_BYTE_MODE) == ERROR) { + DEBUGOUT("[RUPI] NG!\r\n"); + return ERROR; + } + g_u8_channel_x = u8_r_buf[0]; + g_u8_channel_y = u8_r_buf[1]; + DEBUGOUT("[RUPI] X:%d,Y:%d\r\n", g_u8_channel_x, g_u8_channel_y); + + + return SUCCESS; +} + +STATUS ft_test_ctrl_mbist_fun_3x(unsigned char u8_enable) +{ + unsigned int u32_read; + + if (u8_enable) { + if (handle_ic_read(REG_SYSCON_BLKEN_ADDR, 4, (unsigned char *)(&u32_read), g_u8_drv_interface, I2C_WORD_MODE) == ERROR) { + DEBUGOUT("Data RAM Test NG !!!\r\n"); + return ERROR; + } + + u32_read |= (1 << 29); + if (handle_ic_write(REG_SYSCON_BLKEN_ADDR, 4, (unsigned char *)&u32_read, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) { + DEBUGOUT("Data RAM Test NG !!!\r\n"); + return ERROR; + } + } else { + if (handle_ic_read(REG_SYSCON_BLKEN_ADDR, 4, (unsigned char *)(&u32_read), g_u8_drv_interface, I2C_WORD_MODE) == ERROR) { + DEBUGOUT("Data RAM Test NG !!!\r\n"); + return ERROR; + } + + u32_read &= ~(1 << 29); + if (handle_ic_write(REG_SYSCON_BLKEN_ADDR, 4, (unsigned char *)&u32_read, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) { + DEBUGOUT("Data RAM Test NG !!!\r\n"); + return ERROR; + } + } + return SUCCESS; +} + +/* + * STATUS ft_test_ram_test_3x(unsigned char u8_is_stop_mcu) + * { + * unsigned int u32_read; + * unsigned char u8_retry_times = 2; + * unsigned int u32_write = 0; + + * if ((g_st_test_info.u16_ft_test_info_1) == WEARBLE_FT_PRAM_SYSTEM_NG_CASE) { + * g_u32_wearable_test_result = WEARABLE_FT_TEST_RESULT_PRAM_NG; + * return ERROR; + * } + * + * RETRY_RAM_TEST: + * + * if (u8_is_stop_mcu) { + * stop_mcu_3x(TRUE); + * } + * + * if (ft_test_ctrl_mbist_fun_3x(ENABLE) == ERROR) { + * goto EXIT_ERROR; + * } + * + * u32_write = 0x04000080; + * if (handle_ic_write(RAM_WRITE_TEST_ADDR1, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) { + * goto EXIT_ERROR; + * } + * + * u32_write = 0x00FE90FE; + * if (handle_ic_write(RAM_WRITE_TEST_ADDR2, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) { + * goto EXIT_ERROR; + * } + * + * u32_write = 0x0A800080; + * if (handle_ic_write(RAM_WRITE_TEST_ADDR3, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) { + * goto EXIT_ERROR; + * } + * + * delay_ms(5); + * + * if (handle_ic_read(RAM_READ_TEST_ADDR1, 4, (unsigned char *)(&u32_read), g_u8_drv_interface, I2C_WORD_MODE) == ERROR) { + * goto EXIT_ERROR; + * } + * + * if ((u32_read & 0xFE00) != 0) { + * DEBUGOUT("RAM Test NG 954[0x%x]!!!\r\n", u32_read); + * goto EXIT_ERROR; + * } + * + * if (handle_ic_read(RAM_READ_TEST_ADDR2, 4, (unsigned char *)(&u32_read), g_u8_drv_interface, I2C_WORD_MODE) == ERROR) { + * goto EXIT_ERROR; + * } + * + * if (u32_read != 0x19007F00) { + * DEBUGOUT("RAM Test NG B04[0x%x]!!!\r\n", u32_read); + * goto EXIT_ERROR; + * } + * + * if (handle_ic_read(RAM_READ_TEST_ADDR3, 4, (unsigned char *)(&u32_read), g_u8_drv_interface, I2C_WORD_MODE) == ERROR) { + * goto EXIT_ERROR; + * } + * + * if (u32_read != 0x40007F00) { + * DEBUGOUT("RAM Test NG B08[0x%x]!!!\r\n", u32_read); + * goto EXIT_ERROR; + * } + * + * if (ft_test_ctrl_mbist_fun_3x(DISABLE) == ERROR) { + * goto EXIT_ERROR; + * } else { + * DEBUGOUT("RAM Test Pass\r\n"); + * return SUCCESS; + * } + * + * EXIT_ERROR: + * if ((u8_retry_times-- > 0)) { + * goto RETRY_RAM_TEST; + * } + * + * ft_test_ctrl_mbist_fun_3x(DISABLE); + * g_u32_wearable_test_result = WEARABLE_FT_TEST_RESULT_PRAM_NG; + * DEBUGOUT("PRAM Test NG !!!\r\n"); + * return ERROR; + * } + */ +STATUS ft_test_ram_test_3x(unsigned char u8_is_stop_mcu) +{ + unsigned int u32_read; + unsigned char u8_retry_times = 2, u8_check_time = 8; + unsigned int u32_write = 0; + unsigned int u32_addr = 0; + unsigned short u16_retry, u16DataBufLength; + + if ((g_st_test_info.u16_ft_test_info_1) == WEARBLE_FT_PRAM_SYSTEM_NG_CASE) { + g_u32_wearable_test_result = WEARABLE_FT_TEST_RESULT_PRAM_NG; + return ERROR; + } + +RETRY_RAM_TEST: + +#if ENABLE_WDT + /* reload the Watchdog timer*/ + Chip_WWDT_Feed(LPC_WWDT); +#endif + + if (u8_is_stop_mcu) + stop_mcu_3x(TRUE); + + u32_write = (USEFW_LOCK | CONFIG_LOCK | COMP_LOCK | BASEL_LOCK | INICO_LOCK); + if (handle_ic_write(REG_FLASHCTL_FLASH_PRAM_LOCK, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + goto EXIT_ERROR; + + /* i2c tx buffer size = 0x38 (56)*/ + u16DataBufLength = 0x38; + u32_addr = 0; + while (u32_addr < u16DataBufLength) { + g_u8_data_buf[u32_addr] = 0xFF; + g_u8_data_buf[u32_addr + 1] = 0x00; + g_u8_data_buf[u32_addr + 2] = 0xAA; + g_u8_data_buf[u32_addr + 3] = 0x55; + u32_addr += 4; + } + u32_addr = PRAM_BOOT_START; + + while (u32_addr < PRAM_BOOT_LENGTH) { + if ((u32_addr + u16DataBufLength) > PRAM_BOOT_LENGTH) + u16DataBufLength = PRAM_BOOT_LENGTH - u32_addr; + + if (handle_ic_write(u32_addr, u16DataBufLength, g_u8_data_buf, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + goto EXIT_ERROR; + + u32_addr += u16DataBufLength; + +#if ENABLE_WDT + /* reload the Watchdog timer*/ + Chip_WWDT_Feed(LPC_WWDT); +#endif + } + + gpio_touch_reset_pin_control(FALSE);/*Low*/ + delay_ms(1); + gpio_touch_reset_pin_control(TRUE);/*High*/ + delay_ms(25); + + g_u8_mute_i2c_err_log = TRUE; + + u16_retry = 150; + do { +#if ENABLE_WDT + /* reload the Watchdog timer*/ + Chip_WWDT_Feed(LPC_WWDT); +#endif + + delay_ms(35); + if (handle_ic_read(REG_FLASHCTL_FLASH_STATE_REG_ADDR, 4, (unsigned char *)(&u32_read), g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + goto EXIT_ERROR; + + /*delay_ms(2);*/ + u16_retry--; + } while (((u32_read & BLDR_FINISH) != BLDR_FINISH) && (u16_retry != 0)); + + g_u8_mute_i2c_err_log = FALSE; + + if ((u32_read & BLDR_FINISH) != BLDR_FINISH) { + g_u32_wearable_test_result |= WEARABLE_FT_TEST_RESULT_PRAM_NG; + DEBUGOUT("PRAM Test NG !!!\r\n"); + return ERROR; + } + + if ((u8_check_time-- > 0)) + goto RETRY_RAM_TEST; + + DEBUGOUT("RAM Test Pass\r\n"); + return SUCCESS; + +EXIT_ERROR: + if ((u8_retry_times-- > 0)) { + u8_check_time = 4; + goto RETRY_RAM_TEST; + } + + g_u32_wearable_test_result = WEARABLE_FT_TEST_RESULT_PRAM_NG; + DEBUGOUT("PRAM Test NG !!!\r\n"); + + g_u8_mute_i2c_err_log = FALSE; + + return ERROR; +} + +STATUS ft_test_connect_test_3x(void) +{ + unsigned char u8_retry = IC_TEST_RETRY_TIME; + unsigned int u32_w_buf, u32_r_buf; + unsigned int u32_test_addr = RM_DATAMEM0_BASE; + char i8_str[8]; + + if ((g_st_test_info.u16_ft_test_info_1) == WEARBLE_FT_I2C_SYSTEM_NG_CASE) { + g_u32_wearable_test_result = WEARABLE_FT_TEST_RESULT_I2C_NG; + return ERROR; + } + +RETRY_CONNECT_TEST: + + if (g_u8_drv_interface == SPI_INTERFACE) + snprintf(i8_str, RM_F303_MAX_STR_LENGTH, "SPI"); + else + snprintf(i8_str, RM_F303_MAX_STR_LENGTH, "I2C"); + + u32_w_buf = 0x55aa00ff; + + if (handle_ic_write(u32_test_addr, 4, (unsigned char *)&u32_w_buf, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) { + DEBUGOUT("%s Test Write NG!!\r\n", i8_str); + goto NG_CASE; + } + + if (handle_ic_read(u32_test_addr, 4, (unsigned char *)&u32_r_buf, g_u8_drv_interface, I2C_BYTE_MODE) == ERROR) { + DEBUGOUT("%s Test Read NG!!\r\n", i8_str); + goto NG_CASE; + } + + + if (u32_w_buf != u32_r_buf) { + DEBUGOUT("%s Test Compare NG [%d], W=0x%x,R=0x%x\r\n", i8_str, 0, u32_w_buf, u32_r_buf); + goto NG_CASE; + } + + +#if ENABLE_WDT + Chip_WWDT_Feed(LPC_WWDT);/* reload the Watchdog timer*/ +#endif + + + DEBUGOUT("%s Test Pass\r\n", i8_str); + return SUCCESS; + +NG_CASE: + if (--u8_retry > 0) { + DEBUGOUT("%s Test Retry=%d\r\n", i8_str, u8_retry); + if (hardware_reset_3x(TRUE) == ERROR) + goto NG_CASE2; + /*Stop MCU*/ + stop_mcu_3x(FALSE); + goto RETRY_CONNECT_TEST; + } + +NG_CASE2: + + g_u32_wearable_test_result = WEARABLE_FT_TEST_RESULT_I2C_NG; + DEBUGOUT("%s Test NG !!!\r\n", i8_str); + return ERROR; +} + +STATUS ft_test_reset_pin_test_3x(void) +{ + unsigned int u32_write = 0; + + if ((g_st_test_info.u16_ft_test_info_1) == WEARBLE_FT_RESET_PIN_SYSTEM_NG_CASE) { + g_u32_wearable_test_result = WEARABLE_FT_TEST_RESULT_RESET_NG; + return ERROR; + } + + u32_write = 0x00000404; + if (handle_ic_write(REG_SYSCON_MISCIER_ADDR, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + if (!gpio_touch_int_pin_state_access()) { /* check INT in high state*/ + g_u32_wearable_test_result = WEARABLE_FT_TEST_RESULT_INT_NG; + return ERROR; + } + gpio_touch_reset_pin_control(FALSE);/*Low*/ + delay_ms(1); + if (gpio_touch_int_pin_state_access()) { + g_u32_wearable_test_result = WEARABLE_FT_TEST_RESULT_RESET_NG; + DEBUGOUT("Reset Pin Test NG(Not High) !!!\r\n"); + return ERROR; + } + gpio_touch_reset_pin_control(TRUE);/*High*/ + delay_ms(25); + DEBUGOUT("Reset Pin Test Pass !!!\r\n"); + return SUCCESS; +} + +STATUS ft_test_panel_model_check_3x(unsigned short u16_version) +{ + unsigned int u32_read; + unsigned short u16_model_version; + + if (handle_ic_read(FW_FT_SRAM_FW_VERSION, 4, (unsigned char *)(&u32_read), g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + u16_model_version = (u32_read & 0x0000FFFF); + DEBUGOUT("Panel Model Test! 0x%08X:0x%08X\r\n", u32_read, u16_version); + DEBUGOUT("Panel Model 0x%08X\r\n", u16_model_version); + if (u16_model_version == u16_version) + return SUCCESS; + + DEBUGOUT("Panel Model NG! 0x%x:0x%x\r\n", u16_model_version, u16_version); + return ERROR; +} + +void ft_raw_data_checksum_cal_3x(unsigned short *u16_buffer) +{ + unsigned char u8_i; + + u16_buffer[48] = 0x55AA; + u16_buffer[49] = 0; + for (u8_i = 0; u8_i < (MAX_SENSING_PIN_NUM - 1); u8_i++) + u16_buffer[49] += u16_buffer[u8_i]; + + DEBUGOUT("[RDCSA] %x:%x\r\n", u16_buffer[48], u16_buffer[49]); +} + +void ft_test_result_checksum_cal_3x(unsigned char *u8_buffer) +{ + unsigned char u8_i; + + u8_buffer[48] = 0x5A; + u8_buffer[49] = 0; + for (u8_i = 0; u8_i < (MAX_SENSING_PIN_NUM - 1); u8_i++) + u8_buffer[49] += u8_buffer[u8_i]; + + DEBUGOUT("[TRCSA] %x:%x\r\n", u8_buffer[48], u8_buffer[49]); +} + +STATUS ft_raw_data_checksum_check_3x(unsigned short *u16_buffer) +{ + unsigned char u8_i; + unsigned short u16_sum = 0; + + if (u16_buffer[48] != 0x55AA) { + DEBUGOUT("u16_buffer[34]:%x\r\n", u16_buffer[48]); + DEBUGOUT("[RDCSC] Pattern NG! [0x%p](0x%x)\n", u16_buffer, u16_buffer[48]); + return ERROR; + } + + /*u16_buffer[35]=0;*/ + for (u8_i = 0; u8_i < (MAX_SENSING_PIN_NUM - 1); u8_i++) + u16_sum += u16_buffer[u8_i]; + + if (u16_buffer[49] != u16_sum) { + DEBUGOUT("[RDCSC] Check SUM NG! [0x%p](0x%x:0x%x)\n", u16_buffer, u16_sum, u16_buffer[49]); + return ERROR; + } + + DEBUGOUT("[RDCSC] PASS (0x%x:0x%x)\r\n", u16_buffer[48], u16_buffer[49]); + + return SUCCESS; +} + +STATUS ft_test_result_checksum_check_3x(unsigned char *u8_buffer) +{ + unsigned char u8_i; + unsigned char u8_sum = 0; + + if (u8_buffer[48] != 0x5A) { + DEBUGOUT("[TRCSC] Pattern NG! [0x%p](0x%x:0x%x)\n", u8_buffer, u8_buffer[48], u8_buffer[49]); + return ERROR; + } + + /*u32_buffer[35]=0;*/ + for (u8_i = 0; u8_i < (MAX_SENSING_PIN_NUM - 1); u8_i++) + u8_sum += u8_buffer[u8_i]; + + if (u8_buffer[49] != u8_sum) { + DEBUGOUT("[TRCSC] Check SUM NG! [0x%p](0x%x:0x%x)\n", u8_buffer, u8_sum, u8_buffer[49]); + return ERROR; + } + + DEBUGOUT("[TRCSC] PASS (0x%x:0x%x)\r\n", u8_buffer[48], u8_buffer[49]); + + return SUCCESS; +} + +STATUS baseline_update_control_3x(bool b_enable_baseline_update) +{ + unsigned int u32_write_buf; + unsigned char u8_read; + short i16_time_out = 100; + + if (b_enable_baseline_update) { + u32_write_buf = (SYS_CMD_FUNC_DIS_BS_UPDATE); + DEBUGOUT("[BUC] Enable Baseline update\r\n"); + } else { + u32_write_buf = (DIS_BASELINE_UPDATE | SYS_CMD_FUNC_DIS_BS_UPDATE); + DEBUGOUT("[BUC] Disable Baseline update\r\n"); + } + + if (handle_ic_write(FW_SYS_CMD_ADDR, 4, (unsigned char *)&u32_write_buf, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + delay_ms(5); + while (i16_time_out--) { + if (handle_ic_read(FW_SYS_CMD_ADDR, 1, &u8_read, g_u8_drv_interface, I2C_BYTE_MODE) == SUCCESS) { + if (u8_read == 0x00) + break; + } else { + DEBUGOUT("[BUC] I2C Read NG\r\n"); + return ERROR; + } + delay_ms(2); + } + + if (i16_time_out == IC_TEST_TIME_OUT) { + DEBUGOUT("[BUC] Baseline Update Control (%d) NG\r\n", b_enable_baseline_update); + return ERROR; + } + + return SUCCESS; +} + +STATUS wait_fw_init_ready_3x(void) +{ + unsigned short u16_time_out; + unsigned char u8_r_buf[2]; + + u16_time_out = 100; + /*Check FW Ready ?*/ + do { + if (handle_ic_read(FW_FT_ARG0_ADDR, 2, u8_r_buf, g_u8_drv_interface, I2C_BYTE_MODE) == ERROR) + return ERROR; + DEBUGOUT("FW Flag = 0x%x,0x%x\r\n", u8_r_buf[0], u8_r_buf[1]); + if (u8_r_buf[0] == 0xAA && u8_r_buf[1] == 0x55) + return SUCCESS; + delay_ms(2); + + } while (u16_time_out-- > 0); + + return ERROR; +} + + +STATUS enter_normal_fw_3x(void) +{ + + unsigned char u8_pattern_noise_only = 0; +#if ENABLE_TEST_TIME_MEASURMENT + unsigned int u32_fun_time = 0; + + u32_fun_time = get_system_time(); +#endif + if (g_u8_is_normal_fw) + return SUCCESS; + +#if !SELFTEST_3X + /*Set INT Falling Triggle*/ + gpio_touch_int_trigger_control(FALSE); +#endif + + g_u32_wearable_test_result &= ~WEARABLE_FT_TEST_RESULT_NORMAL_FW_RESET_NG; + + if (g_u8_drv_interface == SPI_INTERFACE) { + if (hardware_reset_3x(TRUE) == ERROR) { + DEBUGOUT("[ENF]No INT, no OTP!!\r\n"); + g_u32_wearable_test_result |= WEARABLE_FT_TEST_RESULT_NORMAL_FW_RESET_NG; + return ERROR; + } + +#if !SELFTEST_3X + if (burn_pram_from_dongle_flash_3x() == ERROR) + return ERROR; +#endif + } else { + if ((g_st_test_info.u16_ft_test_item & ~(IC_TEST_ITEMS_SYSTEM | IC_TEST_ITEMS_PANEL_TEST_2)) == FALSE) { + u8_pattern_noise_only = TRUE; + DEBUGOUT("Panel Test!!!\r\n"); + } + + if (!u8_pattern_noise_only) { + /*PRAM Test*/ + if (ft_test_ram_test_3x(1) == ERROR) + return ERROR; + } + + if (hardware_reset_3x(FALSE) == ERROR) { + DEBUGOUT("[ENF]No INT, no OTP!!\r\n"); + g_u32_wearable_test_result |= WEARABLE_FT_TEST_RESULT_NORMAL_FW_RESET_NG; + return ERROR; + } + } + + if (wait_fw_init_ready_3x() == ERROR) + return ERROR; + + if (ft_test_read_used_pin_infor_3x(g_u8_wearable_pin_map) == ERROR) + return ERROR; + +#if ENABLE_TEST_TIME_MEASURMENT + DEBUGOUT("[ENF] (%d)\r\n", get_system_time() - u32_fun_time); +#endif + + g_u8_is_normal_fw = TRUE; + return SUCCESS; +} + +STATUS check_cc_bl_flag_3x(void) +{ + unsigned char u8_read_buf[4]; + unsigned char u8_is_cc_ready = 0; + unsigned char u8_fw_version[4]; + short i16_time_out; + + DEBUGOUT("[CCBF] Check CC BL Flag\r\n"); + + if (handle_ic_read(FW_FT_FW_VERSION, 4, u8_fw_version, g_u8_drv_interface, I2C_BYTE_MODE) == ERROR) + return ERROR; + + i16_time_out = 20; + + while (i16_time_out--) { + if (!u8_is_cc_ready) { + if (handle_ic_read(PRAM_ADDR_CC_INFO, 4, u8_read_buf, g_u8_drv_interface, I2C_BYTE_MODE) == ERROR) + return ERROR; + if ((u8_read_buf[0] >= 1) && (u8_read_buf[2] == u8_fw_version[2]) && (u8_read_buf[3] == u8_fw_version[3])) + u8_is_cc_ready = 1; + } + + if (u8_is_cc_ready) + break; + delay_ms(1); + } + + /*Check flag*/ + if (i16_time_out == IC_TEST_TIME_OUT) { + DEBUGOUT("CC Flag=%d NG!!\r\n", u8_read_buf[0]); + DEBUGOUT("FW Ver=%X.%X:%X.%X NG!!\r\n", u8_fw_version[2], u8_fw_version[3], u8_read_buf[2], u8_read_buf[3]); + return ERROR; + } + + return SUCCESS; +} + +STATUS burn_cc_to_ic_flash_3x(void) +{ + short i16_time_out = 1000; + unsigned int u32_cc_table; + unsigned char u8_fw_value[4]; + unsigned int u32_write = 0; +#if SELFTEST_3X + unsigned int u32_read; +#endif + +#if ENABLE_TEST_TIME_MEASURMENT_CC + DEBUGOUT("[BCTF] Before Switch to Bootloader\r\n"); + g_u32_spend_time = get_system_time(); +#endif + +#if !SELFTEST /*210708 add, for fw use bootloader block*/ + if (stop_mcu_3x(0) == ERROR) + return ERROR; + + /*WRT boot-loader to PRAM first*/ + u32_write = (USEFW_LOCK | CONFIG_LOCK | COMP_LOCK | BASEL_LOCK); + if (handle_ic_write(REG_FLASHCTL_FLASH_PRAM_LOCK, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + /*read flash data and write to pram (Bootloader)*/ + if (handle_spiFlash_to_pram(PRAM_BOOT_START, PRAM_BOOT_LENGTH, PRAM_BOOT_START) == ERROR) { + DEBUGOUT("confirm crc error!!\r\n"); + return ERROR; + } + + /*check pram bootloader crc*/ + if (check_pram_crc32_3x(PRAM_BOOT_START, PRAM_BOOT_CRC_LENGTH) == ERROR) { + DEBUGOUT("confirm bootloader crc error!!\r\n"); + return ERROR; + } + + /*read flash data and write to pram (Initial_code)*/ + if (handle_spiFlash_to_pram(PRAM_DIS_INIT_START, PRAM_DIS_INIT_LENGTH, PRAM_DIS_INIT_START) == ERROR) { + DEBUGOUT("confirm crc error!!\r\n"); + return ERROR; + } + + /*check pram Initial code crc*/ + if (check_pram_crc32_3x(PRAM_DIS_INIT_START, 0x7C) == ERROR) { + DEBUGOUT("confirm init crc error!!\r\n"); + return ERROR; + } +#endif + + /*Set Skip_Load = 1*/ + u32_write = (BL_CRC_CHK | SKIP_LOAD); + if (handle_ic_write(REG_FLASHCTL_FLASH_STATE_REG_ADDR, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + /*wait sw rst finish*/ + u32_write = BLKRST_SW_RST; + if (handle_ic_write(REG_SYSCON_BLKRST_ADDR, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + delay_ms(2); + + if (wait_fw_state_3x(BOOT_RET_DATA_ADDR, WAIT_TEST_MODE, 1, i16_time_out) == ERROR) { + DEBUGOUT("[BCTF] Check Burn CC & BL Fail\r\n"); + return ERROR; + } + +#if ENABLE_TEST_TIME_MEASURMENT_CC + DEBUGOUT("[BCTF] End of Switch to Bootloader = %d\r\n", get_system_time() - g_u32_spend_time); + g_u32_spend_time = get_system_time(); + DEBUGOUT("TICK=%d\r\n", get_system_time()); +#endif + +#if SELFTEST_3X + if (sysfs_burn_cc_bl()) { + /*Read Flash CC Table*/ + if (read_fpc_flash_3x(FLASH_NORMAL_FW_CC_TABLE_ADDR, &u32_cc_table) == ERROR) + return ERROR; + DEBUGOUT("Flash Do CC FW Version=0x%04X\r\n", (u32_cc_table & 0xFFFF0000) >> 16); + DEBUGOUT("Flash Do CC Flag=0x%X\r\n", (u32_cc_table & 0x0000FFFF)); + g_u32_fw_cc_version = u32_cc_table; + + /*Read PRAM FW Version*/ + if (handle_ic_read(FW_FT_SRAM_FW_VERSION, 4, u8_fw_value, g_u8_drv_interface, I2C_BYTE_MODE) == ERROR) + return ERROR; + DEBUGOUT("SRAM FW Version=0x%03x,0x%03x\r\n", u8_fw_value[3], u8_fw_value[2]); + + if (((u32_cc_table & 0xFF000000) >> 24) != u8_fw_value[3] || ((u32_cc_table & 0x00FF0000) >> 16) != u8_fw_value[2]) { + DEBUGOUT("Flash CC Table FW Version is not match!\r\n"); + if (handle_ic_read(REG_FLASHCTL_FLASH_STATE_REG_ADDR, 4, (unsigned char *)(&u32_read), g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + DEBUGOUT("read 5000 0918 error\r\n"); + DEBUGOUT("Flash 0x50000918=0x%08X\r\n", u32_read); + return ERROR; + } + + return SUCCESS; + } else + return ERROR; +#else + if (burn_to_ic_flash_3x(COMP_AREA) == ERROR) { + DEBUGOUT("[BCTF] Burn CC & BL Fail\r\n"); + return ERROR; + } + + u32_write = 0x00000000; + if (handle_ic_write(BOOT_TEST_MODE_ADDR, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + /* clr sync_data(0x20000200) = 0 as finish*/ + u32_write = 0x00000000; + if (handle_ic_write(BOOT_SYNC_DATA_ADDR, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + delay_ms(10); + + if (wait_fw_state_3x(REG_FLASHCTL_FLASH_STATE_REG_ADDR, 0x4000, 2, i16_time_out) == ERROR) { + DEBUGOUT("[BCTF] Check Burn CC & BL Fail\r\n"); + return ERROR; + } + +#if ENABLE_AUO_VERIFY_LOG + if (read_fpc_flash_3x((unsigned int)(FLASH_FW_START + PRAM_FW_LENGTH - 4), &u32_i2c_read_data)) + DEBUGOUT("Normal FW CRC=0x%08X\r\n", u32_i2c_read_data); +#endif + +#if ENABLE_TEST_TIME_MEASURMENT_CC + DEBUGOUT("BCTF] Burn to flash finish[Tick=%d]!\r\n", get_system_time() - g_u32_spend_time); +#endif + + /*Read Flash CC Table*/ + if (read_fpc_flash_3x(FLASH_NORMAL_FW_CC_TABLE_ADDR, &u32_cc_table) == ERROR) + return ERROR; + DEBUGOUT("Flash Do CC FW Version=0x%04X\r\n", (u32_cc_table & 0xFFFF0000) >> 16); + DEBUGOUT("Flash Do CC Flag=0x%X\r\n", (u32_cc_table & 0x0000FFFF)); + + /*Read PRAM FW Version*/ + if (handle_ic_read(FW_FT_SRAM_FW_VERSION, 4, u8_fw_value, g_u8_drv_interface, I2C_BYTE_MODE) == ERROR) + return ERROR; + DEBUGOUT("SRAM FW Version=0x%03x,0x%03x\r\n", u8_fw_value[3], u8_fw_value[2]); + + if (((u32_cc_table & 0xFF000000) >> 24) != u8_fw_value[3] || ((u32_cc_table & 0x00FF0000) >> 16) != u8_fw_value[2]) { + DEBUGOUT("Flash CC Table FW Version is not match!\r\n"); + return ERROR; + } + + + return SUCCESS; +#endif +} + +STATUS do_calibration_3x(unsigned char u8_do_calibration_cmd, unsigned char u8_burn_flash) +{ + unsigned char u8_value[4]; + short u8_time_out = 100; + unsigned char u8_retry = IC_TEST_RETRY_TIME; + + g_u32_wearable_test_result &= ~(WEARABLE_FT_TEST_RESULT_CC_CALIBRATION_NG | WEARABLE_FT_TEST_RESULT_BURN_CC_NG); + + if (u8_do_calibration_cmd == TRUE) { + +#if ENABLE_TEST_TIME_MEASURMENT_CC + DEBUGOUT("[DOCC] Start do calibration (%d)\r\n", get_system_time()); + g_u32_spend_time = get_system_time(); +#endif + while (u8_retry--) { + u8_value[0] = SYS_CMD_DO_CC_CAL; + if (handle_ic_write(FW_SYS_CMD_ADDR, 1, u8_value, g_u8_drv_interface, I2C_BYTE_MODE) == ERROR) /*Trigger Calibration*/ + return ERROR; + + u8_time_out = 400; + + /* Wait calibration ready*/ + while (u8_time_out--) { + delay_ms(10);/*20*/ + if (handle_ic_read(FW_SYS_CMD_ADDR, 4, u8_value, g_u8_drv_interface, I2C_BYTE_MODE) == ERROR) + return ERROR; + else if (u8_value[0] == 0) + break; +#if ENABLE_WDT + Chip_WWDT_Feed(LPC_WWDT);/* reload the Watchdog timer*/ +#endif + } + + if (u8_time_out == IC_TEST_TIME_OUT) { + if (u8_retry) { + DEBUGOUT("[DOCC] Do CC Retry (%d,%d)", u8_retry, u8_value[0]); + hardware_reset_3x(TRUE); +#if !SELFTEST_3X + if (g_u8_drv_interface == SPI_INTERFACE) { + if (burn_pram_from_dongle_flash_3x() == ERROR) + return ERROR; + } +#endif + delay_ms(20); + continue; + } else { + g_u32_wearable_test_result |= WEARABLE_FT_TEST_RESULT_CC_CALIBRATION_NG; + DEBUGOUT("[DOCC] SYS CMD CC NG\r\n"); + return ERROR; + } + } else { + if (check_cc_bl_flag_3x() == ERROR) { + if (u8_retry) { + DEBUGOUT("[DOCC] CC Flag Retry (%d)", u8_retry); + hardware_reset_3x(TRUE); +#if !SELFTEST_3X + if (g_u8_drv_interface == SPI_INTERFACE) { + if (burn_pram_from_dongle_flash_3x() == ERROR) + return ERROR; + } +#endif + delay_ms(20); + continue; + } else { + g_u32_wearable_test_result |= WEARABLE_FT_TEST_RESULT_CC_CALIBRATION_NG; + DEBUGOUT("[DOCC] CC Flag NG\r\n"); + return ERROR; + } + } else /*Calibration OK*/ + break; + } + } +#if ENABLE_TEST_TIME_MEASURMENT_CC + DEBUGOUT("[DOCC] Do CC Finish! [%d:%d]\r\n", get_system_time() - g_u32_spend_time, u8_time_out); + g_u32_spend_time = get_system_time(); +#endif + } + + if (u8_burn_flash == FALSE) { + DEBUGOUT("[DOCC] Do Calibration Finish \r\n"); + return SUCCESS; + } + + if (burn_cc_to_ic_flash_3x() == ERROR) { + DEBUGOUT("[DOCC] Burn to flash NG!\r\n"); + hardware_reset_3x(TRUE); + g_u32_wearable_test_result |= WEARABLE_FT_TEST_RESULT_BURN_CC_NG; + return ERROR; + } + +#if ENABLE_TEST_TIME_MEASURMENT_CC + DEBUGOUT("[DOCC] Burn CC Finish (%d)\r\n", get_system_time() - g_u32_spend_time); +#endif + + DEBUGOUT("[DOCC] Burn CC Finish \r\n"); + return SUCCESS; +} + +STATUS hw_int_pin_Test_3x(void) +{ + unsigned int u32_write = 0; + + if ((g_st_test_info.u16_ft_test_info_1) == WEARBLE_FT_INT_SYSTEM_NG_CASE) { + g_u32_wearable_test_result = WEARABLE_FT_TEST_RESULT_INT_NG; + return ERROR; + } + + /*Trigger INT to Low*/ + u32_write = 0x00000004; + if (handle_ic_write(REG_SYSCON_MISCIER_ADDR, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + /*Read Pin state*/ + if (!gpio_touch_int_pin_state_access()) { + /*DEBUGOUT("INT is LOW\r\n");*/ + } else { + DEBUGOUT("INT Test NG!\r\n"); + g_u32_wearable_test_result = WEARABLE_FT_TEST_RESULT_INT_NG; + return ERROR; + } + + /*Trigger INT to High*/ + u32_write = 0x00000404; + if (handle_ic_write(REG_SYSCON_MISCIER_ADDR, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) + return ERROR; + + /*Read Pin state*/ + if (gpio_touch_int_pin_state_access()) { + /*DEBUGOUT("INT is High\r\n");*/ + } else { + DEBUGOUT("INT Test NG!\r\n"); + g_u32_wearable_test_result = WEARABLE_FT_TEST_RESULT_INT_NG; + return ERROR; + } + gpio_touch_int_access(TRUE); + DEBUGOUT("INT Test Pass\r\n"); + return SUCCESS; +} + +STATUS read_test_fw_data_3x(unsigned short u16_test_items) +{ + unsigned char u8_retry = IC_TEST_RETRY_TIME; + unsigned int u32_test_item_result; +#if ENABLE_TEST_TIME_MEASURMENT + unsigned int u32_fun_time = 0; + + u32_fun_time = get_system_time(); +#endif + + g_u32_wearable_test_result &= ~(WEARABLE_FT_TEST_RESULT_GET_DATA_NG | WEARABLE_FT_TEST_RESULT_OPEN_NG + | WEARABLE_FT_TEST_RESULT_SHORT_NG | WEARABLE_FT_TEST_RESULT_UB_NG + | WEARABLE_FT_TEST_RESULT_UC_NG | WEARABLE_FT_TEST_RESULT_SINGLE_CC_OPEN_NG + | WEARABLE_FT_TEST_RESULT_SINGLE_CC_SHORT_NG); + if ((g_st_test_info.u16_ft_test_item & (IC_TEST_ITEMS_OPEN | IC_TEST_ITEMS_SHORT | + IC_TEST_ITEMS_UC | IC_TEST_ITEMS_UB)) == 0) { + return SUCCESS; + } + +RETRY: + memset(g_i16_raw_data_1_short_buf, 0, sizeof(g_i16_raw_data_1_short_buf)); + memset(g_i16_raw_data_2_open_buf, 0, sizeof(g_i16_raw_data_2_open_buf)); + memset(g_u16_raw_data3_cc_buf, 0, sizeof(g_u16_raw_data3_cc_buf)); + memset(g_u16_uc_buf, 0, sizeof(g_u16_uc_buf)); + memset(g_u8_wearable_pin_map, F303_NA_P, sizeof(g_u8_wearable_pin_map)); + + if (handle_ic_read(FT_RAWDATA1_SHORT_BUF_ADDR, MAX_SENSING_PIN_NUM * 2, (unsigned char *)g_i16_raw_data_1_short_buf, g_u8_drv_interface, I2C_BYTE_MODE) == ERROR) + goto ERROR_EXIT; + + + if (handle_ic_read(FT_RAWDATA2_OPEN_BUF_ADDR, MAX_SENSING_PIN_NUM * 2, (unsigned char *)g_i16_raw_data_2_open_buf, g_u8_drv_interface, I2C_BYTE_MODE) == ERROR) + goto ERROR_EXIT; + + + if (handle_ic_read(FT_RAWDATA3_CC_BUF_ADDR, MAX_SENSING_PIN_NUM * 2, (unsigned char *)(g_u16_raw_data3_cc_buf), g_u8_drv_interface, I2C_BYTE_MODE) == ERROR) + goto ERROR_EXIT; + + if (handle_ic_read(FT_UC_BUF_ADDR, MAX_SENSING_PIN_NUM * 2, (unsigned char *)g_u16_uc_buf, g_u8_drv_interface, I2C_BYTE_MODE) == ERROR) + goto ERROR_EXIT; + + if (handle_ic_read(FT_TEST_RESULT_BUF_ADDR, MAX_SENSING_PIN_NUM, (unsigned char *)(g_u8_test_result), g_u8_drv_interface, I2C_BYTE_MODE) == ERROR) + goto ERROR_EXIT; + + if (handle_ic_read(FT_TEST_ITEM_RESULT, 4, (unsigned char *)(&u32_test_item_result), g_u8_drv_interface, I2C_BYTE_MODE) == ERROR) + goto ERROR_EXIT; + + if (ft_raw_data_checksum_check_3x((unsigned short *)g_i16_raw_data_1_short_buf) == ERROR) + goto ERROR_EXIT; + + if (ft_raw_data_checksum_check_3x((unsigned short *)g_i16_raw_data_2_open_buf) == ERROR) + goto ERROR_EXIT; + + if (ft_raw_data_checksum_check_3x((unsigned short *)g_u16_raw_data3_cc_buf) == ERROR) + goto ERROR_EXIT; + + if (ft_raw_data_checksum_check_3x(g_u16_uc_buf) == ERROR) + goto ERROR_EXIT; + + if (ft_test_result_checksum_check_3x(g_u8_test_result) == ERROR) + goto ERROR_EXIT; + + if (ft_test_read_used_pin_infor_3x(g_u8_wearable_pin_map) == ERROR) + goto ERROR_EXIT; + +#if ENABLE_TEST_RSU_DATA_SHOW + DEBUGOUT("Slow Data:\r\n"); + dump_image_data_3x(g_i16_raw_data_1_short_buf, TRUE); + DEBUGOUT("Quick Data:\r\n"); + dump_image_data_3x(g_i16_raw_data_2_open_buf, TRUE); + DEBUGOUT("P CC:\r\n"); + dump_image_hex_data_3x((short *)g_u16_raw_data3_cc_buf); + DEBUGOUT("Open Golden CC:\r\n"); + dump_image_hex_data_3x((short *)g_u16_raw_data3_golden_cc_buf); + DEBUGOUT("UC:\r\n"); + dump_image_data_3x((short *)g_u16_uc_buf, TRUE); + DEBUGOUT("Golden UC:\r\n"); + dump_image_data_3x((short *)g_u16_uc_golden_cc_buf, TRUE); + DEBUGOUT("test item result:0x%x\r\n", u32_test_item_result); +#endif + + if (g_u8_print_debug_msg & PRINT_DEBUG_MSG_TYPE_4) { + DEBUGOUT("Slow Data:\r\n"); + dump_image_data_3x(g_i16_raw_data_1_short_buf, TRUE); + DEBUGOUT("Quick Data:\r\n"); + dump_image_data_3x(g_i16_raw_data_2_open_buf, TRUE); + DEBUGOUT("P CC:\r\n"); + dump_image_data_3x((short *)g_u16_raw_data3_cc_buf, TRUE); + + } + if (g_u8_print_debug_msg & PRINT_DEBUG_MSG_TYPE_5) { + DEBUGOUT("UC Data:\r\n"); + dump_image_data_3x((short *)g_u16_uc_buf, TRUE); + } + +#if ENABLE_TEST_TIME_MEASURMENT + DEBUGOUT("#read_test_data end=%d\r\n", get_system_time() - u32_fun_time); +#endif + g_u32_wearable_test_result |= u32_test_item_result; + DEBUGOUT("Read Test FW Result Finish\r\n"); + return SUCCESS; + +ERROR_EXIT: + if (u8_retry) { + u8_retry--; + DEBUGOUT("Read Test FW Result Retry:%d", u8_retry); + goto RETRY; + } + + g_u32_wearable_test_result |= WEARABLE_FT_TEST_RESULT_GET_DATA_NG; + DEBUGOUT("Read Test FW Result NG\r\n"); + return ERROR; +} + +void test_item_message_3x(void) +{ + unsigned short u16_test_item = g_st_test_info.u16_ft_test_item; + + DEBUGOUT("================================\r\n"); + DEBUGOUT("Enter 3x IC Test,(%d)\r\n", get_system_time()); + DEBUGOUT("IC Test Items=0x%X\r\n", u16_test_item); + + if (u16_test_item & IC_TEST_ITEMS_SYSTEM) + DEBUGOUT("Enable System Test\r\n"); + + if (u16_test_item & IC_TEST_ITEMS_OPEN) + DEBUGOUT("Enable Open Test\r\n"); + + if (u16_test_item & IC_TEST_ITEMS_SHORT) + DEBUGOUT("Enable Short Test\r\n"); + + if (u16_test_item & IC_TEST_ITEMS_UC) + DEBUGOUT("Enable Uniformity CC test\r\n"); + + if (u16_test_item & IC_TEST_ITEMS_UB) + DEBUGOUT("Enable Uniformity BL test\r\n"); + + if (u16_test_item & IC_TEST_ITEMS_BURN_FW) + DEBUGOUT("Enable Burn FW\r\n"); + + if (u16_test_item & IC_TEST_ITEMS_BURN_CC) + DEBUGOUT("Enable Burn CC\r\n"); +} + +STATUS burn_cc_3x(unsigned short u16_test_items) +{ + unsigned char u8_retry = IC_TEST_RETRY_TIME; +#if ENABLE_AUO_VERIFY_LOG + unsigned int u32_flash_crc = 0; +#endif +#if ENABLE_TEST_TIME_MEASURMENT + unsigned int u32_fun_time = 0; + + u32_fun_time = get_system_time(); +#endif + g_u32_wearable_test_result &= ~(WEARABLE_FT_TEST_RESULT_BURN_CC_NG | WEARABLE_FT_TEST_RESULT_NORMAL_FW_NG + | WEARABLE_FT_TEST_RESULT_CC_CALIBRATION_NG); + + if ((g_st_test_info.u16_ft_test_info_1) == WEARBLE_FT_BURN_CC_SYSTEM_NG_CASE) { + g_u32_wearable_test_result = WEARABLE_FT_TEST_RESULT_BURN_CC_NG; + return ERROR; + } + + if ((g_st_test_info.u16_ft_test_item & (IC_TEST_ITEMS_BURN_CC)) == 0) { + g_u32_wearable_test_result &= ~WEARABLE_FT_TEST_RESULT_NORMAL_FW_RESET_NG; + return SUCCESS; + } + + while (u8_retry--) { + if (enter_normal_fw_3x() == ERROR) { + if (u8_retry && !(g_u32_wearable_test_result & WEARABLE_FT_TEST_RESULT_PRAM_NG)) + continue; + else { + g_u32_wearable_test_result |= WEARABLE_FT_TEST_RESULT_NORMAL_FW_NG; + DEBUGOUT("Enter Normal FW NG\r\n"); + return ERROR; + } + } + /*Do CC*/ +#if ENABLE_WDT + Chip_WWDT_Feed(LPC_WWDT);/* reload the Watchdog timer*/ +#endif + + DEBUGOUT("[BC] Burn CC Start!!\r\n"); + if (g_u8_drv_interface == SPI_INTERFACE) { + /*flashless, bypass*/ + DEBUGOUT("[BC]Flashless Burn CC ByPass!!\r\n"); + return SUCCESS; + } + /*Burn CC to Flash*/ + if (do_calibration_3x(TRUE, TRUE) == SUCCESS) { + DEBUGOUT("[BC] Burn CC Pass!!(%d)\r\n", u8_retry); + break; + } + if (u8_retry) + continue; + else { + DEBUGOUT("[BC] Burn CC NG!!\r\n"); + return ERROR; + } + } + +#if ENABLE_TEST_TIME_MEASURMENT + DEBUGOUT("[BC] Burn CC Time(%d)\r\n", get_system_time() - u32_fun_time); +#endif + + return SUCCESS; +} + +/*Use dongle ext.Flash to read test fw and write to pram*/ +STATUS load_test_fw_3x(void) +{ + STATUS u8_ret = ERROR; +#if ENABLE_TEST_TIME_MEASURMENT + unsigned int u32_fun_time = 0; + + u32_fun_time = get_system_time(); + DEBUGOUT("load_test_fw Start Time= %d\r\n", u32_fun_time); +#endif + g_u32_wearable_test_result &= ~(WEARABLE_FT_TEST_RESULT_LOAD_TESTFW_NG); + + if (g_st_test_info.u16_ft_test_info_1 == WEARBLE_FT_LOAD_TEST_FW_SYSTEM_NG_CASE) { + g_u32_wearable_test_result = WEARABLE_FT_TEST_RESULT_LOAD_TESTFW_NG; + return ERROR; + } + + if ((g_st_test_info.u16_ft_test_item & (IC_TEST_ITEMS_OPEN | IC_TEST_ITEMS_SHORT | IC_TEST_ITEMS_UC | + IC_TEST_ITEMS_UB)) == 0) + return SUCCESS; + + DEBUGOUT("[LTFW] start load test fw\r\n"); +#if SELFTEST_3X + u8_ret = raydium_upgrade_test_fw_3x(0); +#else + u8_ret = load_test_fw_ft_3x(); +#endif + DEBUGOUT("[LTFW] Load test fw finish!!\r\n"); +#if ENABLE_TEST_TIME_MEASURMENT + DEBUGOUT("load_test_fw Spend Time= %d\r\n", get_system_time() - u32_fun_time); +#endif + + return u8_ret; +} + +STATUS system_test_3x(void) +{ + STATUS u8_test_result = SUCCESS; + unsigned short u16_panel_mode; +#if ENABLE_TEST_TIME_MEASURMENT + unsigned int u32_fun_time; +#endif + + /*Clear Test Item Test result*/ + g_u32_wearable_test_result &= ~(WEARABLE_FT_TEST_RESULT_I2C_NG | WEARABLE_FT_TEST_RESULT_INT_NG + | WEARABLE_FT_TEST_RESULT_RESET_NG | WEARABLE_FT_TEST_RESULT_PRAM_NG + | WEARABLE_FT_TEST_RESULT_IC_SUB_VERSION_NG | WEARABLE_FT_TEST_RESULT_IC_FW_VERIFY_NG); + + if ((g_st_test_info.u16_ft_test_item & IC_TEST_ITEMS_SYSTEM) == FALSE) + return SUCCESS; + + if (enable_ic_block_3x() == ERROR) { + DEBUGOUT("enable_ic_block NG!!!\r\n"); + /*return ERROR;*/ + } + + stop_mcu_3x(1); + +/* + * Check Dongle Ext Flash FW/Test FW version PK INI + * + * if (check_ext_flash_fw_version() == ERROR) { + * u8_test_result = ERROR; + * g_u32_wearable_test_result = WEARABLE_FT_TEST_RESULT_IC_FW_VERIFY_NG; + * goto ERROR_EXIT; + * } + */ + +#if ENABLE_TEST_TIME_MEASURMENT + u32_fun_time = get_system_time(); +#endif + /*I2C or SPI Test*/ + if (ft_test_connect_test_3x() == ERROR) { + u8_test_result = ERROR; + goto ERROR_EXIT; + } +#if ENABLE_TEST_TIME_MEASURMENT + DEBUGOUT("Interface test Spend Time= %d\r\n", get_system_time() - u32_fun_time); +#endif + + if ((g_st_test_info.u16_ft_eng_item & IC_TEST_ENG_ITEMS_RESV_12)) { + DEBUGOUT("INI Model = 0x%08X\r\n", g_st_test_para_resv.u32_normal_fw_version); + u16_panel_mode = (g_st_test_para_resv.u32_normal_fw_version & 0x0000FFFF); + if (ft_test_panel_model_check_3x(u16_panel_mode) == ERROR) { + u8_test_result = ERROR; + g_u32_wearable_test_result = WEARABLE_FT_TEST_RESULT_IC_FW_VERIFY_NG; + goto ERROR_EXIT; + } + } + +#if ENABLE_TEST_TIME_MEASURMENT + u32_fun_time = get_system_time(); +#endif + /*INT Pin Test*/ + if (hw_int_pin_Test_3x() == ERROR) { + u8_test_result = ERROR; + goto ERROR_EXIT; + } +#if ENABLE_TEST_TIME_MEASURMENT + DEBUGOUT("INT Spend Time= %d\r\n", get_system_time() - u32_fun_time); +#endif + +#if ENABLE_TEST_TIME_MEASURMENT + u32_fun_time = get_system_time(); +#endif + +/* + * RAM Test + * if (ft_test_ram_test_3x(0) == ERROR) { + * u8_test_result = ERROR; + * goto ERROR_EXIT; + * } + */ + +#if ENABLE_TEST_TIME_MEASURMENT + DEBUGOUT("RAM Spend Time= %d\r\n", get_system_time() - u32_fun_time); +#endif + + if ((g_st_test_info.u16_ft_eng_item & IC_TEST_ENG_ITEMS_RESV_11)) { + if (check_dev_sub_version_3x(g_st_test_para_resv.u8_para_resv_0[21]) == ERROR) { + u8_test_result = ERROR; + g_u32_wearable_test_result = WEARABLE_FT_TEST_RESULT_IC_SUB_VERSION_NG; + goto ERROR_EXIT; + } + } + + + +#if ENABLE_TEST_TIME_MEASURMENT + u32_fun_time = get_system_time(); +#endif + /*Reset Pin Test*/ + if (ft_test_reset_pin_test_3x() == ERROR) { + u8_test_result = ERROR; + goto ERROR_EXIT; + } + +#if ENABLE_TEST_TIME_MEASURMENT + DEBUGOUT("Reset Spend Time= %d\r\n", get_system_time() - u32_fun_time); +#endif + +ERROR_EXIT: + + if (u8_test_result == ERROR) + DEBUGOUT("System Test NG!!\r\n"); + else + DEBUGOUT("System Test PASS\r\n"); + + return u8_test_result; +} + +void do_ic_test_3x(void) +{ + unsigned short u16_test_items; + +#if ENABLE_TEST_TIME_MEASURMENT + unsigned char u8_ic_test_state; +#endif + +#if ENABLE_TEST_TIME_MEASURMENT + unsigned int u32_fun_time = 0; +#endif + unsigned int u32_read_data = 0; +#if ENABLE_CONTROL_OPENSHORT_WDT + Chip_WWDT_SetTimeOut(LPC_WWDT, (WDT_OSC / 10) * 150); +#endif + + g_u32_wearable_test_result &= ~(WEARABLE_FT_TEST_RESULT_POWER_ON_NG | WEARABLE_FT_TEST_RESULT_IC_VERSION_NG + | WEARABLE_FT_TEST_RESULT_MCU_HOLD_NG | WEARABLE_FT_TEST_RESULT_EXT_FLASH_EMPTY_NG + | WEARABLE_FT_TEST_RESULT_FLASH_ID_NG | WEARABLE_FT_TEST_RESULT_NORMAL_FW_VER_NG + | WEARABLE_FT_TEST_RESULT_PANEL_TEST_3_NG | WEARABLE_FT_TEST_RESULT_CB_NG | WEARABLE_FT_TEST_RESULT_TEST_FW_VER_NG + | WEARABLE_FT_TEST_RESULT_AUO_JIG_NG); + + if (!wearable_ic_test_init()) { + g_u32_wearable_test_result |= WEARABLE_FT_TEST_RESULT_TEST_INIT_NG; + g_u8_ic_test_state = IC_TEST_EXIT; + return; + } + + test_item_message_3x(); +#if !SELFTEST_3X + if ((g_st_test_info.u16_ft_eng_item & IC_TEST_ENG_ITEMS_RESV_16)) { + + + + DEBUGOUT("TEST STATUS= %d\r\n", g_u8_panel_jig_test_status); + + DEBUGOUT("[burn_header_log_to_flash]\r\n"); + if (!b_is_auo_jig_testing) { + b_is_auo_jig_testing = true; + g_u8_test_log_burn_times = 0; + if (!burn_header_log_to_flash_3x(FALSE, FALSE)) + DEBUGOUT("[burn_header_log_to_flash_3x ERROR!]\r\n"); + if (g_u8_test_log_burn_times > 5) { + if (!burn_header_log_to_flash_3x(FALSE, TRUE)) + DEBUGOUT("[burn_header_log_to_flash_3x ERROR!]\r\n"); + } + } + } +#endif + if (g_st_test_info.u16_ft_test_item == 0) + return; +/* + * if (g_st_test_info.u8_device_id != 2) { + * g_u32_wearable_test_result = WEARABLE_FT_TEST_RESULT_IC_VERSION_NG; + * return; + * } + */ + + /*handle_display_write(g_u8_display_pattern_sleep_to_active, sizeof(g_u8_display_pattern_sleep_to_active));*/ + handle_ic_read(0x50001100, 4, (unsigned char *)(&u32_read_data), g_u8_drv_interface, I2C_WORD_MODE); + DEBUGOUT("display mode 0x%x\r\n", u32_read_data); + + g_u8_ic_test_state = IC_TEST_INIT; + + u16_test_items = g_st_test_info.u16_ft_test_item; + + g_u32_wearable_test_result &= ~(WEARABLE_FT_TEST_RESULT_POWER_ON_NG | WEARABLE_FT_TEST_RESULT_IC_VERSION_NG + | WEARABLE_FT_TEST_RESULT_MCU_HOLD_NG | WEARABLE_FT_TEST_RESULT_EXT_FLASH_EMPTY_NG + | WEARABLE_FT_TEST_RESULT_FLASH_ID_NG | WEARABLE_FT_TEST_RESULT_NORMAL_FW_VER_NG + | WEARABLE_FT_TEST_RESULT_PANEL_TEST_3_NG | WEARABLE_FT_TEST_RESULT_CB_NG | WEARABLE_FT_TEST_RESULT_TEST_FW_VER_NG + + | WEARABLE_FT_TEST_RESULT_AUO_JIG_NG); + + + while (u16_test_items) { +#if ENABLE_TEST_TIME_MEASURMENT + u8_ic_test_state = g_u8_ic_test_state; + g_u32_spend_time = get_system_time(); +#endif + + switch (g_u8_ic_test_state) { + case IC_TEST_INIT: +#if ENABLE_TEST_TIME_MEASURMENT + u32_fun_time = get_system_time(); +#endif + g_u8_ic_test_state = IC_TEST_SYSTEM; +#if ENABLE_TEST_TIME_MEASURMENT + DEBUGOUT("IC_TEST_INIT Spend Time= %d\r\n", get_system_time() - u32_fun_time); +#endif + + break; + case IC_TEST_SYSTEM: + if (system_test_3x() == SUCCESS || + (g_st_test_info.u16_ft_eng_item & IC_TEST_ENG_ITEMS_TEST_ALL)) { + + g_u8_ic_test_state = IC_TEST_BURN_FW; + } else { + g_u8_ic_test_state = IC_TEST_EXIT; + } + break; + case IC_TEST_BURN_FW: + if (burn_fw_3x() == ERROR) { + g_u32_wearable_test_result = WEARABLE_FT_TEST_RESULT_BURN_FW_NG; + g_u8_ic_test_state = IC_TEST_EXIT; + } else { + g_u8_ic_test_state = IC_TEST_LOAD_TEST_FW; + } + break; + case IC_TEST_LOAD_TEST_FW: + if (load_test_fw_3x() == ERROR) { + DEBUGOUT("Load Test FW NG!!\r\n"); + g_u32_wearable_test_result = WEARABLE_FT_TEST_RESULT_LOAD_TESTFW_NG; + g_u8_ic_test_state = IC_TEST_EXIT; + } else { + g_u8_ic_test_state = IC_TEST_ENTER_FW_TEST_MODE; + } + break; + case IC_TEST_ENTER_FW_TEST_MODE: + if (enter_fw_test_mode_3x() == ERROR) { + DEBUGOUT("Enter FW Test Mode NG!!\r\n"); + g_u8_ic_test_state = IC_TEST_EXIT; + } else { + g_u8_ic_test_state = IC_TEST_OPEN_SHORT; + } + break; + case IC_TEST_OPEN_SHORT: + if (ft_test_do_fw_test_3x(u16_test_items) == SUCCESS) { + DEBUGOUT("FW Test Finish! \r\n"); + g_u8_ic_test_state = IC_TEST_READ_TEST_FW_DATA; + } else { + DEBUGOUT("FW Test NG!!\r\n"); + g_u32_wearable_test_result = WEARABLE_FT_TEST_RESULT_TEST_INIT_NG; + g_u8_ic_test_state = IC_TEST_EXIT; + } + break; + case IC_TEST_READ_TEST_FW_DATA: + if (read_test_fw_data_3x(u16_test_items) == SUCCESS) { + g_u8_ic_test_state = IC_TEST_BURN_CC; + } else { + DEBUGOUT("Read Test FW Data NG!!\r\n"); + g_u8_ic_test_state = IC_TEST_EXIT; + } + break; + case IC_TEST_BURN_CC: + if (burn_cc_3x(u16_test_items) == ERROR) { + DEBUGOUT("Burn CC NG!!\r\n"); + g_u32_wearable_test_result |= WEARABLE_FT_TEST_RESULT_BURN_CC_NG; + g_u8_ic_test_state = IC_TEST_PANEL_PATTERN_TEST; + } else { + g_u8_ic_test_state = IC_TEST_PANEL_PATTERN_TEST; + } + break; + case IC_TEST_PANEL_PATTERN_TEST: +#if ENABLE_TEST_GPIO_MEASURMENT + Chip_GPIO_SetPinOutHigh(LPC_GPIO, DONGLE_GPIO_PORT_1, DONGLE_GPIO_PIN_10); + delay_ms(1); + Chip_GPIO_SetPinOutLow(LPC_GPIO, DONGLE_GPIO_PORT_1, DONGLE_GPIO_PIN_10); +#endif + DEBUGOUT("B g_u32_wearable_test_result = 0x%08x\r\n", g_u32_wearable_test_result); + +#if !SELFTEST_3X + do_ic_panel_test_3x(); +#endif + g_u8_ic_test_state = IC_TEST_EXIT; + break; + case IC_TEST_EXIT: +#if ENABLE_TEST_TIME_MEASURMENT + u32_fun_time = get_system_time(); + DEBUGOUT("Final action Start Time= %d\r\n", u32_fun_time); +#endif + DEBUGOUT("g_u32_wearable_test_result = 0x%08x\r\n", g_u32_wearable_test_result); + DEBUGOUT("Exit IC Test!\r\n"); +#if !SELFTEST_3X + gpio_touch_int_trigger_control(FALSE); +#endif + + g_u8_ic_test_state = IC_TEST_INIT; + if ((g_st_test_info.u16_ft_test_item & (IC_TEST_ITEMS_OPEN | IC_TEST_ITEMS_SHORT)) == 0) { + ft_raw_data_checksum_cal_3x((unsigned short *)g_i16_raw_data_1_short_buf); + ft_raw_data_checksum_cal_3x((unsigned short *)g_i16_raw_data_2_open_buf); + ft_raw_data_checksum_cal_3x(g_u16_raw_data3_cc_buf); + } + ft_raw_data_checksum_cal_3x(g_u16_uc_buf); + + ft_raw_data_checksum_cal_3x((unsigned short *)g_u16_raw_data3_golden_cc_buf); + ft_raw_data_checksum_cal_3x((unsigned short *)g_u16_uc_golden_cc_buf); + ft_test_result_checksum_cal_3x(g_u8_test_result); +#if !SELFTEST_3X + DEBUGOUT("g_u8_panel_jig_test_status= %d\r\n", g_u8_panel_jig_test_status); + if (g_u8_panel_jig_test_status == STATUS_DATA_PANEL_JIG_FINISH && (g_st_test_info.u16_ft_eng_item & IC_TEST_ENG_ITEMS_ENABLE_BURN_TEST_LOG)) { + b_is_auo_jig_testing = false; + burn_header_log_to_flash_3x(TRUE, TRUE); + memset(g_u32_save_config, 0, sizeof(g_u32_save_config)); + if (!burn_data_log_to_flash_3x()) + DEBUGOUT("[burn_log_to_flash ERROR!]\r\n"); + } +#endif +/* + * if (g_u8_panel_jig_test_status == STATUS_DATA_PANEL_JIG_FINISH && (g_st_test_info.u16_ft_eng_item & IC_TEST_ENG_ITEMS_ENABLE_BURN_TEST_LOG)) { + * if (!burn_log_to_flash_3x()) + * DEBUGOUT("[burn_log_to_flash ERROR!]\r\n"); + * } + */ + +#if ENABLE_CONTROL_OPENSHORT_WDT + /*Chip_WWDT_SetTimeOut(LPC_WWDT, (WDT_OSC / 10) * 50);*/ +#endif + u16_test_items = 0; +#if ENABLE_TEST_TIME_MEASURMENT + DEBUGOUT("Final action Spend Time= %d\r\n", get_system_time() - u32_fun_time); +#endif + break; + } + +#if !SELFTEST_3X + update_dongle_test_status(g_u8_ic_test_state); +#endif + +#if ENABLE_WDT + /* reload the Watchdog timer*/ + Chip_WWDT_Feed(LPC_WWDT); +#endif + +#if !SELFTEST_3X + if (g_u8_ic_power_on_ng) { + g_u32_wearable_test_result |= WEARABLE_FT_TEST_RESULT_POWER_ON_NG; + g_u8_ic_test_state = IC_TEST_EXIT; + } +#endif + +#if ENABLE_TEST_TIME_MEASURMENT + DEBUGOUT("Test Item [%d] End (%d)\r\n", u8_ic_test_state, get_system_time() - g_u32_spend_time); +#endif + } /*while(u16_test_items)*/ +} + + diff --git a/qcom/opensource/touch-drivers/raydium/chip_raydium/f303_ic_test.h b/qcom/opensource/touch-drivers/raydium/chip_raydium/f303_ic_test.h new file mode 100644 index 0000000000..5ce4db850d --- /dev/null +++ b/qcom/opensource/touch-drivers/raydium/chip_raydium/f303_ic_test.h @@ -0,0 +1,65 @@ +/* f303_ic_test.h + * + * Raydium TouchScreen driver. + * + * Copyright (c) 2021 Raydium tech Ltd. + * + * 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. + * + */ + +#include "ic_drv_global.h" + +/**************************************************************************** + * Defined Const Value + *****************************************************************************/ +#define FT_CMD_DO_FT_TEST 0x14 + +extern STATUS turn_on_flash_3x(void); +extern STATUS read_fpc_flash_3x(unsigned int u32_addr, unsigned int *p_u32_data); +extern STATUS set_test_info_thd_para_3x(void); +extern STATUS check_test_fw_status_3x(unsigned char u8_target_status, unsigned char *p_u8_result); +extern STATUS ft_test_do_fw_test_3x(unsigned short u16_test_items); +extern STATUS enter_fw_test_mode_3x(void); +extern STATUS system_test_3x(void); +extern STATUS ft_test_ctrl_mbist_fun_3x(unsigned char u8_enable); +extern STATUS ft_test_ram_test_3x(unsigned char u8_is_stop_mcu); +extern STATUS ft_test_connect_test_3x(void); +extern STATUS ft_test_reset_pin_test_3x(void); +extern STATUS ft_raw_data_checksum_check_3x(unsigned short *u16_buffer); +extern STATUS ft_test_result_checksum_check_3x(unsigned char *u8_buffer); +extern STATUS burn_cc_to_ic_flash_3x(void); +extern STATUS check_cc_bl_flag_3x(void); +extern STATUS read_test_fw_data_3x(unsigned short u16_test_items); +extern STATUS load_test_fw_3x(void); + +extern void dump_image_data_3x(short *p_image_buf, unsigned char u8_remap); +extern void dump_image_hex_data_3x(short *p_image_buf); +extern STATUS ft_test_read_used_pin_infor_3x(unsigned char *p_u8_infor); +extern void ft_raw_data_checksum_cal_3x(unsigned short *u16_buffer); +extern void ft_test_result_checksum_cal_3x(unsigned char *u8_buffer); +extern STATUS baseline_update_control_3x(bool b_enable_baseline_update); +extern STATUS enter_normal_fw_3x(void); +extern STATUS do_calibration_3x(unsigned char u8_do_calibration_cmd, unsigned char u8_burn_flash); +extern STATUS hw_int_pin_Test_3x(void); +extern void test_item_message_3x(void); +extern STATUS burn_cc_3x(unsigned short u16_test_items); +extern void do_ic_test_3x(void); +extern STATUS burn_data_log_to_flash_3x(void); +extern STATUS burn_header_log_to_flash_3x(bool is_test_finish, bool is_in_header_data_page); + +//-----------------------------extern FT function ------------------------------------------ +extern STATUS load_test_fw_ft_3x(void); +extern void do_ic_panel_test_3x(void); +extern STATUS burn_fw_3x(void); +extern STATUS burn_to_ic_flash_3x(unsigned char u8_type); +extern unsigned char notify_panel_jig_start_test_3x(unsigned char u8_cmd); + diff --git a/qcom/opensource/touch-drivers/raydium/chip_raydium/ic_drv_global.c b/qcom/opensource/touch-drivers/raydium/chip_raydium/ic_drv_global.c new file mode 100644 index 0000000000..13816efa0b --- /dev/null +++ b/qcom/opensource/touch-drivers/raydium/chip_raydium/ic_drv_global.c @@ -0,0 +1,127 @@ +/* i2c_drv_global.c + * + * Raydium TouchScreen driver. + * + * Copyright (c) 2021 Raydium tech Ltd. + * + * 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. + * + */ + +#include "../Config.h" +#include "ic_drv_global.h" +#include "ic_drv_interface.h" +#ifdef __KERNEL__ +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#include "../drv_interface.h" + + + +#if !SELFTEST +#include "usb.h" +#include "Descriptors.h" +#endif +/* + ***************************************************************************** + ** + ** Declared Global Variable + ** + ***************************************************************************** + ********************* Global ********************* + */ + + +unsigned short g_u16_dev_id; + +unsigned char g_u8_drv_interface; + +volatile unsigned char g_u8_gpio_irq_trigger; + +unsigned char g_u8_raw_data_buffer[MAX_IMAGE_BUFFER_SIZE * 2]; +unsigned short g_u16_raw_data_tmp[MAX_IMAGE_BUFFER_SIZE]; + +// IC Test +short g_i16_raw_data_1_short_buf[MAX_SENSING_PIN_NUM]; +short g_i16_raw_data_2_open_buf[MAX_SENSING_PIN_NUM]; +unsigned short g_u16_raw_data3_cc_buf[MAX_SENSING_PIN_NUM]; +unsigned short g_u16_uc_buf[MAX_SENSING_PIN_NUM];//CC +unsigned short g_u16_raw_data3_golden_cc_buf[MAX_SENSING_PIN_NUM]; +unsigned short g_u16_uc_golden_cc_buf[MAX_SENSING_PIN_NUM]; + +unsigned int g_u32_test_result[MAX_SENSING_PIN_NUM]; // each node test result (bit[0]:open ng, bit[1]:short ng, bit[2]:uniformity ng..etc) +unsigned char g_u8_test_result[MAX_SENSING_PIN_NUM]; + + +unsigned int g_u32_wearable_test_result; +unsigned char g_u8_wearable_pin_map[MAX_IMAGE_BUFFER_SIZE]; + + +unsigned char g_u8_data_buf[DATA_BUFFER_SIZE]; +unsigned char g_u8_mipi_read_buf[56]; + +unsigned char g_u8_channel_x; +unsigned char g_u8_channel_y; + +unsigned char g_u8_is_normal_fw; + +volatile unsigned short g_u16_test_items_host_cmd; +unsigned short g_u16_test_items_tool_cmd; +unsigned char g_u8_ic_power_on_ng; +char g_i8_test_baseline_msg[30]; +volatile unsigned short g_u16_panel_jig_set_test_items; +bool b_is_auo_jig_testing; + +#if ENABLE_TEST_TIME_MEASURMENT || ENABLE_TEST_TIME_MEASURMENT_CC +unsigned int g_u32_spend_time; +#endif + +unsigned int g_u32_fw_cc_version; +unsigned char g_u8_print_debug_msg; +unsigned char g_u8_display_interface; +unsigned char g_u8_PAGE_ADDR; + +int g_u32_dongle_flash_ini_addr;// 0xF000 +int g_u32_ini_threshold_addr;// DONGLE_FLASH_INI_ADDR + 16 +int g_u32_ini_para_addr;// INI_THRESHOLD_ADDR + 36 +//int u32_ini_raw_data_2_bl_addr;// INI_PARA_ADDR+48 +int g_u32_ini_raw_data_3_cc_addr;// INI_RAW_DATA2_BL_ADDR+72 +int g_u32_ini_uc_cc_addr;// INI_RAW_DATA_3_CC_ADDR + 72// INI_RAW_DATA2_ADDR + +int g_u32_initial_code_start_addr; + +void ic_drv_init(void) +{ + g_u16_test_items_host_cmd = 0xFFFF; + g_u16_test_items_tool_cmd = 0; + g_u16_dev_id = DEVICE_ID_3X; + g_u8_print_debug_msg = 0; + g_u8_display_interface = DISPLAY_TOUCH_2_DRIVER_MODE; + b_is_auo_jig_testing = false; +#if !SELFTEST + g_u8_ic_power_on_ng = FALSE; +#endif +} + +/* + ***************************************************************************** + ** + * End Of File + ****************************************************************************** + */ diff --git a/qcom/opensource/touch-drivers/raydium/chip_raydium/ic_drv_global.h b/qcom/opensource/touch-drivers/raydium/chip_raydium/ic_drv_global.h new file mode 100644 index 0000000000..3de8078751 --- /dev/null +++ b/qcom/opensource/touch-drivers/raydium/chip_raydium/ic_drv_global.h @@ -0,0 +1,190 @@ +/* i2c_drv_global.h + * + * Raydium TouchScreen driver. + * + * Copyright (c) 2021 Raydium tech Ltd. + * + * 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. + * + */ + +#ifndef _DRVGLOBAL_H_ +#define _DRVGLOBAL_H_ +#include "../Config.h" +#ifdef __KERNEL__ +#include +#else +#include +#endif + +#if (SELFTEST_2X) +#include "f302_ic_control.h" +#endif + +#if !SELFTEST +#include "f302_ic_control.h" +#include "f303_ic_control.h" +#include "usb.h" +#endif +//********* Basic Parameter Definition ***********// +/***************************************************************************** +** +** Declared Global Variable +** +*****************************************************************************/ + + +#define MAX_IMAGE_BUFFER_SIZE 64 +#define MAX_SENSING_PIN_NUM 50 // (wearable) +#define F302_MAX_IMAGE_BUFFER_SIZE 36 +#define DATA_BUFFER_SIZE (0x100) + +#define F302_DONGLE_FLASH_INI_ADDR 0xF000 +#define F302_INI_THRESHOLD_ADDR (F302_DONGLE_FLASH_INI_ADDR + 16) +#define F302_INI_PARA_ADDR (F302_INI_THRESHOLD_ADDR + 36) +#define F302_INI_RAW_DATA2_BL_ADDR (F302_INI_PARA_ADDR + 48) +#define F302_INI_RAW_DATA_3_CC_ADDR (F302_INI_RAW_DATA2_BL_ADDR + 72) +#define F302_INI_UC_CC_ADDR (F302_INI_RAW_DATA_3_CC_ADDR + 72) // INI_RAW_DATA2_ADDR +#define F302_NA_P 36 + +#define F303_DONGLE_FLASH_INI_ADDR 0x10000 +#define F303_INI_THRESHOLD_ADDR (F303_DONGLE_FLASH_INI_ADDR + 16) +#define F303_INI_PARA_ADDR (F303_INI_THRESHOLD_ADDR + 36) +#define F303_INI_RAW_DATA2_BL_ADDR (F303_INI_PARA_ADDR + 48) +#define F303_INI_RAW_DATA_3_CC_ADDR (F303_INI_RAW_DATA2_BL_ADDR + 128) +#define F303_INI_UC_CC_ADDR (F303_INI_RAW_DATA_3_CC_ADDR + 128) // INI_RAW_DATA2_ADDR +#define F303_MAX_SENSING_PIN_NUM 48 +#define F303_NA_P 65 + +#define F302_INITIAL_CODE_START_ADDR 0xF400 +#define F303_INITIAL_CODE_START_ADDR 0x10400 + +#define PRINT_DEBUG_MSG_TYPE_1 0x01 +#define PRINT_DEBUG_MSG_TYPE_2 0x02 +#define PRINT_DEBUG_MSG_TYPE_3 0x04 +#define PRINT_DEBUG_MSG_TYPE_4 0x08 +#define PRINT_DEBUG_MSG_TYPE_5 0x10 +#define PRINT_DEBUG_MSG_TYPE_6 0x20 +#define PRINT_DEBUG_MSG_TYPE_7 0x40 +#define PRINT_DEBUG_MSG_TYPE_8 0x80 + +#define FW_SYS_CMD_ADDR 0x20000288 + +#define SYS_CMD_TP_MANUAL_MODE 0x34 +#define SYS_CMD_DO_BL_CAL 0x5A +#define SYS_CMD_DO_CC_CAL 0x5C + +#define FW_TP_SEQ_NUM_ADDR 0x20000290 +#define FW_TP_POINT_DATA_ADDR 0x20000294 +#define REG_SYSCON_MISCIER_ADDR 0x40000014 +#define FW_FT_IMG_ADDR 0x2000019C +#define RM_DATAMEM0_BASE 0x20000000 + +#define F303_DRAM_FT_DBG_START 0x2000004C + +//======================= Basic Hardware Define ============================== + + +//==================== End of Basic Hardware Define ========================== + +#if SELFTEST + +#ifdef __KERNEL__ +typedef enum { + error = -1, + success = 1 +} STATUS; + +#else +typedef enum { + ERROR = 0, + SUCCESS = 1 +} STATUS; +typedef enum { + DISABLE = 0, + ENABLE = 1 +} FunctionalState; + +typedef enum { + false = 0, + true = 1 +} bool; +#endif +#endif + +//========== Basic Parameter Information ========== +//********************* Global *********************// + +// Global variable for dongle +extern unsigned char g_u8_drv_interface; + +extern volatile unsigned char g_u8_gpio_irq_trigger; +extern unsigned short g_u16_dev_id; + + +extern unsigned char g_u8_raw_data_buffer[MAX_IMAGE_BUFFER_SIZE * 2]; +extern unsigned short g_u16_raw_data_tmp[MAX_IMAGE_BUFFER_SIZE]; + +extern short g_i16_raw_data_1_short_buf[MAX_SENSING_PIN_NUM]; +extern short g_i16_raw_data_2_open_buf[MAX_SENSING_PIN_NUM]; +extern unsigned short g_u16_raw_data3_cc_buf[MAX_SENSING_PIN_NUM]; +extern unsigned short g_u16_uc_buf[MAX_SENSING_PIN_NUM]; + + +extern unsigned short g_u16_raw_data3_golden_cc_buf[MAX_SENSING_PIN_NUM]; +extern unsigned short g_u16_uc_golden_cc_buf[MAX_SENSING_PIN_NUM]; +extern unsigned int g_u32_test_result[MAX_SENSING_PIN_NUM]; // each node test result (open ng, short ng, uniformity ng..etc) +extern unsigned char g_u8_wearable_pin_map[MAX_IMAGE_BUFFER_SIZE]; +extern unsigned char g_u8_test_result[MAX_SENSING_PIN_NUM]; + +extern volatile unsigned short g_u16_test_items_host_cmd; +extern unsigned short g_u16_test_items_tool_cmd; +extern volatile unsigned short g_u16_panel_jig_set_test_items; + +extern unsigned int g_u32_wearable_test_result; +extern unsigned char g_u8_channel_x; +extern unsigned char g_u8_channel_y; +extern unsigned char g_u8_is_normal_fw; +extern unsigned char g_u8_data_buf[DATA_BUFFER_SIZE]; +extern unsigned char g_u8_mipi_read_buf[56]; + +extern unsigned int g_u32_fw_cc_version; +extern unsigned char g_u8_print_debug_msg; + +extern char g_i8_test_baseline_msg[30]; +extern bool b_is_auo_jig_testing; +#if ENABLE_TEST_TIME_MEASURMENT || ENABLE_TEST_TIME_MEASURMENT_CC +extern unsigned int g_u32_spend_time; +#endif + + +#if !SELFTEST +extern unsigned char g_u8_ic_power_on_ng; +#endif + +extern unsigned char g_u8_display_interface; +extern unsigned char g_u8_PAGE_ADDR; + +extern int g_u32_dongle_flash_ini_addr;// 0xF000 +extern int g_u32_ini_threshold_addr;// DONGLE_FLASH_INI_ADDR + 16 +extern int g_u32_ini_para_addr;// INI_THRESHOLD_ADDR + 36 +//int u32_ini_raw_data_2_bl_addr;// INI_PARA_ADDR+48 +extern int g_u32_ini_raw_data_3_cc_addr;// INI_RAW_DATA2_BL_ADDR+72 +extern int g_u32_ini_uc_cc_addr;// INI_RAW_DATA_3_CC_ADDR + 72// INI_RAW_DATA2_ADDR + +extern int g_u32_initial_code_start_addr; +extern void ic_drv_init(void); + +#endif /* end _DRVGLOBAL_H_*/ + +/****************************************************************************** +** End Of File +******************************************************************************/ diff --git a/qcom/opensource/touch-drivers/raydium/chip_raydium/ic_drv_interface.c b/qcom/opensource/touch-drivers/raydium/chip_raydium/ic_drv_interface.c new file mode 100644 index 0000000000..1d7621f5ab --- /dev/null +++ b/qcom/opensource/touch-drivers/raydium/chip_raydium/ic_drv_interface.c @@ -0,0 +1,229 @@ +/* i2c_drv_interface.c + * + * Raydium TouchScreen driver. + * + * Copyright (c) 2021 Raydium tech Ltd. + * + * 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. + * + */ +#ifdef __KERNEL__ +#include +#include +#include +#include +#include +#include +#include +#include +#else +#include +#include +#endif +#include "ic_drv_interface.h" +#include "../drv_interface.h" +#include "ic_drv_global.h" +#if SELFTEST_2X +#include "f302_ic_control.h" +#include "f302_ic_test.h" +#endif +#if SELFTEST_3X +#include "f303_ic_control.h" +#include "f303_ic_test.h" +#endif +#if !SELFTEST +#include "f302_ic_test.h" +#include "f302_ic_test_ft.h" +#include "f303_ic_test.h" +#include "f303_ic_test_ft.h" +#include "ic_drv_global_ft.h" +#include "main.h" +#endif + +st_test_threshold g_st_test_thd; +st_test_info g_st_test_info; +st_test_para_resv g_st_test_para_resv; +unsigned char g_u8_ic_test_state; + +STATUS wearable_ic_test_read_info(void) +{ + if (read_flash_data(g_u32_dongle_flash_ini_addr, 16) != ERROR) { + memcpy(&g_st_test_info, g_u8_data_buf, sizeof(g_st_test_info)); + } else { + DEBUGOUT("Read Flash Data ERROR\r\n"); + return ERROR; + } + + return SUCCESS; +} + +STATUS wearable_ic_test_info_init(void) +{ +#if SELFTEST + g_u16_test_items_host_cmd = IC_TEST_ITEMS_SYSTEM | IC_TEST_ITEMS_OPEN | IC_TEST_ITEMS_SHORT | IC_TEST_ITEMS_UC | IC_TEST_ITEMS_UB | IC_TEST_ITEMS_BURN_CC; +#endif + + if (!wearable_ic_test_read_info()) { + DEBUGOUT("Read Info ERROR\r\n"); + return ERROR; + } + + if (g_u16_test_items_host_cmd != 0) + g_st_test_info.u16_ft_test_item &= g_u16_test_items_host_cmd; + g_u16_panel_jig_set_test_items |= g_st_test_info.u16_ft_test_item; + + DEBUGOUT("TestItem:0x%x:0x%x\r\n", g_st_test_info.u16_ft_test_item, g_st_test_info.u16_ft_eng_item); + DEBUGOUT("g_u16_panel_jig_set_test_items:0x%x\r\n", g_u16_panel_jig_set_test_items); + if (read_flash_data(g_u32_ini_threshold_addr, 36)) { + memcpy(&g_st_test_thd, g_u8_data_buf, sizeof(g_st_test_thd)); + } else { + DEBUGOUT("Read THD Data ERROR\r\n"); + return ERROR; + } + + DEBUGOUT("THD:\r\n%d,%d,%d,%d,\r\n%d,%d,%d,%d,\r\n%d,%d,%d,%d\r\n", + g_st_test_thd.i16_ft_test_open_lower_thd, + g_st_test_thd.i16_ft_test_short_upper_thd, + g_st_test_thd.i16_ft_test_short_lower_thd, + g_st_test_thd.i16_ft_test_single_cc_upper_thd, + g_st_test_thd.i16_ft_test_single_cc_lower_thd, + g_st_test_thd.i16_ft_test_uniformity_bl_upper_thd, + g_st_test_thd.i16_ft_test_uniformity_bl_lower_thd, + g_st_test_thd.i16_ft_test_uniformity_cc_upper_thd, + g_st_test_thd.i16_ft_test_uniformity_cc_lower_thd, + g_st_test_thd.i16_ft_test_panel_test_1_thd, + g_st_test_thd.i16_ft_test_panel_test_3_thd, + g_st_test_thd.i16_ft_test_panel_test_2_thd); + + if (read_flash_data(g_u32_ini_para_addr, 48)) { + memcpy(&g_st_test_para_resv, g_u8_data_buf, sizeof(g_st_test_para_resv)); + } else { + DEBUGOUT("Read INI Para ERROR\r\n"); + return ERROR; + } +/* + * DEBUGOUT(" g_st_test_para_resv.u32_normal_fw_version = %X ,g_st_test_para_resv.u32_test_fw_version= %X \r\n", + * g_st_test_para_resv.u32_normal_fw_version, + * g_st_test_para_resv.u32_test_fw_version + * ); + */ + + if (g_u16_dev_id == DEVICE_ID_3X) { + if (read_flash_data(g_u32_ini_raw_data_3_cc_addr, 128)) { + memcpy(g_u16_raw_data3_golden_cc_buf, g_u8_data_buf, sizeof(g_u16_raw_data3_golden_cc_buf)); + } else { + DEBUGOUT("read raw data 3 cc ERROR\r\n"); + return ERROR; + } +/* + * DEBUGOUT(" g_u16_raw_data3_golden_cc_buf[0] = %d,g_u16_raw_data3_golden_cc_buf[1 =%d g_u16_raw_data3_golden_cc_buf[2]=%d,\r\n", + * g_u16_raw_data3_golden_cc_buf[0], + * g_u16_raw_data3_golden_cc_buf[1], + * g_u16_raw_data3_golden_cc_buf[2] + * ); + */ + if (read_flash_data(g_u32_ini_uc_cc_addr, 128)) { + memcpy(g_u16_uc_golden_cc_buf, g_u8_data_buf, sizeof(g_u16_uc_golden_cc_buf)); + } else { + DEBUGOUT("read uc cc ERROR\r\n"); + return ERROR; + } + } else if (g_u16_dev_id == DEVICE_ID_2X) { + if (read_flash_data(g_u32_ini_raw_data_3_cc_addr, 72)) { + memcpy(g_u16_raw_data3_golden_cc_buf, g_u8_data_buf, sizeof(g_u16_raw_data3_golden_cc_buf)); + } else { + DEBUGOUT("read raw data 3 cc ERROR\r\n"); + return ERROR; + } +/* + * DEBUGOUT(" g_u16_raw_data3_golden_cc_buf[0] = %d,g_u16_raw_data3_golden_cc_buf[1 =%d g_u16_raw_data3_golden_cc_buf[2]=%d,\r\n", + * g_u16_raw_data3_golden_cc_buf[0], + * g_u16_raw_data3_golden_cc_buf[1], + * g_u16_raw_data3_golden_cc_buf[2] + * ); + */ + if (read_flash_data(g_u32_ini_uc_cc_addr, 72)) { + memcpy(g_u16_uc_golden_cc_buf, g_u8_data_buf, sizeof(g_u16_uc_golden_cc_buf)); + } else { + DEBUGOUT("read uc cc ERROR\r\n"); + return ERROR; + } + } +/* if (read_flash_data(INI_RAW_DATA2_BL_ADDR, 72)) + * memcpy(g_i16_raw_data2_golden_bl_buf, g_u8_data_buf, sizeof(g_i16_raw_data2_golden_bl_buf)); + */ + return SUCCESS; +} + +STATUS wearable_ic_test_init(void) +{ + if (!wearable_ic_test_info_init()) + return ERROR; + if (!(g_st_test_info.u16_ft_eng_item & IC_TEST_ENG_ITEMS_PANEL_TEST_JIG)) + wearable_ic_test_init_buffer(); + + return SUCCESS; +} + +void wearable_ic_test_init_buffer(void) +{ + g_u32_wearable_test_result = WEARABLE_FT_TEST_RESULT_IC_INIT_STATE; + memset(g_u32_test_result, 0, sizeof(g_u32_test_result)); + memset(g_u8_test_result, 0, sizeof(g_u8_test_result)); + g_u8_is_normal_fw = FALSE; +} + +STATUS handle_burn_log_to_flash(void) +{ + STATUS u8_status = ERROR; + + switch (g_u16_dev_id) { +#if (!SELFTEST_2X && (!SELFTEST)) + case DEVICE_ID_2X: + burn_data_log_to_flash_2x(); + break; +#endif +#if (!SELFTEST_3X && (!SELFTEST)) + case DEVICE_ID_3X: + b_is_auo_jig_testing = false; + burn_header_log_to_flash_3x(TRUE, TRUE); + memset(g_u32_save_config, 0, sizeof(g_u32_save_config)); + if (burn_data_log_to_flash_3x()) + u8_status = SUCCESS; + break; +#endif + } + return u8_status; +} + +void handle_ic_test(void) +{ + + switch (g_u16_dev_id) { +#if (SELFTEST_2X | (!SELFTEST)) + case DEVICE_ID_2X: + do_ic_test_2x(); + break; +#endif +#if (SELFTEST_3X | (!SELFTEST)) + case DEVICE_ID_3X: + do_ic_test_3x(); + break; +#endif + } +} + +void handle_set_display_interface(unsigned char u8_interface) +{ + g_u8_display_interface = u8_interface; + DEBUGOUT("%s: 0x%x \r\n", __func__, u8_interface); +} + diff --git a/qcom/opensource/touch-drivers/raydium/chip_raydium/ic_drv_interface.h b/qcom/opensource/touch-drivers/raydium/chip_raydium/ic_drv_interface.h new file mode 100644 index 0000000000..104ac162f7 --- /dev/null +++ b/qcom/opensource/touch-drivers/raydium/chip_raydium/ic_drv_interface.h @@ -0,0 +1,211 @@ +/* i2c_drv_interface.h + * + * Raydium TouchScreen driver. + * + * Copyright (c) 2021 Raydium tech Ltd. + * + * 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. + * + */ + +#include "ic_drv_global.h" + +#define DEVICE_ID_2X 0xF302 +#define DEVICE_ID_3X 0xF303 + +#define I2C_PDA2_BYTE_MODE 0x03 +#define I2C_PDA2_WORD_MODE 0x43 +#define I2C_BYTE_MODE (0x80|0x20) +#define I2C_WORD_MODE (0xc0|0x20) +#define I2C_MCU_MODE 0x40 +#define I2C_PDA2_MODE 0x10 +#define I2C_PDA_MODE 0x08 + +#define SPI_BYTE_MODE (0x80|0x03) +#define SPI_WORD_MODE (0xc0|0x03) +#define SPI_MCU_MODE 0x40 + +#define I2C_INTERFACE 0 +#define SPI_INTERFACE 1 + +#define DISPLAY_SPI_MODE 0 +#define DISPLAY_MIPI_MODE 1 +#define DISPLAY_TOUCH_2_DRIVER_MODE 2 + +#define DATA_REMAP_TO_IC_PIN 1 +#define DATA_REMAP_TO_SENSOR_PAD 2 + +#define IC_TEST_RETRY_TIME 3 +#define IC_TEST_TIME_OUT (-1) +#define FT_TEST_STUATUS_PATTERN 0x5A00 + +#define FT_CMD_INIT 0x11 +#define FT_CMD_DO_FT_TEST 0x14 + +#define IC_TEST_ITEMS_SYSTEM 0x0001 +#define IC_TEST_ITEMS_BURN_FW 0x0002 +#define IC_TEST_ITEMS_OPEN 0x0004 +#define IC_TEST_ITEMS_SHORT 0x0008 +#define IC_TEST_ITEMS_UC 0x0010 +#define IC_TEST_ITEMS_UB 0x0020 +#define IC_TEST_ITEMS_BURN_CC 0x0040 +#define IC_TEST_ITEMS_PANEL_TEST_1 0x0080 +#define IC_TEST_ITEMS_PANEL_TEST_2 0x0100 +#define IC_TEST_ITEMS_PANEL_TEST_3 0x0200 +#define IC_TEST_ITEMS_RESV_0 0x0400 +#define IC_TEST_ITEMS_RESV_1 0x0800 +#define IC_TEST_ITEMS_RESV_2 0x1000 +#define IC_TEST_ITEMS_RESV_3 0x2000 +#define IC_TEST_ITEMS_RESV_4 0x4000 +#define IC_TEST_ITEMS_RESV_5 0x8000 + +#define IC_TEST_ENG_ITEMS_RESV_1 0x0001 +#define IC_TEST_ENG_ITEMS_RESV_2 0x0002 +#define IC_TEST_ENG_ITEMS_RESV_3 0x0004 +#define IC_TEST_ENG_ITEMS_TEST_ALL 0x0008 +#define IC_TEST_ENG_ITEMS_RESV_5 0x0010 +#define IC_TEST_ENG_ITEMS_RESV_6 0x0020 +#define IC_TEST_ENG_ITEMS_RESV_7 0x0040 +#define IC_TEST_ENG_ITEMS_RESV_8 0x0080 +#define IC_TEST_ENG_ITEMS_PANEL_TEST_JIG 0x0100 +#define IC_TEST_ENG_ITEMS_RESV_10 0x0200 +#define IC_TEST_ENG_ITEMS_RESV_11 0x0400 +#define IC_TEST_ENG_ITEMS_RESV_12 0x0800 +#define IC_TEST_ENG_ITEMS_RESV_13 0x1000 +#define IC_TEST_ENG_ITEMS_RESV_14 0x2000 +#define IC_TEST_ENG_ITEMS_RESV_15 0x4000 +#define IC_TEST_ENG_ITEMS_RESV_16 0x8000 + +#define WEARABLE_FT_TEST_RESULT_IC_INIT_STATE 0xDFFFE5DF +#define WEARABLE_FT_TEST_RESULT_PANEL_INIT_STATE 0x20001A20 +#define WEARABLE_FT_TEST_RESULT_PASS 0x00000000 +#define WEARABLE_FT_TEST_RESULT_OPEN_NG 0x00000001 +#define WEARABLE_FT_TEST_RESULT_SHORT_NG 0x00000002 +#define WEARABLE_FT_TEST_RESULT_UB_NG 0x00000004 +#define WEARABLE_FT_TEST_RESULT_I2C_NG 0x00000008 +#define WEARABLE_FT_TEST_RESULT_INT_NG 0x00000010 +#define WEARABLE_FT_TEST_RESULT_PANEL_TEST_1_NG 0x00000020 +#define WEARABLE_FT_TEST_RESULT_RESET_NG 0x00000040 +#define WEARABLE_FT_TEST_RESULT_CB_NG 0x00000080 +#define WEARABLE_FT_TEST_RESULT_PRAM_NG 0x00000100 +#define WEARABLE_FT_TEST_RESULT_NORMAL_FW_NG 0x00000200 +#define WEARABLE_FT_TEST_RESULT_BURN_CC_NG 0x00000400 +#define WEARABLE_FT_TEST_RESULT_PANEL_TEST_3_NG 0x00000800 +#define WEARABLE_FT_TEST_RESULT_GET_DATA_NG 0x00001000 +#define WEARABLE_FT_TEST_RESULT_FLASH_ID_NG 0x00002000 +#define WEARABLE_FT_TEST_RESULT_NORMAL_FW_VER_NG 0x00004000 +#define WEARABLE_FT_TEST_RESULT_TEST_FW_VER_NG 0x00008000 +#define WEARABLE_FT_TEST_RESULT_UC_NG 0x00010000 +#define WEARABLE_FT_TEST_RESULT_SINGLE_CC_OPEN_NG 0x00020000 +#define WEARABLE_FT_TEST_RESULT_SINGLE_CC_SHORT_NG 0x00040000 +#define WEARABLE_FT_TEST_RESULT_CC_CALIBRATION_NG 0x00080000 +#define WEARABLE_FT_TEST_RESULT_TEST_INIT_NG 0x00100000 +#define WEARABLE_FT_TEST_RESULT_LOAD_TESTFW_NG 0x00200000 +#define WEARABLE_FT_TEST_RESULT_BURN_FW_NG 0x00400000 +#define WEARABLE_FT_TEST_RESULT_IC_FW_VERIFY_NG 0x00800000 +#define WEARABLE_FT_TEST_RESULT_EXT_FLASH_EMPTY_NG 0x01000000 +#define WEARABLE_FT_TEST_RESULT_MCU_HOLD_NG 0x02000000 +#if SELFTEST +#define WEARABLE_FT_TEST_RESULT_SYSFS_NG 0x04000000 +#else +#define WEARABLE_FT_TEST_RESULT_AUO_JIG_NG 0x04000000 +#endif +#define WEARABLE_FT_TEST_RESULT_NORMAL_FW_RESET_NG 0x08000000 +#define WEARABLE_FT_TEST_RESULT_POWER_ON_NG 0x10000000 +#define WEARABLE_FT_TEST_RESULT_CMD_NG 0x20000000 +#define WEARABLE_FT_TEST_RESULT_IC_SUB_VERSION_NG 0x40000000 +#define WEARABLE_FT_TEST_RESULT_IC_VERSION_NG 0x80000000 + +#define WEARBLE_FT_BURN_CC_SYSTEM_NG_CASE 1 +#define WEARBLE_FT_FW_VER_SYSTEM_NG_CASE 2 +#define WEARBLE_FT_I2C_SYSTEM_NG_CASE 3 +#define WEARBLE_FT_INT_SYSTEM_NG_CASE 4 +#define WEARBLE_FT_LOAD_TEST_FW_SYSTEM_NG_CASE 5 +#define WEARBLE_FT_PRAM_SYSTEM_NG_CASE 6 +#define WEARBLE_FT_RESET_PIN_SYSTEM_NG_CASE 7 + + +// Define ic test state +typedef enum { + IC_TEST_INIT = 0, + IC_TEST_SYSTEM, //1 + IC_TEST_BURN_FW, //2 + IC_TEST_BURN_CC, //3 + IC_TEST_LOAD_TEST_FW, //4 + IC_TEST_ENTER_FW_TEST_MODE, //5 + IC_TEST_OPEN_SHORT, //6 + IC_TEST_READ_TEST_FW_DATA, //7 + IC_ENTER_NORMAL_FW, //8 + IC_TEST_UC_UB, //9 + IC_TEST_PANEL_PATTERN_TEST, //10 + IC_TEST_EXIT, //11 +} IC_TEST_STATE; + +#pragma pack(push) +#pragma pack(1) +typedef struct { + //-------- FT Test Info (Byte 16)-----------------// + unsigned char u8_device_id; + unsigned char u8_company_id; + unsigned char u8_project_id; + unsigned char u8_station_id; + unsigned short u16_ft_test_item; + unsigned short u16_ft_eng_item; + unsigned short u16_ft_test_info_1; + unsigned short u16_ft_test_info_2; + unsigned short u16_ft_test_info_3; + unsigned short u16_ft_test_info_4; +} st_test_info; +extern st_test_info g_st_test_info; + +typedef struct { + //-------- FT Test Threshold (Byte 26)----------------// + unsigned char u8_ft_test_company_id; + unsigned char u8_ft_test_station_id; + signed short i16_ft_test_open_lower_thd; //TEST_THD_01 + signed short i16_ft_test_short_upper_thd; //TEST_THD_02 + signed short i16_ft_test_short_lower_thd; //TEST_THD_03 + signed short i16_ft_test_single_cc_upper_thd; //TEST_THD_04 + signed short i16_ft_test_single_cc_lower_thd; //TEST_THD_05 + signed short i16_ft_test_uniformity_bl_upper_thd; //TEST_THD_06 + signed short i16_ft_test_uniformity_bl_lower_thd; //TEST_THD_07 + signed short i16_ft_test_uniformity_cc_upper_thd; //TEST_THD_08 + signed short i16_ft_test_uniformity_cc_lower_thd; //TEST_THD_09 + signed short i16_ft_test_panel_test_1_thd; //TEST_THD_10 + signed short i16_ft_test_panel_test_3_thd; //TEST_THD_11 + signed short i16_ft_test_panel_test_2_thd; //TEST_THD_12 + //-------- reserve (Byte 10)-----------------// + signed short i16_ft_test_panel_test_2_s2_thd; //TEST_THD_13 + signed short i16_ft_test_thd_14; + signed short i16_ft_test_thd_15; + signed short i16_ft_test_thd_16; + signed short i16_ft_test_thd_resv; +} st_test_threshold; +extern st_test_threshold g_st_test_thd; + +typedef struct { + //-------- FT Test Parameter (Byte 48)-----------------// + unsigned char u8_para_resv_0[40]; + unsigned int u32_normal_fw_version; + unsigned int u32_test_fw_version; +} st_test_para_resv; +extern st_test_para_resv g_st_test_para_resv; + +#pragma pack(pop) + +extern unsigned char g_u8_ic_test_state; +extern STATUS wearable_ic_test_read_info(void); +extern STATUS wearable_ic_test_init(void); +extern void handle_ic_test(void); +extern void wearable_ic_test_init_buffer(void); +extern STATUS handle_burn_log_to_flash(void); +extern void handle_set_display_interface(unsigned char u8_interface); + diff --git a/qcom/opensource/touch-drivers/raydium/drv_interface.c b/qcom/opensource/touch-drivers/raydium/drv_interface.c new file mode 100644 index 0000000000..b065b8db01 --- /dev/null +++ b/qcom/opensource/touch-drivers/raydium/drv_interface.c @@ -0,0 +1,468 @@ +/* drv_interface.c + * + * Raydium TouchScreen driver. + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * Qualcomm Innovation Center, Inc. chooses to use it under GPLv2 + * Copyright (c) 2021 Raydium tech Ltd. + * + * 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. + * + * BSD LICENSE + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. or Linaro Ltd. nor the names of + * its contributors may be used to endorse or promote products + * derived from this software without specific prior written + * permission. + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "chip_raydium/ic_drv_global.h" +#include "chip_raydium/ic_drv_interface.h" +#include "drv_interface.h" +#include "raydium_selftest.h" +#include "raydium_driver.h" +#include "chip_raydium/f303_ic_control.h" + +unsigned char g_u8_m_buf[2][128]; +unsigned char g_u8_ini_flash[0x400]; +struct raydium_ts_data *ts; +unsigned char g_u8_mute_i2c_err_log; + + +STATUS i2c_burst_read_pda2(unsigned char u8_addr, unsigned short u16ReadLen, unsigned char *p_u8_output_buf) +{ + return ERROR; +} +STATUS i2c_burst_write_pda2(unsigned char u8_addr, unsigned char bWriteLen, unsigned char *bValue) +{ + return ERROR; +} + +unsigned char spi_write_pda(unsigned int u32_addr, unsigned char u8_write_len, unsigned char *bValue, unsigned char u8_trans_mode) +{ + return ERROR; +} +unsigned char spi_read_pda(unsigned int u32_addr, unsigned char u8_read_len, unsigned char *p_u8_output_buf) +{ + return ERROR; +} +STATUS burn_fw_3x(void) +{ + g_u32_wearable_test_result &= ~(WEARABLE_FT_TEST_RESULT_CMD_NG | WEARABLE_FT_TEST_RESULT_BURN_FW_NG); + return SUCCESS; +} +unsigned char fw_upgrade(unsigned char type) +{ + int i32_ret = ERROR; + + i32_ret = raydium_burn_fw(g_raydium_ts->client); + if (i32_ret < 0) + pr_err("[touch]FW update fail:%d\n", i32_ret); + return i32_ret; + +} +unsigned char read_flash_data(unsigned int u32_addr, unsigned short u16_lenth) +{ + unsigned int u32_data_offset; + + if (g_u16_dev_id == DEVICE_ID_2X) { + u32_data_offset = u32_addr - 0x800; + + if (u32_addr < 0x8000) { + if (u32_data_offset >= 0x8000 && u32_addr < F302_DONGLE_FLASH_INI_ADDR) { + u32_data_offset = u32_data_offset - 0x6200; + memcpy(g_u8_data_buf, g_rad_para_image + u32_data_offset, u16_lenth); + } else if (u32_addr >= F302_DONGLE_FLASH_INI_ADDR) { + u32_data_offset = u32_addr - F302_DONGLE_FLASH_INI_ADDR; + memcpy(g_u8_data_buf, g_u8_ini_flash + u32_data_offset, u16_lenth); + } else + memcpy(g_u8_data_buf, g_rad_fw_image + u32_data_offset, u16_lenth); + } else { + u32_data_offset -= 0x8000; + if (u32_data_offset >= 0x6200 && u32_addr < F302_DONGLE_FLASH_INI_ADDR) { + u32_data_offset = u32_data_offset - 0x6200; + memcpy(g_u8_data_buf, g_rad_testpara_image + u32_data_offset, u16_lenth); + } else if (u32_addr >= F302_DONGLE_FLASH_INI_ADDR) { + u32_data_offset = u32_addr - F302_DONGLE_FLASH_INI_ADDR; + memcpy(g_u8_data_buf, g_u8_ini_flash + u32_data_offset, u16_lenth); + } else { + memcpy(g_u8_data_buf, g_rad_testfw_image + u32_data_offset, u16_lenth); + } + } + return TRUE; + } else if (g_u16_dev_id == DEVICE_ID_3X) { + u32_data_offset = u32_addr - 0x800; + + if (u32_addr < 0x7800) { + if (u32_data_offset >= 0x7300 && u32_addr < F303_DONGLE_FLASH_INI_ADDR) { + u32_data_offset = u32_data_offset - 0x7300; + memcpy(g_u8_data_buf, g_rad_para_image + u32_data_offset, u16_lenth); + } else + memcpy(g_u8_data_buf, g_rad_fw_image + u32_data_offset, u16_lenth); + } else { + u32_data_offset -= 0x7800; + if (u32_data_offset >= 0x7300 && u32_addr < F303_DONGLE_FLASH_INI_ADDR) { + u32_data_offset = u32_data_offset - 0x7300; + memcpy(g_u8_data_buf, g_rad_testpara_image + u32_data_offset, u16_lenth); + } else if (u32_addr >= F303_DONGLE_FLASH_INI_ADDR) { + u32_data_offset = u32_addr - F303_DONGLE_FLASH_INI_ADDR; + pr_info("ini addr 0x%x offset %d\r\n", u32_addr, u32_data_offset); + memcpy(g_u8_data_buf, g_u8_ini_flash + u32_data_offset, u16_lenth); + } else { + memcpy(g_u8_data_buf, g_rad_testfw_image + u32_data_offset, u16_lenth); + } + } + return TRUE; + } + return FALSE; +} + +unsigned int get_system_time(void) +{ +/* unsigned int u32_timer; + * struct timeval timer; + * do_gettimeofday(&timer); + * u32_timer = (timer.tv_sec % 1000) * 1000 + (timer.tv_usec / 1000); + * return u32_timer; + */ + #if (KERNEL_VERSION(5, 0, 0) <= LINUX_VERSION_CODE) + struct timespec64 ts; + + ktime_get_ts64(&ts); + return (ts.tv_sec*1000 + ts.tv_nsec/1000000); + #else + struct timeval tv; + + do_gettimeofday(&tv); + return (tv.tv_sec*1000 + tv.tv_usec/1000); + #endif +} + +unsigned char gpio_touch_int_pin_state_access(void) +{ + return gpio_get_value(ts->irq_gpio); +} + +unsigned char gpio_touch_int_access(unsigned char u8_is_clear_flag) +{ + unsigned char u8_flag = g_u8_raydium_flag; + + if (u8_is_clear_flag && u8_flag) + g_u8_raydium_flag &= ~INT_FLAG; + + return u8_flag; +} + +unsigned char sysfs_burn_cc_bl(void) +{ + unsigned char ret = ERROR; + + DEBUGOUT("%s\r\n", __func__); + ret = raydium_burn_comp(ts->client); + return ret; +} + +unsigned char raydium_upgrade_test_fw_2x(unsigned long ul_fw_addr) +{ + int ret = ERROR; + unsigned char u8_retry = 2; + unsigned int u32_read; + unsigned int u32_write; + +RETRY: + gpio_touch_reset_pin_control(0); + delay_ms(10); + gpio_touch_reset_pin_control(1); + delay_ms(2); + u32_write = 0x00000030; + handle_ic_write(0x50000918, 4, (unsigned char *)&u32_write, g_u8_drv_interface, I2C_WORD_MODE); + + if (raydium_load_test_fw(ts->client) == SUCCESS) { + ret = SUCCESS; + DEBUGOUT("### Raydium Load test FW SUCCESS ###\n"); + } + + handle_ic_read(0x6A04, 4, (unsigned char *)&u32_read, g_u8_drv_interface, I2C_WORD_MODE); + + if (u32_read != g_st_test_para_resv.u32_test_fw_version) { + DEBUGOUT("Read FW version NG=0x%x:0x%x!!\r\n", u32_read, g_st_test_para_resv.u32_test_fw_version); + goto ERROR_EXIT; + } + + return ret; + +ERROR_EXIT: + if (u8_retry) { + u8_retry--; + goto RETRY; + } + + return ERROR; +} + +unsigned char raydium_upgrade_test_fw_3x(unsigned long ul_fw_addr) +{ + int ret = ERROR; + unsigned char u8_retry = 2; + unsigned int u32_read; + +RETRY: + + if (raydium_load_test_fw(ts->client) == SUCCESS) { + ret = SUCCESS; + DEBUGOUT("### Raydium Load test FW SUCCESS ###\n"); + } + + handle_ic_read(0x7B04, 4, (unsigned char *)&u32_read, g_u8_drv_interface, I2C_WORD_MODE); + if (u32_read != g_st_test_para_resv.u32_test_fw_version) { + DEBUGOUT("Read FW version NG=0x%x:0x%x!!\r\n", u32_read, g_st_test_para_resv.u32_test_fw_version); + goto ERROR_EXIT; + } + + return ret; + +ERROR_EXIT: + if (u8_retry) { + u8_retry--; + goto RETRY; + } + + return ERROR; +} + +void gpio_touch_reset_pin_control(unsigned char u8_high) +{ + + if (u8_high) + gpio_set_value(ts->rst_gpio, 1); + else + gpio_set_value(ts->rst_gpio, 0); + + return; + +} + + +void gpio_touch_hw_reset(void) +{ + gpio_touch_reset_pin_control(0); + delay_ms(10); + gpio_touch_reset_pin_control(1); + +} +void set_raydium_ts_data(struct raydium_ts_data *ts_old) +{ + ts = ts_old; +} + +/****************************************************************************** + **Function name:handle_ic_read + ** + **Descriptions:handle read data from ic + ** + **parameters:u32_addr,Address + ** u8_read_len,Read datalength + ** p_u8_output_buf,Data buffer + ** u8_interface,SPI or I2C + ** u8_trans_modePDA2_MODE, PDA_WORD_MODE, PDA_BYTE_MODE, MCU_MODE + ** + **Returned value:ERROR,SUCCESS + ** + ****************************************************************************** + */ +unsigned char handle_ic_read( + unsigned int u32_addr, + unsigned short u8_read_len, + unsigned char *p_u8_output_buf, + unsigned char u8_interface, + unsigned char u8_trans_mode) +{ + if (u8_trans_mode == I2C_PDA2_MODE) { + /*PDA2 MODE */ + if (raydium_i2c_pda2_read(g_raydium_ts->client, (unsigned char)u32_addr, p_u8_output_buf, u8_read_len) == ERROR) { + DEBUGOUT("%s\r\n", __func__); + return ERROR; + } + } else { + /*PDA MODE*/ + if ((u8_trans_mode == I2C_WORD_MODE) && (u32_addr & 0x00000003)) { + DEBUGOUT("[HRW] Handle Read Word ADDR Not Word Align!!\r\n"); + return ERROR; + } + + if (u8_interface == SPI_INTERFACE) { + if (spi_read_pda(u32_addr, u8_read_len, p_u8_output_buf) == ERROR) { + DEBUGOUT("%s\r\n", __func__); + return ERROR; + } + } else { + if (g_u16_dev_id == DEVICE_ID_3X) { + if ((u8_interface & I2C_PDA_MODE) != 0) { + if (raydium_i2c_pda_read(g_raydium_ts->client, u32_addr, p_u8_output_buf, u8_read_len) == ERROR) { + DEBUGOUT("%s\r\n", __func__); + return ERROR; + } + } else { + if (raydium_i2c_read_pda_via_pda2(g_raydium_ts->client, u32_addr, p_u8_output_buf, u8_read_len) == ERROR) { + DEBUGOUT("%s\r\n", __func__); + return ERROR; + } + } + } else { + if (raydium_i2c_pda_read(g_raydium_ts->client, u32_addr, p_u8_output_buf, u8_read_len) == ERROR) { + DEBUGOUT("%s\r\n", __func__); + return ERROR; + } + } + } + } + + return SUCCESS; +} + +/****************************************************************************** + ** Function name:handle_ic_write + ** + ** Descriptions:handle write data to ic + ** + ** parameters:u32_addr,Address + ** u8_write_len,datalength + ** bValue,Data + ** u8_interface,SPI or I2C + ** u8_trans_modePDA2_MODE, PDA_WORD_MODE, PDA_BYTE_MODE, MCU_MODE + ** + ** Returned value:ERROR,SUCCESS + ** + ******************************************************************************/ +unsigned char handle_ic_write( + unsigned int u32_addr, + unsigned char u8_write_len, + unsigned char *bValue, + unsigned char u8_interface, + unsigned char u8_trans_mode) +{ + if (u8_trans_mode == I2C_PDA2_MODE) { + /*PDA2 MODE*/ + if (raydium_i2c_pda2_write(g_raydium_ts->client, (unsigned char)u32_addr, bValue, u8_write_len) == ERROR) { + DEBUGOUT("%s\r\n", __func__); + return ERROR; + } + } else { + /*PDA MODE*/ + if ((u8_trans_mode == I2C_WORD_MODE) && (u32_addr & 0x00000003)) { + DEBUGOUT("[I2CRW] Handle Write Word ADDR Not Word Align!!\r\n"); + return ERROR; + } + + if (u8_interface == SPI_INTERFACE) { + switch (u8_trans_mode) { + case I2C_BYTE_MODE: + u8_trans_mode = SPI_BYTE_MODE; + break; + case I2C_WORD_MODE: + u8_trans_mode = SPI_WORD_MODE; + break; + /* case I2C_MCU_MODE: + * + * u8_trans_mode = SPI_MCU_MODE; + * + * break; + */ + case SPI_BYTE_MODE: + case SPI_WORD_MODE: + break; + default: + DEBUGOUT("%s%d\r\n", __func__, u8_trans_mode); + return ERROR; + } + + if (spi_write_pda(u32_addr, u8_write_len, bValue, u8_trans_mode) == ERROR) { + DEBUGOUT("%s\r\n", __func__); + return ERROR; + } + } else { + if (g_u16_dev_id == DEVICE_ID_3X) { + if ((u8_interface & I2C_PDA_MODE) != 0) { + if (raydium_i2c_pda_write(g_raydium_ts->client, u32_addr, bValue, u8_write_len) == ERROR) { + DEBUGOUT("%s\r\n", __func__); + return ERROR; + } + } else { + if (raydium_i2c_write_pda_via_pda2(g_raydium_ts->client, u32_addr, bValue, u8_write_len) == ERROR) { + DEBUGOUT("%s\r\n", __func__); + return ERROR; + } + } + } else { + if (raydium_i2c_pda_write(g_raydium_ts->client, u32_addr, bValue, u8_write_len) == ERROR) { + DEBUGOUT("%s\r\n", __func__); + return ERROR; + } + } + } + } + + return SUCCESS; +} + +/****************************************************************************** + ** Function name:handle_display_write + ** + ** Descriptions:handle write data to display + ** + ** parameters:p_u8_data,Data + ** u16DataLength,datalength + ** + ** + ** Returned value:ERROR,SUCCESS + ** + ******************************************************************************/ +unsigned char handle_display_write( + unsigned char *p_u8_data, + unsigned short u16DataLength) +{ + + if (WriteDriverByTouchMode(p_u8_data, u16DataLength) == ERROR) { + DEBUGOUT("[HDW] WriteDriverByTouchMode NG!\r\n"); + return ERROR; + } + + return SUCCESS; +} diff --git a/qcom/opensource/touch-drivers/raydium/drv_interface.h b/qcom/opensource/touch-drivers/raydium/drv_interface.h new file mode 100644 index 0000000000..0fee291a0f --- /dev/null +++ b/qcom/opensource/touch-drivers/raydium/drv_interface.h @@ -0,0 +1,84 @@ +/* drv_interface.h + * + * Raydium TouchScreen driver. + * + * Copyright (c) 2021 Raydium tech Ltd. + * + * 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. + * + */ + +#include "Config.h" +#include "raydium_driver.h" + +#define DEBUGOUT pr_info +#define delay_ms msleep + +#define I2C_INTERFACE 0 +#define SPI_INTERFACE 1 + +#define M_RX_BUF 1 +#define M_TX_BUF 0 + +#define __I volatile +#define __O volatile +#define __IO volatile + + +#define WORD 4 + +#define RAD_CMD_START_BURN 0x01 +#define RAD_CMD_LOAD_TESTFW 0x04 +#define RAD_CMD_UPDATE_BIN 0x80 +#define RAD_CMD_UPDATE_END 0x81 +#define RAD_CMD_BURN_FINISH 0x82 + +#define WEARABLE_FT_TEST_RESULT_AUO_JIG_NG WEARABLE_FT_TEST_RESULT_SYSFS_NG + + +extern unsigned char g_u8_m_buf[2][128]; +extern struct timeval timer; +extern unsigned char g_rad_fw_flash[32768], g_rad_testfw_flash[32768]; +extern unsigned char g_u8_ini_flash[0x400]; +extern unsigned long g_u32_save_config[64]; +extern unsigned char g_u8_mute_i2c_err_log; +extern short g_i16_raw_data_frame_buffer[101][50]; +extern unsigned char g_u8_test_log_burn_times; + +extern unsigned char read_flash_data(unsigned int u32_addr, unsigned short u16_lenth); +extern unsigned int get_system_time(void); + +extern unsigned char sysfs_mode_control(unsigned char u8_type); +//extern unsigned char sysfs_read_int_flag(unsigned char *p_u8_flag, unsigned char u8_is_clear_flag); +extern unsigned char sysfs_write_int_flag(char *p_cmd); +//extern unsigned char sysfs_i2c_read(char *p_u8_addr, char u8_len, unsigned char *p_u8_data); +//extern unsigned char sysfs_i2c_write(char *p_u8_addr, char u8_len, unsigned char *p_u8_data); +extern unsigned char sysfs_do_cal(void); +extern unsigned char sysfs_burn_cc_bl(void); + +extern void gpio_touch_reset_pin_control(unsigned char u8_high); +extern void gpio_touch_hw_reset(void); +extern unsigned char fw_upgrade(unsigned char type); +extern unsigned char gpio_touch_int_access(unsigned char u8_is_clear_flag); +extern unsigned char gpio_touch_int_pin_state_access(void); + +extern unsigned char handle_read_word(unsigned int addr, unsigned int *p_data); +extern unsigned char handle_write_word(unsigned int u32_addr, unsigned int u32_data); +extern unsigned char handle_read_pda(unsigned int u32_addr, unsigned char u8_read_len, unsigned char *p_u8_output_buf); +extern unsigned char handle_write_pda(unsigned int u32_addr, unsigned char u8_write_len, unsigned char *bValue, unsigned char u8_trans_mode); +extern unsigned char handle_ic_read(unsigned int u32_addr, unsigned short u8_read_len, unsigned char *p_u8_output_buf, unsigned char u8_interface, unsigned char u8_trans_mode); +extern unsigned char handle_ic_write(unsigned int u32_addr, unsigned char u8_write_len, unsigned char *bValue, unsigned char u8_interface, unsigned char u8_trans_mode); + +extern unsigned char raydium_upgrade_test_fw(unsigned long ul_fw_addr); +extern unsigned char raydium_upgrade_test_fw_3x(unsigned long ul_fw_addr); +extern unsigned char raydium_upgrade_test_fw_2x(unsigned long ul_fw_addr); +extern void set_raydium_ts_data(struct raydium_ts_data *ts_old); + diff --git a/qcom/opensource/touch-drivers/raydium/rad_fw_image_30.h b/qcom/opensource/touch-drivers/raydium/rad_fw_image_30.h new file mode 100644 index 0000000000..fc549b4aeb --- /dev/null +++ b/qcom/opensource/touch-drivers/raydium/rad_fw_image_30.h @@ -0,0 +1,7791 @@ +/* rad_fw_image_30.h + * + * QTI elects to receive under BSD license only. + * + * Raydium TouchScreen driver. + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + + * Copyright (c) 2021 Raydium tech Ltd. + * + * 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.i2c_driver/rad_fw_image_30.h + * + * BSD LICENSE + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. or Linaro Ltd. nor the names of + * its contributors may be used to endorse or promote products + * derived from this software without specific prior written + * permission. + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* RAYDIUM FW VERSION : 0x02020001 */ + +#define RAD_30 0x3202 +const unsigned char u8_rad_boot_30[] = { + 0x18, 0x01, 0x00, 0x20, 0x95, 0x01, 0x00, 0x00, + 0x99, 0x01, 0x00, 0x00, 0x9B, 0x01, 0x00, 0x00, + 0x9D, 0x01, 0x00, 0x00, 0x9F, 0x01, 0x00, 0x00, + 0xA1, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xA3, 0x01, 0x00, 0x00, + 0xA5, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xA7, 0x01, 0x00, 0x00, 0x81, 0x0F, 0x00, 0x00, + 0xE1, 0x0C, 0x00, 0x00, 0x01, 0x0A, 0x00, 0x00, + 0xA1, 0x0F, 0x00, 0x00, 0x61, 0x0E, 0x00, 0x00, + 0xA1, 0x0D, 0x00, 0x00, 0x41, 0x0A, 0x00, 0x00, + 0xAB, 0x01, 0x00, 0x00, 0x01, 0x0B, 0x00, 0x00, + 0xAB, 0x01, 0x00, 0x00, 0x41, 0x0B, 0x00, 0x00, + 0xE1, 0x0A, 0x00, 0x00, 0xA1, 0x0A, 0x00, 0x00, + 0xC1, 0x0A, 0x00, 0x00, 0x81, 0x0A, 0x00, 0x00, + 0xF1, 0x0F, 0x00, 0x00, 0xAB, 0x01, 0x00, 0x00, + 0x03, 0xF3, 0x00, 0x0B, 0x03, 0x48, 0x85, 0x46, + 0x00, 0xF0, 0x92, 0xF8, 0x00, 0x48, 0x00, 0x47, + 0xCB, 0x00, 0x00, 0x00, 0x18, 0x01, 0x00, 0x20, + 0x80, 0xF3, 0x08, 0x88, 0x70, 0x47, 0x00, 0x00, + 0x10, 0xB5, 0x25, 0x48, 0x81, 0x69, 0x01, 0x22, + 0x92, 0x03, 0x11, 0x43, 0x81, 0x61, 0x41, 0x69, + 0x10, 0x22, 0x11, 0x43, 0x41, 0x61, 0xD4, 0x01, + 0x20, 0x68, 0xFF, 0xF7, 0xED, 0xFF, 0x60, 0x68, + 0x1E, 0x49, 0x48, 0x60, 0x08, 0x60, 0x80, 0x47, + 0x10, 0xBD, 0x01, 0x26, 0xB6, 0x07, 0x30, 0x68, + 0x1B, 0x49, 0x08, 0x43, 0x30, 0x60, 0x18, 0x49, + 0x48, 0x69, 0x20, 0x22, 0x10, 0x43, 0x48, 0x61, + 0x00, 0xF0, 0x34, 0xF8, 0x00, 0xF0, 0xA5, 0xF8, + 0x16, 0x4F, 0x17, 0x4C, 0x01, 0x25, 0x60, 0x68, + 0x00, 0x28, 0x06, 0xD0, 0x01, 0x28, 0x16, 0xD0, + 0x02, 0x28, 0xF8, 0xD1, 0xFF, 0xF7, 0xD0, 0xFF, + 0xF5, 0xE7, 0x00, 0xF0, 0x5F, 0xF9, 0x03, 0x28, + 0x07, 0xD0, 0x02, 0x28, 0xEF, 0xD1, 0x65, 0x60, + 0x7D, 0x20, 0xC0, 0x00, 0x00, 0xF0, 0x84, 0xF8, + 0xE9, 0xE7, 0xB8, 0x68, 0x00, 0x28, 0xE6, 0xD1, + 0x02, 0x20, 0x60, 0x60, 0xE3, 0xE7, 0x00, 0xF0, + 0x91, 0xFA, 0x01, 0x28, 0xDF, 0xD1, 0x00, 0x20, + 0x60, 0x60, 0x75, 0x60, 0xDB, 0xE7, 0x00, 0x00, + 0x00, 0x09, 0x00, 0x50, 0x0C, 0x00, 0x00, 0x20, + 0x40, 0x01, 0x00, 0xC8, 0x10, 0x02, 0x00, 0x20, + 0x00, 0x02, 0x00, 0x20, 0x0D, 0x48, 0x0C, 0x49, + 0x01, 0x61, 0x0D, 0x4A, 0x03, 0x21, 0xD1, 0x61, + 0x01, 0x21, 0x89, 0x07, 0x0A, 0x68, 0x0B, 0x4B, + 0x1A, 0x43, 0x0A, 0x60, 0x0A, 0x4A, 0x02, 0x63, + 0x02, 0x6A, 0x52, 0x00, 0x52, 0x08, 0x02, 0x62, + 0xC2, 0x6A, 0x04, 0x23, 0x1A, 0x43, 0xC2, 0x62, + 0x10, 0x20, 0x48, 0x60, 0x70, 0x47, 0x00, 0x00, + 0x00, 0x00, 0x06, 0x07, 0x00, 0x06, 0x00, 0x50, + 0x00, 0x0E, 0x00, 0x50, 0x10, 0x01, 0x00, 0x80, + 0x0A, 0x66, 0x00, 0x00, 0x05, 0x48, 0x00, 0x47, + 0xFE, 0xE7, 0xFE, 0xE7, 0xFE, 0xE7, 0xFE, 0xE7, + 0xFE, 0xE7, 0xFE, 0xE7, 0xFE, 0xE7, 0xFE, 0xE7, + 0xFE, 0xE7, 0xFE, 0xE7, 0x85, 0x00, 0x00, 0x00, + 0x06, 0x4C, 0x01, 0x25, 0x06, 0x4E, 0x05, 0xE0, + 0xE3, 0x68, 0x07, 0xCC, 0x2B, 0x43, 0x0C, 0x3C, + 0x98, 0x47, 0x10, 0x34, 0xB4, 0x42, 0xF7, 0xD3, + 0xFF, 0xF7, 0x60, 0xFF, 0xDC, 0x06, 0x00, 0x00, + 0xFC, 0x06, 0x00, 0x00, 0x30, 0xB4, 0x74, 0x46, + 0x64, 0x1E, 0x25, 0x78, 0x64, 0x1C, 0xAB, 0x42, + 0x00, 0xD2, 0x1D, 0x46, 0x63, 0x5D, 0x5B, 0x00, + 0xE3, 0x18, 0x30, 0xBC, 0x18, 0x47, 0x02, 0xE0, + 0x08, 0xC8, 0x12, 0x1F, 0x08, 0xC1, 0x00, 0x2A, + 0xFA, 0xD1, 0x70, 0x47, 0x70, 0x47, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x49, 0x00, 0x20, + 0x88, 0x60, 0xC8, 0x60, 0x48, 0x60, 0x3F, 0x22, + 0x0A, 0x60, 0x08, 0x61, 0xFB, 0x49, 0x08, 0x60, + 0xFB, 0x49, 0x08, 0x60, 0x48, 0x60, 0x70, 0x47, + 0xE1, 0x21, 0x09, 0x01, 0x48, 0x43, 0x00, 0x21, + 0x00, 0xE0, 0x49, 0x1C, 0x81, 0x42, 0xFC, 0xD3, + 0x70, 0x47, 0x10, 0xB5, 0xF3, 0x49, 0x00, 0x20, + 0x48, 0x60, 0x88, 0x60, 0xC8, 0x60, 0xF2, 0x4B, + 0x98, 0x60, 0xEF, 0x4C, 0xA0, 0x69, 0x01, 0x21, + 0x89, 0x03, 0x88, 0x43, 0xA0, 0x61, 0xFF, 0xF7, + 0xD9, 0xFF, 0xA0, 0x69, 0x00, 0x05, 0x05, 0xD5, + 0x82, 0x20, 0x99, 0x68, 0x00, 0x29, 0x01, 0xD1, + 0x58, 0x60, 0xFA, 0xE7, 0x10, 0xBD, 0x70, 0xB5, + 0x05, 0x46, 0x00, 0x20, 0x02, 0x46, 0x01, 0x26, + 0x09, 0xE0, 0x34, 0x46, 0x94, 0x40, 0x33, 0x46, + 0x2C, 0x42, 0x03, 0xD0, 0x8C, 0x1A, 0x64, 0x1E, + 0xA3, 0x40, 0x18, 0x43, 0x52, 0x1C, 0x8A, 0x42, + 0xF3, 0xD3, 0x70, 0xBD, 0xF0, 0xB5, 0x00, 0x24, + 0xE4, 0x43, 0x0E, 0x46, 0x07, 0x46, 0x00, 0x25, + 0x14, 0xE0, 0x78, 0x5D, 0x08, 0x21, 0xFF, 0xF7, + 0xE2, 0xFF, 0xC1, 0xB2, 0xD9, 0x4A, 0x00, 0x20, + 0x23, 0x46, 0x4B, 0x40, 0xDB, 0x07, 0x02, 0xD0, + 0x64, 0x08, 0x54, 0x40, 0x00, 0xE0, 0x64, 0x08, + 0x40, 0x1C, 0x49, 0x08, 0xC0, 0xB2, 0x08, 0x28, + 0xF2, 0xD3, 0x6D, 0x1C, 0xB5, 0x42, 0xE8, 0xD3, + 0x20, 0x21, 0x20, 0x46, 0xFF, 0xF7, 0xCB, 0xFF, + 0xF0, 0xBD, 0x1F, 0x20, 0x80, 0x01, 0xCE, 0x49, + 0xC0, 0x6B, 0x08, 0x60, 0xC8, 0x49, 0x80, 0x31, + 0x0A, 0x68, 0x82, 0x42, 0x15, 0xD1, 0xCB, 0x48, + 0xCB, 0x4A, 0x40, 0x6B, 0x10, 0x60, 0x4A, 0x68, + 0x82, 0x42, 0x0E, 0xD1, 0xC9, 0x48, 0xCA, 0x4A, + 0xC0, 0x6B, 0x10, 0x60, 0x8A, 0x68, 0x82, 0x42, + 0x07, 0xD1, 0xC6, 0x48, 0xC7, 0x4A, 0x80, 0x30, + 0xC0, 0x6B, 0x10, 0x60, 0xC9, 0x68, 0x81, 0x42, + 0x01, 0xD0, 0x00, 0x20, 0x70, 0x47, 0x01, 0x20, + 0x70, 0x47, 0x70, 0xB5, 0x0D, 0x46, 0x03, 0x46, + 0xC1, 0x4A, 0xB7, 0x4E, 0x02, 0xE0, 0x01, 0x20, + 0xFF, 0xF7, 0x7A, 0xFF, 0x74, 0x69, 0x1C, 0x40, + 0xAC, 0x42, 0x04, 0xD0, 0x10, 0x46, 0x52, 0x1E, + 0x92, 0xB2, 0x00, 0x28, 0xF3, 0xD1, 0x00, 0x2A, + 0x01, 0xD0, 0x01, 0x20, 0x70, 0xBD, 0x00, 0x20, + 0x70, 0xBD, 0xF0, 0xB5, 0x00, 0x23, 0x01, 0x24, + 0xB6, 0x4D, 0xE4, 0x02, 0x19, 0x46, 0x1A, 0x46, + 0x01, 0x28, 0x0E, 0xD0, 0x04, 0x28, 0x05, 0xD0, + 0x10, 0x28, 0x0E, 0xD1, 0x03, 0x02, 0xB2, 0x4A, + 0x21, 0x46, 0x0A, 0xE0, 0x09, 0x23, 0x61, 0x22, + 0xA8, 0x49, 0x1B, 0x03, 0xD2, 0x00, 0x38, 0x31, + 0x03, 0xE0, 0x80, 0x22, 0xFF, 0x21, 0x23, 0x46, + 0xC9, 0x01, 0x9F, 0x4C, 0x20, 0x68, 0x1D, 0x26, + 0xB0, 0x43, 0x20, 0x60, 0x23, 0x61, 0x40, 0x27, + 0x11, 0xE0, 0x60, 0x69, 0x38, 0x43, 0x60, 0x61, + 0x28, 0x46, 0x63, 0x69, 0x5B, 0x06, 0x04, 0xD5, + 0x03, 0x46, 0x40, 0x1E, 0x80, 0xB2, 0x00, 0x2B, + 0xF7, 0xD1, 0xE0, 0x6B, 0x01, 0xC1, 0x20, 0x69, + 0x00, 0x1D, 0x20, 0x61, 0x12, 0x1F, 0x00, 0x2A, + 0xEB, 0xD1, 0x20, 0x68, 0x30, 0x43, 0x20, 0x60, + 0x01, 0x20, 0xF0, 0xBD, 0xF0, 0xB5, 0x8F, 0x4B, + 0x98, 0x68, 0x01, 0x25, 0x8C, 0x4C, 0x00, 0x28, + 0x09, 0xD0, 0x80, 0x27, 0x8C, 0x4E, 0x01, 0x28, + 0x0F, 0xD0, 0x00, 0x21, 0x02, 0x28, 0x1E, 0xD0, + 0x03, 0x28, 0x1F, 0xD1, 0x24, 0xE0, 0xA0, 0x69, + 0x80, 0x04, 0x1B, 0xD4, 0xA0, 0x69, 0x00, 0x05, + 0x01, 0xD5, 0x9D, 0x60, 0x16, 0xE0, 0x03, 0x20, + 0x0F, 0xE0, 0x1D, 0x60, 0xA0, 0x20, 0x30, 0x60, + 0x81, 0x20, 0x70, 0x60, 0xFF, 0xF7, 0x65, 0xFF, + 0x00, 0x28, 0x00, 0xD1, 0x77, 0x60, 0xA0, 0x69, + 0x01, 0x21, 0xC9, 0x02, 0x88, 0x43, 0xA0, 0x61, + 0x02, 0x20, 0x98, 0x60, 0x02, 0xE0, 0x18, 0x68, + 0x00, 0x28, 0x01, 0xD0, 0x00, 0x20, 0xF0, 0xBD, + 0x99, 0x60, 0xFF, 0xF7, 0xEB, 0xFE, 0x27, 0xE0, + 0x99, 0x60, 0xA0, 0x69, 0xC0, 0x06, 0x02, 0xD5, + 0xA0, 0x69, 0x80, 0x05, 0x15, 0xD5, 0xFF, 0xF7, + 0x48, 0xFF, 0x00, 0x28, 0x1C, 0xD1, 0x10, 0x20, + 0xFF, 0xF7, 0x7F, 0xFF, 0x04, 0x20, 0xFF, 0xF7, + 0x7C, 0xFF, 0x01, 0x20, 0xFF, 0xF7, 0x79, 0xFF, + 0xA0, 0x69, 0x80, 0x05, 0x12, 0xD4, 0x21, 0x20, + 0x00, 0x01, 0xA0, 0x61, 0x80, 0x06, 0x45, 0x60, + 0x0C, 0xE0, 0xA0, 0x69, 0x40, 0x05, 0xA0, 0x69, + 0x04, 0xD5, 0x01, 0x21, 0x89, 0x02, 0x88, 0x43, + 0xA0, 0x61, 0x01, 0xE0, 0x40, 0x06, 0x01, 0xD5, + 0x03, 0x20, 0xF0, 0xBD, 0x77, 0x60, 0x02, 0x20, + 0xF0, 0xBD, 0xF0, 0xB5, 0x69, 0x4F, 0x00, 0x26, + 0x5B, 0x4C, 0x3E, 0x70, 0x21, 0x68, 0x62, 0x68, + 0x91, 0x43, 0x21, 0x60, 0x04, 0x28, 0x2C, 0xD0, + 0x10, 0x28, 0x27, 0xD0, 0x20, 0x28, 0x2C, 0xD1, + 0x5A, 0x48, 0xC1, 0x21, 0x89, 0x00, 0x38, 0x30, + 0xFF, 0xF7, 0xEC, 0xFE, 0x59, 0x4D, 0xE9, 0x6B, + 0x88, 0x42, 0x18, 0xD0, 0x20, 0x68, 0x04, 0x21, + 0x88, 0x43, 0x20, 0x60, 0x00, 0x20, 0x41, 0x1E, + 0x1F, 0x22, 0x92, 0x02, 0x83, 0x00, 0x9B, 0x18, + 0x40, 0x1C, 0xC0, 0xB2, 0x99, 0x67, 0xC1, 0x28, + 0xF8, 0xD3, 0x4E, 0x48, 0xC1, 0x21, 0x89, 0x00, + 0x38, 0x30, 0xAE, 0x63, 0xFF, 0xF7, 0xD2, 0xFE, + 0xE8, 0x63, 0x01, 0x20, 0x38, 0x70, 0x20, 0x68, + 0x10, 0x21, 0x04, 0xE0, 0x20, 0x68, 0x08, 0x21, + 0x01, 0xE0, 0x20, 0x68, 0x02, 0x21, 0x88, 0x43, + 0x20, 0x60, 0xF0, 0xBD, 0xF0, 0xB5, 0x3E, 0x4C, + 0x0F, 0x46, 0x05, 0x46, 0xC6, 0x19, 0x20, 0x61, + 0x0E, 0xE0, 0x60, 0x69, 0x02, 0x21, 0x08, 0x43, + 0x60, 0x61, 0x08, 0x21, 0x08, 0x46, 0xFF, 0xF7, + 0xFC, 0xFE, 0x00, 0x28, 0x16, 0xD0, 0x21, 0x69, + 0x01, 0x20, 0x00, 0x03, 0x08, 0x18, 0x20, 0x61, + 0x20, 0x69, 0xB0, 0x42, 0xED, 0xD3, 0x25, 0x61, + 0x38, 0x0A, 0x00, 0x02, 0xE0, 0x60, 0x60, 0x69, + 0x04, 0x21, 0x08, 0x43, 0x60, 0x61, 0x08, 0x21, + 0x08, 0x46, 0xFF, 0xF7, 0xE6, 0xFE, 0x00, 0x28, + 0x00, 0xD0, 0x01, 0x20, 0xF0, 0xBD, 0x2A, 0x4A, + 0x52, 0x68, 0x04, 0x2A, 0x0D, 0xD0, 0x10, 0x2A, + 0x06, 0xD0, 0x20, 0x2A, 0x0F, 0xD1, 0x00, 0x22, + 0x02, 0x60, 0x11, 0x20, 0xC0, 0x01, 0x09, 0xE0, + 0x01, 0x22, 0x12, 0x03, 0x02, 0x60, 0x2E, 0x48, + 0x04, 0xE0, 0x09, 0x22, 0x12, 0x03, 0x02, 0x60, + 0x61, 0x20, 0xC0, 0x00, 0x08, 0x60, 0x70, 0x47, + 0xF0, 0xB5, 0x2B, 0x4C, 0x00, 0x25, 0x20, 0x68, + 0x01, 0x23, 0x1D, 0x4E, 0x1B, 0x4F, 0x00, 0x28, + 0x04, 0xD0, 0x01, 0x28, 0x0D, 0xD0, 0x02, 0x28, + 0x14, 0xD1, 0x15, 0xE0, 0x24, 0x49, 0x08, 0x31, + 0x08, 0x1F, 0xFF, 0xF7, 0xD4, 0xFF, 0x75, 0x60, + 0xA8, 0x20, 0x30, 0x60, 0x23, 0x60, 0x3B, 0x60, + 0x08, 0xE0, 0x38, 0x68, 0x00, 0x28, 0x05, 0xD1, + 0x3B, 0x60, 0x70, 0x68, 0xA8, 0x28, 0x01, 0xD1, + 0x02, 0x20, 0x20, 0x60, 0x00, 0x20, 0xF0, 0xBD, + 0xA1, 0x68, 0x60, 0x68, 0x49, 0x1E, 0xFF, 0xF7, + 0x95, 0xFF, 0x00, 0x28, 0xF7, 0xD0, 0x15, 0x48, + 0x07, 0x4E, 0x00, 0x78, 0x01, 0x28, 0x08, 0xD1, + 0x0A, 0x48, 0x38, 0x30, 0xB0, 0x60, 0x13, 0x49, + 0x30, 0x01, 0xFF, 0xF7, 0x87, 0xFF, 0x00, 0x28, + 0xE9, 0xD0, 0x75, 0x63, 0xB5, 0x63, 0x1F, 0xE0, + 0x00, 0x09, 0x00, 0x50, 0x00, 0x02, 0x00, 0x20, + 0x10, 0x02, 0x00, 0x20, 0x20, 0x83, 0xB8, 0xED, + 0x98, 0x01, 0x00, 0x20, 0x40, 0x7C, 0x00, 0x00, + 0x9C, 0x01, 0x00, 0x20, 0x40, 0x7F, 0x00, 0x00, + 0xA0, 0x01, 0x00, 0x20, 0xA4, 0x01, 0x00, 0x20, + 0xB8, 0x0B, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, + 0x78, 0x74, 0x00, 0x00, 0x14, 0x00, 0x00, 0x20, + 0x00, 0x00, 0x00, 0x20, 0x07, 0x03, 0x00, 0x00, + 0x01, 0x20, 0xC0, 0x02, 0xB0, 0x61, 0x01, 0x20, + 0x25, 0x60, 0xF0, 0xBD, 0xF0, 0xB5, 0x1C, 0x4C, + 0x00, 0x27, 0xE0, 0x68, 0x01, 0x25, 0x1B, 0x4E, + 0x03, 0x00, 0xFF, 0xF7, 0xBB, 0xFD, 0x07, 0x05, + 0x2E, 0x0F, 0x2E, 0x2E, 0x1B, 0x28, 0x2E, 0x00, + 0xB0, 0x68, 0x01, 0x28, 0x25, 0xD1, 0xA1, 0x20, + 0x30, 0x60, 0xFF, 0x20, 0x70, 0x60, 0x25, 0x60, + 0x02, 0x20, 0x1D, 0xE0, 0x20, 0x68, 0x00, 0x28, + 0x1B, 0xD1, 0x25, 0x60, 0x10, 0x48, 0x40, 0x68, + 0xFF, 0xF7, 0xFF, 0xFE, 0xA3, 0x20, 0x30, 0x60, + 0x05, 0x20, 0x11, 0xE0, 0xFF, 0xF7, 0x78, 0xFF, + 0x00, 0x28, 0x0E, 0xD0, 0xB0, 0x68, 0x00, 0x28, + 0x01, 0xD0, 0x25, 0x60, 0x01, 0xE0, 0x00, 0x20, + 0x20, 0x60, 0x06, 0x20, 0x04, 0xE0, 0x20, 0x68, + 0x00, 0x28, 0x02, 0xD1, 0x25, 0x60, 0x01, 0x27, + 0xE0, 0x60, 0x38, 0x46, 0xF0, 0xBD, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x20, 0x10, 0x02, 0x00, 0x20, + 0x00, 0x09, 0x00, 0x50, 0x00, 0x20, 0x01, 0xE0, + 0x01, 0xC1, 0x12, 0x1F, 0x00, 0x2A, 0xFB, 0xD1, + 0x70, 0x47, 0x00, 0x00, 0xFC, 0x06, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x20, 0x18, 0x00, 0x00, 0x00, + 0xEE, 0x01, 0x00, 0x00, 0x14, 0x07, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x20, 0x04, 0x02, 0x00, 0x00, + 0xCC, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xD5, 0x4D, 0xDB, 0x52, +}; +const unsigned char u8_rad_init_30[] = { + 0x10, 0x06, 0x00, 0x50, 0x00, 0x00, 0x06, 0x01, + 0x1C, 0x0E, 0x00, 0x50, 0x03, 0x00, 0x00, 0x00, + 0x28, 0x06, 0x00, 0x50, 0x06, 0x00, 0x00, 0x00, + 0x20, 0x06, 0x00, 0x50, 0x00, 0x00, 0x00, 0x40, + 0x10, 0x07, 0x00, 0x20, 0xFF, 0x00, 0xAA, 0x55, + 0x14, 0x07, 0x00, 0x20, 0xFF, 0x00, 0xAA, 0x55, + 0x18, 0x07, 0x00, 0x20, 0xFF, 0x00, 0xAA, 0x55, + 0x1C, 0x07, 0x00, 0x20, 0xFF, 0x00, 0xAA, 0x55, + 0x20, 0x07, 0x00, 0x20, 0xFF, 0x00, 0xAA, 0x55, + 0x24, 0x07, 0x00, 0x20, 0xFF, 0x00, 0xAA, 0x55, + 0x28, 0x07, 0x00, 0x20, 0xFF, 0x00, 0xAA, 0x55, + 0x2C, 0x07, 0x00, 0x20, 0xFF, 0x00, 0xAA, 0x55, + 0x30, 0x07, 0x00, 0x20, 0xFF, 0x00, 0xAA, 0x55, + 0x34, 0x07, 0x00, 0x20, 0xFF, 0x00, 0xAA, 0x55, + 0x74, 0x09, 0x00, 0x50, 0xFF, 0x00, 0xAA, 0x55, + 0x3C, 0x07, 0x00, 0x20, 0x17, 0x26, 0x28, 0x2A, +}; +const unsigned char u8_rad_fw_30[] = { + 0xD0, 0x0C, 0x00, 0x20, 0xB1, 0x08, 0x00, 0x00, + 0xB9, 0x08, 0x00, 0x00, 0x95, 0x08, 0x00, 0x00, + 0xBD, 0x08, 0x00, 0x00, 0xBF, 0x08, 0x00, 0x00, + 0xC1, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xC3, 0x08, 0x00, 0x00, + 0xC5, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xC7, 0x08, 0x00, 0x00, 0x81, 0x0F, 0x00, 0x00, + 0xE1, 0x0C, 0x00, 0x00, 0x01, 0x0A, 0x00, 0x00, + 0xCB, 0x08, 0x00, 0x00, 0x61, 0x0E, 0x00, 0x00, + 0xA1, 0x0D, 0x00, 0x00, 0x41, 0x0A, 0x00, 0x00, + 0xCB, 0x08, 0x00, 0x00, 0x01, 0x0B, 0x00, 0x00, + 0xCB, 0x08, 0x00, 0x00, 0x41, 0x0B, 0x00, 0x00, + 0xE1, 0x0A, 0x00, 0x00, 0xA1, 0x0A, 0x00, 0x00, + 0xC1, 0x0A, 0x00, 0x00, 0x81, 0x0A, 0x00, 0x00, + 0xCB, 0x08, 0x00, 0x00, 0xCB, 0x08, 0x00, 0x00, + 0x03, 0x48, 0x85, 0x46, 0x00, 0xF0, 0x80, 0xF8, + 0x00, 0x48, 0x00, 0x47, 0x19, 0x44, 0x00, 0x00, + 0xD0, 0x0C, 0x00, 0x20, 0x04, 0x20, 0x71, 0x46, + 0x08, 0x42, 0x02, 0xD0, 0xEF, 0xF3, 0x09, 0x80, + 0x01, 0xE0, 0xEF, 0xF3, 0x08, 0x80, 0x71, 0x46, + 0x00, 0x4A, 0x10, 0x47, 0xC1, 0x42, 0x00, 0x00, + 0x06, 0x48, 0x80, 0x47, 0x06, 0x48, 0x00, 0x47, + 0xFE, 0xE7, 0xFE, 0xE7, 0xFE, 0xE7, 0xFE, 0xE7, + 0xFE, 0xE7, 0xFE, 0xE7, 0xFE, 0xE7, 0xFE, 0xE7, + 0xFE, 0xE7, 0xFE, 0xE7, 0x15, 0x0A, 0x00, 0x00, + 0x81, 0x08, 0x00, 0x00, 0x30, 0xB5, 0x0B, 0x46, + 0x01, 0x46, 0x00, 0x20, 0x20, 0x22, 0x01, 0x24, + 0x09, 0xE0, 0x0D, 0x46, 0xD5, 0x40, 0x9D, 0x42, + 0x05, 0xD3, 0x1D, 0x46, 0x95, 0x40, 0x49, 0x1B, + 0x25, 0x46, 0x95, 0x40, 0x40, 0x19, 0x15, 0x46, + 0x52, 0x1E, 0x00, 0x2D, 0xF1, 0xDC, 0x30, 0xBD, + 0x70, 0xB5, 0x00, 0x24, 0x25, 0x46, 0x00, 0x28, + 0x01, 0xDA, 0x01, 0x24, 0x40, 0x42, 0x00, 0x29, + 0x01, 0xDA, 0x01, 0x25, 0x49, 0x42, 0xFF, 0xF7, + 0xDD, 0xFF, 0xAC, 0x42, 0x00, 0xD0, 0x40, 0x42, + 0x00, 0x2C, 0x00, 0xD0, 0x49, 0x42, 0x70, 0xBD, + 0x03, 0x46, 0x0B, 0x43, 0x9B, 0x07, 0x03, 0xD0, + 0x09, 0xE0, 0x08, 0xC9, 0x12, 0x1F, 0x08, 0xC0, + 0x04, 0x2A, 0xFA, 0xD2, 0x03, 0xE0, 0x0B, 0x78, + 0x49, 0x1C, 0x03, 0x70, 0x40, 0x1C, 0x52, 0x1E, + 0xF9, 0xD2, 0x70, 0x47, 0xD2, 0xB2, 0x01, 0xE0, + 0x02, 0x70, 0x40, 0x1C, 0x49, 0x1E, 0xFB, 0xD2, + 0x70, 0x47, 0x00, 0x22, 0xF6, 0xE7, 0x10, 0xB5, + 0x04, 0x46, 0x08, 0x46, 0x11, 0x46, 0x02, 0x46, + 0x20, 0x46, 0xFF, 0xF7, 0xEF, 0xFF, 0x20, 0x46, + 0x10, 0xBD, 0x00, 0x1D, 0x03, 0x21, 0x40, 0x1E, + 0x03, 0x78, 0x12, 0x02, 0x1A, 0x43, 0x49, 0x1E, + 0xF9, 0xD5, 0x10, 0x46, 0x70, 0x47, 0x00, 0x00, + 0x06, 0x4C, 0x01, 0x25, 0x06, 0x4E, 0x05, 0xE0, + 0x20, 0x46, 0xE3, 0x68, 0x07, 0xC8, 0x2B, 0x43, + 0x98, 0x47, 0x10, 0x34, 0xB4, 0x42, 0xF7, 0xD3, + 0xFF, 0xF7, 0x72, 0xFF, 0x58, 0x5C, 0x00, 0x00, + 0x78, 0x5C, 0x00, 0x00, 0xC1, 0x06, 0xC9, 0x0E, + 0x01, 0x20, 0x88, 0x40, 0x01, 0x49, 0x08, 0x60, + 0x70, 0x47, 0x00, 0x00, 0x00, 0xE1, 0x00, 0xE0, + 0x0B, 0x49, 0x10, 0xB5, 0x88, 0x42, 0x01, 0xD9, + 0x01, 0x20, 0x10, 0xBD, 0x01, 0x02, 0x09, 0x0A, + 0x08, 0x48, 0x49, 0x1E, 0x41, 0x61, 0x08, 0x49, + 0x07, 0x23, 0xCA, 0x69, 0x12, 0x02, 0x12, 0x0A, + 0x04, 0x04, 0x22, 0x43, 0xCA, 0x61, 0x00, 0x21, + 0x81, 0x61, 0x03, 0x61, 0x08, 0x46, 0x10, 0xBD, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xE0, 0x00, 0xE0, + 0x04, 0xED, 0x00, 0xE0, 0x70, 0x47, 0x00, 0x00, + 0x03, 0x49, 0x02, 0x20, 0x08, 0x60, 0x02, 0x49, + 0x80, 0x39, 0x08, 0x60, 0x70, 0x47, 0x00, 0x00, + 0x80, 0xE1, 0x00, 0xE0, 0x62, 0xB6, 0x02, 0x48, + 0x00, 0x21, 0x01, 0x60, 0x70, 0x47, 0x00, 0x00, + 0x0C, 0x00, 0x00, 0x40, 0x30, 0xB4, 0x74, 0x46, + 0x64, 0x1E, 0x25, 0x78, 0x64, 0x1C, 0xAB, 0x42, + 0x00, 0xD2, 0x1D, 0x46, 0x63, 0x5D, 0x5B, 0x00, + 0xE3, 0x18, 0x30, 0xBC, 0x18, 0x47, 0x00, 0x00, + 0x05, 0x48, 0x01, 0x68, 0x49, 0x1C, 0x01, 0x60, + 0x01, 0x20, 0x80, 0x07, 0x01, 0x6A, 0x03, 0x22, + 0xD2, 0x03, 0x11, 0x43, 0x01, 0x62, 0x70, 0x47, + 0xD0, 0x00, 0x00, 0x20, 0x02, 0xE0, 0x08, 0xC8, + 0x12, 0x1F, 0x08, 0xC1, 0x00, 0x2A, 0xFA, 0xD1, + 0x70, 0x47, 0x00, 0x20, 0x01, 0xE0, 0x01, 0xC1, + 0x12, 0x1F, 0x00, 0x2A, 0xFB, 0xD1, 0x70, 0x47, + 0x01, 0x21, 0x89, 0x07, 0x48, 0x60, 0x70, 0x47, + 0x01, 0x20, 0x80, 0x07, 0x41, 0x6A, 0x82, 0x13, + 0x11, 0x43, 0x41, 0x62, 0x41, 0x6A, 0xC2, 0x13, + 0x11, 0x43, 0x41, 0x62, 0x70, 0x47, 0x10, 0xB5, + 0xFF, 0xF7, 0xEE, 0xFF, 0x10, 0xBD, 0x00, 0x00, + 0x03, 0x48, 0x01, 0x68, 0x49, 0x1C, 0x01, 0x60, + 0x02, 0x49, 0x01, 0x20, 0xC8, 0x60, 0x70, 0x47, + 0x08, 0x03, 0x00, 0x20, 0x00, 0x03, 0x00, 0x50, + 0x10, 0xB5, 0x02, 0xF0, 0x8F, 0xF8, 0x10, 0xBD, + 0x70, 0x47, 0x00, 0x00, 0x04, 0x49, 0x06, 0x22, + 0x00, 0x28, 0x08, 0x68, 0x01, 0xD0, 0x10, 0x43, + 0x00, 0xE0, 0x90, 0x43, 0x08, 0x60, 0x70, 0x47, + 0x00, 0x09, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, + 0x70, 0x47, 0x00, 0x00, 0x64, 0x20, 0x05, 0x49, + 0x02, 0xE0, 0x00, 0xBF, 0x40, 0x1E, 0xC0, 0xB2, + 0x4A, 0x68, 0x12, 0x06, 0x01, 0xD5, 0x00, 0x28, + 0xF7, 0xD1, 0x70, 0x47, 0x00, 0x06, 0x00, 0x50, + 0x00, 0xB5, 0x07, 0x48, 0x01, 0x69, 0x02, 0x29, + 0x06, 0xD1, 0x01, 0x61, 0x05, 0x49, 0x8A, 0x78, + 0x00, 0x2A, 0x02, 0xD0, 0x02, 0xF0, 0x28, 0xFB, + 0x00, 0xBD, 0x01, 0x20, 0xC8, 0x70, 0x00, 0xBD, + 0x00, 0x02, 0x00, 0x50, 0x5C, 0x04, 0x00, 0x20, + 0x04, 0x49, 0x00, 0x20, 0x08, 0x60, 0x01, 0x20, + 0x80, 0x07, 0x41, 0x68, 0x42, 0x14, 0x11, 0x43, + 0x41, 0x60, 0x70, 0x47, 0xD0, 0x00, 0x00, 0x20, + 0x30, 0xB5, 0x1E, 0x4B, 0x58, 0x68, 0x99, 0x68, + 0x00, 0x28, 0x01, 0xDA, 0xDA, 0x04, 0x5A, 0x60, + 0x1B, 0x4A, 0x15, 0x68, 0x01, 0x24, 0x05, 0x40, + 0x00, 0x2D, 0x02, 0xD1, 0x50, 0x68, 0x08, 0x42, + 0x0F, 0xD0, 0x18, 0x48, 0x04, 0x70, 0x00, 0xF0, + 0x6F, 0xF9, 0x14, 0x72, 0x16, 0x48, 0x00, 0x78, + 0x00, 0x28, 0x06, 0xD0, 0x18, 0x68, 0x01, 0x21, + 0x00, 0x09, 0x00, 0x01, 0x89, 0x02, 0x08, 0x43, + 0x18, 0x60, 0x58, 0x68, 0x80, 0x02, 0x02, 0xD5, + 0x01, 0x20, 0x40, 0x05, 0x58, 0x60, 0x58, 0x68, + 0x00, 0x04, 0x03, 0xD5, 0x01, 0x20, 0xC0, 0x03, + 0x58, 0x60, 0x94, 0x72, 0x58, 0x68, 0x80, 0x00, + 0x03, 0xD5, 0x01, 0x20, 0x40, 0x07, 0x58, 0x60, + 0xD4, 0x72, 0x58, 0x68, 0x40, 0x00, 0x03, 0xD5, + 0x01, 0x20, 0x80, 0x07, 0x58, 0x60, 0x94, 0x72, + 0x30, 0xBD, 0x00, 0x00, 0x00, 0x10, 0x00, 0x50, + 0x50, 0x02, 0x00, 0x20, 0x99, 0x01, 0x00, 0x20, + 0xCF, 0x00, 0x00, 0x20, 0x30, 0xB5, 0x07, 0x49, + 0x07, 0x4B, 0x0A, 0x68, 0x00, 0x20, 0x41, 0x00, + 0xCD, 0x18, 0x54, 0x5A, 0x2D, 0x88, 0x40, 0x1C, + 0x64, 0x1B, 0xC0, 0xB2, 0x54, 0x52, 0x30, 0x28, + 0xF5, 0xD3, 0x30, 0xBD, 0xF8, 0x02, 0x00, 0x20, + 0x00, 0x00, 0x01, 0x20, 0x10, 0xB5, 0x02, 0xF0, + 0x09, 0xF9, 0x10, 0xBD, 0x00, 0x00, 0x00, 0x00, + 0x70, 0xB5, 0x2B, 0x4B, 0x05, 0x20, 0x19, 0x7A, + 0x40, 0x04, 0x02, 0x29, 0x4C, 0xD1, 0x29, 0x4C, + 0x21, 0x78, 0x00, 0x29, 0x45, 0xD1, 0x28, 0x49, + 0x0A, 0x78, 0x00, 0x2A, 0x44, 0xD0, 0x00, 0x22, + 0x0A, 0x70, 0x27, 0x49, 0x25, 0x4A, 0x09, 0x78, + 0x26, 0x4D, 0x05, 0xE0, 0x16, 0x78, 0x81, 0x2E, + 0x0A, 0xD0, 0x00, 0x29, 0x08, 0xD1, 0x40, 0x1E, + 0x2E, 0x78, 0x00, 0x2E, 0x04, 0xD0, 0x00, 0x28, + 0x02, 0xD0, 0x1E, 0x7A, 0x02, 0x2E, 0xF1, 0xD0, + 0x18, 0x7A, 0x02, 0x28, 0x26, 0xD1, 0x10, 0x78, + 0x81, 0x28, 0x23, 0xD0, 0x00, 0x29, 0x21, 0xD1, + 0xFF, 0xF7, 0x44, 0xFF, 0x1A, 0x48, 0x40, 0x68, + 0x40, 0x01, 0x40, 0x0D, 0xBC, 0x28, 0x01, 0xD9, + 0xBC, 0x38, 0x80, 0xB2, 0x17, 0x49, 0x49, 0x68, + 0x09, 0x06, 0x0D, 0xD4, 0x16, 0x49, 0x09, 0x88, + 0x88, 0x42, 0x03, 0xD3, 0x15, 0x49, 0x09, 0x88, + 0x88, 0x42, 0x05, 0xD9, 0x01, 0x20, 0x80, 0x07, + 0x81, 0x68, 0x01, 0x22, 0x11, 0x43, 0x81, 0x60, + 0x00, 0x20, 0x00, 0xBF, 0x40, 0x1C, 0xC0, 0xB2, + 0x14, 0x28, 0xFA, 0xD3, 0x20, 0x78, 0x00, 0x28, + 0x02, 0xD0, 0x20, 0x78, 0x40, 0x1E, 0x20, 0x70, + 0xFF, 0xF7, 0x3E, 0xFF, 0x70, 0xBD, 0x00, 0x00, + 0x50, 0x02, 0x00, 0x20, 0xEC, 0x02, 0x00, 0x20, + 0x99, 0x01, 0x00, 0x20, 0x84, 0x04, 0x00, 0x20, + 0x72, 0x04, 0x00, 0x20, 0x90, 0x02, 0x00, 0x20, + 0x00, 0x11, 0x00, 0x50, 0x00, 0x06, 0x00, 0x50, + 0x60, 0x02, 0x00, 0x20, 0x62, 0x02, 0x00, 0x20, + 0x10, 0xB5, 0x02, 0xF0, 0xF1, 0xF9, 0x10, 0xBD, + 0x70, 0xB5, 0x01, 0x25, 0xAD, 0x07, 0xE8, 0x68, + 0x40, 0x08, 0x40, 0x00, 0xE8, 0x60, 0x1B, 0x48, + 0x41, 0x68, 0x01, 0x26, 0x49, 0x07, 0x00, 0x29, + 0x2A, 0xDA, 0x42, 0x68, 0x04, 0x21, 0x0A, 0x43, + 0x42, 0x60, 0x17, 0x48, 0x00, 0x68, 0x17, 0x4C, + 0x40, 0x05, 0x40, 0x0F, 0x20, 0x73, 0x20, 0x7B, + 0x06, 0x28, 0x00, 0xD1, 0x21, 0x73, 0x20, 0x7B, + 0x02, 0x28, 0x1D, 0xD0, 0x20, 0x7B, 0x06, 0x28, + 0x05, 0xD8, 0x20, 0x7B, 0x04, 0x28, 0x02, 0xD3, + 0x01, 0x20, 0x01, 0xF0, 0xA1, 0xF9, 0x20, 0x7B, + 0x0D, 0x49, 0x09, 0x78, 0x88, 0x42, 0x0B, 0xD0, + 0x0C, 0x4C, 0xA0, 0x79, 0x00, 0x28, 0x01, 0xD0, + 0x01, 0xF0, 0xF8, 0xF9, 0x0A, 0x48, 0x01, 0x78, + 0x31, 0x43, 0x01, 0x70, 0x81, 0x20, 0x20, 0x70, + 0xE8, 0x68, 0x30, 0x43, 0xE8, 0x60, 0x70, 0xBD, + 0x00, 0x20, 0xE6, 0xE7, 0x40, 0x00, 0x00, 0x50, + 0x00, 0x11, 0x00, 0x50, 0x50, 0x02, 0x00, 0x20, + 0xE5, 0x02, 0x00, 0x20, 0x84, 0x04, 0x00, 0x20, + 0xCE, 0x00, 0x00, 0x20, 0x10, 0xB5, 0x08, 0x4A, + 0x00, 0x21, 0x12, 0x68, 0x60, 0x32, 0x12, 0x78, + 0x52, 0x1C, 0xD4, 0xB2, 0x4A, 0x00, 0x83, 0x5E, + 0x49, 0x1C, 0x63, 0x43, 0x1B, 0x11, 0xC9, 0xB2, + 0x83, 0x52, 0x30, 0x29, 0xF6, 0xD3, 0x10, 0xBD, + 0xB8, 0x02, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x10, 0xB5, 0x14, 0x48, 0x80, 0x69, 0x40, 0x04, + 0x23, 0xD5, 0x13, 0x4C, 0xE0, 0x68, 0xA2, 0x68, + 0x02, 0x40, 0xE0, 0x68, 0xA1, 0x68, 0x88, 0x43, + 0xE0, 0x60, 0x10, 0x07, 0x03, 0xD5, 0x08, 0x20, + 0xE0, 0x60, 0x04, 0xF0, 0x3F, 0xFB, 0x50, 0x07, + 0x0E, 0xD5, 0x04, 0x20, 0xE0, 0x60, 0x04, 0xF0, + 0x39, 0xFB, 0x02, 0xF0, 0xFD, 0xFC, 0x00, 0x28, + 0x06, 0xD1, 0x08, 0x48, 0x00, 0x78, 0x00, 0x28, + 0x02, 0xD0, 0x01, 0x20, 0xFF, 0xF7, 0x68, 0xFE, + 0xFF, 0x20, 0xF3, 0x30, 0xE0, 0x60, 0x01, 0x20, + 0xE0, 0x60, 0x10, 0xBD, 0x00, 0x09, 0x00, 0x50, + 0x00, 0x05, 0x00, 0x50, 0x90, 0x02, 0x00, 0x20, + 0x00, 0xB5, 0x03, 0x46, 0x00, 0x20, 0xFF, 0xF7, + 0x5D, 0xFE, 0x04, 0x49, 0x01, 0x20, 0x09, 0x68, + 0x0A, 0x88, 0x9A, 0x43, 0x0A, 0x80, 0xFF, 0xF7, + 0x55, 0xFE, 0x00, 0xBD, 0x08, 0x00, 0x00, 0x20, + 0x01, 0x28, 0x05, 0xD0, 0x02, 0x28, 0x05, 0xD0, + 0x04, 0x28, 0x06, 0xD0, 0x00, 0x20, 0x70, 0x47, + 0x03, 0x48, 0x70, 0x47, 0x02, 0x48, 0xC0, 0x30, + 0x70, 0x47, 0x02, 0x48, 0x70, 0x47, 0x00, 0x00, + 0x78, 0x7C, 0x00, 0x00, 0xF8, 0x7D, 0x00, 0x00, + 0x03, 0x48, 0x02, 0x49, 0x41, 0x60, 0x03, 0x49, + 0x81, 0x60, 0x70, 0x47, 0x1F, 0x1F, 0x5F, 0x1F, + 0x00, 0x10, 0x00, 0x50, 0x1F, 0x1F, 0x1F, 0x1F, + 0xF8, 0xB5, 0x2B, 0x48, 0x80, 0x69, 0x40, 0x04, + 0x51, 0xD5, 0x2A, 0x4D, 0xA8, 0x6A, 0xE9, 0x68, + 0xAC, 0x68, 0x0C, 0x40, 0xE9, 0x68, 0xAA, 0x68, + 0x91, 0x43, 0xE9, 0x60, 0x26, 0x4E, 0xC0, 0x07, + 0x15, 0xD0, 0x68, 0x69, 0x89, 0x27, 0xC0, 0xB2, + 0xEF, 0x60, 0xAA, 0x6A, 0x01, 0x21, 0x0A, 0x43, + 0xAA, 0x62, 0x0B, 0x28, 0x03, 0xD2, 0x31, 0x70, + 0x01, 0xF0, 0x04, 0xF8, 0x03, 0xE0, 0x00, 0x21, + 0x31, 0x70, 0x00, 0xF0, 0xDB, 0xFF, 0xBC, 0x43, + 0x01, 0x20, 0xFF, 0xF7, 0x05, 0xFE, 0x1B, 0x48, + 0xA1, 0x04, 0x04, 0xD5, 0x29, 0x6A, 0x81, 0x43, + 0x29, 0x62, 0x41, 0x14, 0xE9, 0x60, 0xE1, 0x04, + 0x05, 0xD5, 0x29, 0x6A, 0x81, 0x43, 0x29, 0x62, + 0x01, 0x20, 0x00, 0x03, 0xE8, 0x60, 0xA0, 0x05, + 0x02, 0xD5, 0x01, 0x20, 0x40, 0x02, 0xE8, 0x60, + 0x20, 0x07, 0x09, 0xD5, 0x30, 0x78, 0x01, 0x28, + 0x02, 0xD1, 0x0A, 0x20, 0x00, 0xF0, 0xBA, 0xFF, + 0x64, 0x08, 0x64, 0x00, 0x09, 0x20, 0xE8, 0x60, + 0x60, 0x07, 0x07, 0xD5, 0x30, 0x78, 0x01, 0x28, + 0x02, 0xD1, 0x0A, 0x20, 0x00, 0xF0, 0xAE, 0xFF, + 0x05, 0x20, 0xE8, 0x60, 0xFF, 0x20, 0xF3, 0x30, + 0xE8, 0x60, 0x01, 0x20, 0xE8, 0x60, 0xF8, 0xBD, + 0x00, 0x09, 0x00, 0x50, 0x00, 0x06, 0x00, 0x50, + 0x9B, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00, 0x40, + 0x70, 0xB5, 0x41, 0x18, 0x49, 0x1E, 0x64, 0x24, + 0x09, 0x04, 0x0B, 0x4D, 0x01, 0x43, 0x69, 0x63, + 0xE8, 0x68, 0x81, 0x21, 0x09, 0x06, 0x08, 0x43, + 0xE8, 0x60, 0x02, 0xE0, 0x01, 0x20, 0xFF, 0xF7, + 0xCB, 0xFE, 0xE8, 0x68, 0xC0, 0x01, 0x04, 0xD5, + 0x20, 0x46, 0x64, 0x1E, 0xA4, 0xB2, 0x00, 0x28, + 0xF4, 0xD1, 0xA8, 0x6B, 0x70, 0xBD, 0x00, 0x00, + 0x40, 0x09, 0x00, 0x50, 0x04, 0x49, 0x29, 0x20, + 0xC8, 0x60, 0x04, 0x49, 0x35, 0x20, 0x08, 0x63, + 0x04, 0x20, 0x01, 0x07, 0x88, 0x60, 0x70, 0x47, + 0x40, 0x00, 0x00, 0x50, 0x80, 0x10, 0x00, 0x50, + 0x10, 0xB5, 0x00, 0xF0, 0x09, 0xF8, 0x10, 0xBD, + 0x02, 0x48, 0x01, 0x68, 0x49, 0x1C, 0x01, 0x60, + 0x70, 0x47, 0x00, 0x00, 0x80, 0x04, 0x00, 0x20, + 0x01, 0x20, 0x80, 0x07, 0x41, 0x68, 0x82, 0x14, + 0x11, 0x43, 0x41, 0x60, 0x70, 0x47, 0x00, 0x00, + 0x05, 0x20, 0x00, 0x07, 0x82, 0x69, 0x0C, 0x49, + 0x00, 0x2A, 0x07, 0xDA, 0x82, 0x69, 0xC3, 0x00, + 0x92, 0x00, 0x92, 0x08, 0xD2, 0x18, 0x82, 0x61, + 0x01, 0x22, 0x4A, 0x72, 0x82, 0x69, 0x52, 0x00, + 0x08, 0xD5, 0x82, 0x69, 0x01, 0x23, 0x92, 0x00, + 0x92, 0x08, 0x9B, 0x07, 0xD2, 0x18, 0x82, 0x61, + 0x00, 0x20, 0x48, 0x72, 0x70, 0x47, 0x00, 0x00, + 0x84, 0x04, 0x00, 0x20, 0xF0, 0xB5, 0x00, 0x20, + 0x84, 0x46, 0x91, 0x48, 0x87, 0xB0, 0x00, 0x68, + 0x90, 0x49, 0xB0, 0x30, 0x00, 0x90, 0x00, 0x20, + 0x08, 0x5E, 0x8F, 0x49, 0x01, 0x90, 0x00, 0x20, + 0x08, 0x5E, 0x02, 0x90, 0x0B, 0xE1, 0x34, 0x21, + 0x48, 0x43, 0x81, 0x19, 0x0C, 0x46, 0x20, 0x34, + 0x60, 0x7D, 0xFF, 0x28, 0x31, 0xD1, 0x00, 0x22, + 0x00, 0x20, 0x08, 0xE0, 0x34, 0x25, 0x45, 0x43, + 0xAD, 0x19, 0x20, 0x35, 0x6D, 0x7D, 0x95, 0x42, + 0x03, 0xD0, 0x40, 0x1C, 0xC0, 0xB2, 0x83, 0x42, + 0xF4, 0xD8, 0x83, 0x42, 0x01, 0xD1, 0x62, 0x75, + 0x03, 0xE0, 0x52, 0x1C, 0xD2, 0xB2, 0x02, 0x2A, + 0xEA, 0xD3, 0x0A, 0x25, 0x4D, 0x5F, 0x30, 0x20, + 0x7C, 0x4E, 0x42, 0x43, 0xAB, 0x01, 0x90, 0x19, + 0x83, 0x60, 0xC3, 0x60, 0xB5, 0x50, 0x0C, 0x22, + 0x45, 0x60, 0x8A, 0x5E, 0x93, 0x01, 0x83, 0x61, + 0x02, 0x61, 0xC3, 0x61, 0x42, 0x61, 0x0E, 0x22, + 0x8A, 0x5E, 0x93, 0x01, 0x83, 0x62, 0x02, 0x62, + 0xC3, 0x62, 0x42, 0x62, 0x60, 0x7D, 0xFF, 0x28, + 0x7E, 0xD0, 0x71, 0x4A, 0xC7, 0xB2, 0x12, 0x78, + 0xC8, 0x69, 0x00, 0x2A, 0x11, 0xD1, 0x00, 0x9A, + 0xD3, 0x79, 0x94, 0x79, 0x1A, 0x02, 0x22, 0x43, + 0x82, 0x42, 0x01, 0xD2, 0x02, 0x22, 0x09, 0xE0, + 0x00, 0x9A, 0x53, 0x7A, 0x14, 0x7A, 0x1A, 0x02, + 0x22, 0x43, 0x82, 0x42, 0x01, 0xD2, 0x03, 0x22, + 0x00, 0xE0, 0x04, 0x22, 0x65, 0x4D, 0x28, 0x78, + 0x82, 0x42, 0x28, 0xD0, 0x82, 0x42, 0x02, 0xD3, + 0x13, 0x1A, 0x02, 0x2B, 0x23, 0xDA, 0x62, 0x4C, + 0x23, 0x78, 0x00, 0x2B, 0x14, 0xD0, 0x61, 0x4E, + 0x33, 0x78, 0x9A, 0x42, 0x02, 0xD0, 0x00, 0x23, + 0x23, 0x70, 0x32, 0x70, 0x22, 0x78, 0x5D, 0x4B, + 0x52, 0x1C, 0xD2, 0xB2, 0x22, 0x70, 0x1B, 0x78, + 0x98, 0x42, 0x08, 0xD9, 0x01, 0x2A, 0x06, 0xD9, + 0x00, 0x20, 0x20, 0x70, 0x2B, 0x70, 0x0A, 0xE0, + 0x56, 0x4B, 0x1A, 0x70, 0xEE, 0xE7, 0x98, 0x42, + 0x05, 0xD2, 0x02, 0x2A, 0x03, 0xD9, 0x00, 0x22, + 0x22, 0x70, 0x40, 0x1C, 0x28, 0x70, 0x28, 0x78, + 0x02, 0x28, 0x6B, 0xD0, 0x03, 0x28, 0x6D, 0xD0, + 0x29, 0x22, 0x64, 0x23, 0x05, 0x24, 0x3E, 0x46, + 0x30, 0x20, 0x46, 0x43, 0x47, 0x48, 0x30, 0x18, + 0xC5, 0x68, 0x03, 0x95, 0x87, 0x68, 0x5D, 0x43, + 0x57, 0x43, 0xED, 0x1B, 0x20, 0x35, 0xAD, 0x11, + 0xAE, 0x46, 0x0A, 0x25, 0x4D, 0x5F, 0x41, 0x4F, + 0x04, 0x95, 0xBF, 0x59, 0xEF, 0x19, 0x45, 0x68, + 0x6D, 0x00, 0x7F, 0x19, 0x67, 0x43, 0xBF, 0x1C, + 0xBF, 0x10, 0x75, 0x46, 0x7D, 0x19, 0xAE, 0x46, + 0xC5, 0x69, 0x87, 0x69, 0x5D, 0x43, 0x57, 0x43, + 0xED, 0x1B, 0x20, 0x35, 0xAD, 0x11, 0x06, 0x95, + 0x0C, 0x25, 0x4D, 0x5F, 0x05, 0x95, 0x07, 0x69, + 0xEF, 0x19, 0x45, 0x69, 0x6D, 0x00, 0x7D, 0x19, + 0x65, 0x43, 0xAD, 0x1C, 0xAF, 0x10, 0x00, 0xE0, + 0x4D, 0xE0, 0x06, 0x9D, 0x7F, 0x19, 0xC5, 0x6A, + 0x5D, 0x43, 0x83, 0x6A, 0x53, 0x43, 0xEA, 0x1A, + 0x0E, 0x23, 0x20, 0x32, 0xCB, 0x5E, 0x95, 0x11, + 0x06, 0x93, 0x02, 0x6A, 0x9A, 0x18, 0x43, 0x6A, + 0x5B, 0x00, 0xD2, 0x18, 0x62, 0x43, 0x92, 0x1C, + 0x92, 0x10, 0x52, 0x19, 0x03, 0x9D, 0x85, 0x60, + 0x75, 0x46, 0xC5, 0x60, 0xC3, 0x69, 0xC7, 0x61, + 0x83, 0x61, 0xC3, 0x6A, 0x83, 0x62, 0xC2, 0x62, + 0x20, 0x4C, 0x43, 0x68, 0xA3, 0x51, 0x04, 0x9D, + 0x45, 0x60, 0x43, 0x69, 0x03, 0x61, 0x05, 0x9D, + 0x45, 0x61, 0x43, 0x6A, 0x03, 0x62, 0x06, 0x9B, + 0x43, 0x62, 0x75, 0x46, 0x01, 0x9C, 0xA8, 0x11, + 0xBB, 0x11, 0x92, 0x11, 0xA0, 0x42, 0x09, 0xDB, + 0x60, 0x1E, 0x0A, 0xE0, 0x1C, 0x22, 0x4D, 0x23, + 0x0F, 0x24, 0x94, 0xE7, 0x22, 0x22, 0x58, 0x23, + 0x0A, 0x24, 0x90, 0xE7, 0x00, 0x28, 0x00, 0xDA, + 0x00, 0x20, 0x02, 0x9C, 0xA3, 0x42, 0x01, 0xDB, + 0x63, 0x1E, 0x02, 0xE0, 0x00, 0x2B, 0x00, 0xDA, + 0x00, 0x23, 0x00, 0x2A, 0x00, 0xDA, 0x00, 0x22, + 0x48, 0x81, 0x8B, 0x81, 0xCA, 0x81, 0x60, 0x46, + 0x40, 0x1C, 0xC0, 0xB2, 0x84, 0x46, 0x0C, 0x4E, + 0x60, 0x46, 0x33, 0x78, 0x63, 0x45, 0x00, 0xD9, + 0xED, 0xE6, 0x07, 0xB0, 0xF0, 0xBD, 0x00, 0x00, + 0xB8, 0x02, 0x00, 0x20, 0x42, 0x00, 0x00, 0x20, + 0x44, 0x00, 0x00, 0x20, 0x38, 0x01, 0x00, 0x20, + 0x2A, 0x00, 0x00, 0x20, 0x4A, 0x00, 0x00, 0x20, + 0x49, 0x00, 0x00, 0x20, 0x4B, 0x00, 0x00, 0x20, + 0xA0, 0x04, 0x00, 0x20, 0xF0, 0xB5, 0x93, 0xB0, + 0x02, 0xF0, 0x62, 0xFD, 0x0F, 0x90, 0x8D, 0x48, + 0x00, 0x25, 0x41, 0x5F, 0x0F, 0x98, 0x81, 0x42, + 0x7D, 0xDD, 0x8B, 0x48, 0x30, 0x21, 0x00, 0x68, + 0x06, 0x7E, 0x40, 0x7E, 0x11, 0x90, 0x02, 0xA8, + 0xFF, 0xF7, 0x73, 0xFB, 0x87, 0x48, 0x00, 0x78, + 0x06, 0x28, 0x71, 0xD2, 0x01, 0x20, 0x10, 0x90, + 0x08, 0xA8, 0x0E, 0x90, 0x34, 0x20, 0x29, 0x46, + 0x41, 0x43, 0x83, 0x48, 0x00, 0x27, 0x0C, 0x18, + 0x27, 0x76, 0x10, 0x98, 0x00, 0x28, 0x04, 0xD0, + 0x7C, 0x48, 0x01, 0x88, 0x7F, 0x48, 0x01, 0x80, + 0x09, 0xE0, 0x01, 0xA9, 0x68, 0x46, 0x0F, 0x9A, + 0x04, 0xF0, 0x58, 0xF8, 0x00, 0x28, 0x57, 0xD0, + 0x10, 0x98, 0x00, 0x28, 0x0E, 0xD0, 0x7A, 0x48, + 0x31, 0x46, 0x00, 0x88, 0x10, 0x90, 0xFF, 0xF7, + 0x09, 0xFB, 0x69, 0x46, 0x08, 0x70, 0x10, 0x99, + 0x70, 0x43, 0x08, 0x1A, 0x69, 0x46, 0x08, 0x71, + 0x00, 0x20, 0x10, 0x90, 0x0F, 0x98, 0x02, 0xAA, + 0x83, 0xB2, 0x01, 0xA9, 0x68, 0x46, 0x02, 0xF0, + 0x65, 0xFD, 0x70, 0x49, 0x88, 0x42, 0x7D, 0xD0, + 0x6C, 0x4A, 0x00, 0x21, 0x51, 0x5E, 0x61, 0x86, + 0xE0, 0x81, 0x0A, 0x22, 0x50, 0x43, 0xFF, 0xF7, + 0x03, 0xFB, 0x34, 0x21, 0x08, 0x55, 0xFF, 0x20, + 0x20, 0x71, 0x00, 0x20, 0xE0, 0x61, 0x68, 0x46, + 0x01, 0x79, 0x02, 0xAA, 0x01, 0x20, 0x02, 0xF0, + 0xC7, 0xFC, 0x65, 0x49, 0x60, 0x81, 0x0A, 0x78, + 0x00, 0x2A, 0x0B, 0xD0, 0x63, 0x4A, 0x00, 0x23, + 0xD3, 0x5E, 0x63, 0x4A, 0x12, 0x78, 0x9B, 0x1A, + 0x98, 0x42, 0x03, 0xDA, 0x90, 0x42, 0x01, 0xDD, + 0x00, 0x22, 0x0A, 0x70, 0x6A, 0x46, 0x12, 0x79, + 0x00, 0x2A, 0x12, 0xD0, 0x73, 0x1E, 0x9A, 0x42, + 0x28, 0xD1, 0x5B, 0x4A, 0x00, 0x27, 0x12, 0x78, + 0x53, 0x00, 0xD2, 0x18, 0x93, 0x08, 0x57, 0x4A, + 0xD7, 0x5F, 0xFA, 0x1A, 0x90, 0x42, 0x14, 0xDD, + 0x01, 0x20, 0x08, 0x70, 0x14, 0xE0, 0x7E, 0xE0, + 0x87, 0xE0, 0x53, 0x4A, 0x12, 0x78, 0x53, 0x00, + 0xD2, 0x18, 0x92, 0x08, 0x90, 0x42, 0x02, 0xDA, + 0x01, 0x20, 0x08, 0x70, 0x02, 0xE0, 0x08, 0x78, + 0x00, 0x28, 0x0B, 0xD0, 0x02, 0xAA, 0x01, 0x21, + 0x04, 0xE0, 0x08, 0x78, 0x00, 0x28, 0x05, 0xD0, + 0x02, 0xAA, 0x00, 0x21, 0x01, 0x20, 0x02, 0xF0, + 0xEF, 0xFB, 0x60, 0x81, 0x68, 0x46, 0x01, 0x78, + 0x00, 0x20, 0x0E, 0x9A, 0x02, 0xF0, 0x80, 0xFC, + 0x44, 0x49, 0xA0, 0x81, 0x0A, 0x78, 0x00, 0x2A, + 0x0B, 0xD0, 0x43, 0x4A, 0x00, 0x23, 0xD3, 0x5E, + 0x42, 0x4A, 0x12, 0x78, 0x9B, 0x1A, 0x98, 0x42, + 0x03, 0xDA, 0x90, 0x42, 0x01, 0xDD, 0x00, 0x22, + 0x0A, 0x70, 0x6A, 0x46, 0x12, 0x78, 0x00, 0x2A, + 0x13, 0xD0, 0x11, 0x9B, 0x5B, 0x1E, 0x9A, 0x42, + 0x28, 0xD1, 0x3A, 0x4A, 0x12, 0x78, 0x53, 0x00, + 0xD2, 0x18, 0x93, 0x08, 0x36, 0x4A, 0x00, 0x27, + 0xD7, 0x5F, 0x00, 0xE0, 0x37, 0xE0, 0xFA, 0x1A, + 0x90, 0x42, 0x12, 0xDD, 0x01, 0x20, 0x08, 0x70, + 0x12, 0xE0, 0x32, 0x4A, 0x12, 0x78, 0x53, 0x00, + 0xD2, 0x18, 0x92, 0x08, 0x90, 0x42, 0x02, 0xDA, + 0x01, 0x20, 0x08, 0x70, 0x02, 0xE0, 0x08, 0x78, + 0x00, 0x28, 0x0B, 0xD0, 0x01, 0x21, 0x0E, 0x9A, + 0x04, 0xE0, 0x08, 0x78, 0x00, 0x28, 0x05, 0xD0, + 0x0E, 0x9A, 0x00, 0x21, 0x00, 0x20, 0x02, 0xF0, + 0xA7, 0xFB, 0xA0, 0x81, 0x07, 0x98, 0xA0, 0x82, + 0x0D, 0x98, 0xE0, 0x82, 0x68, 0x46, 0x00, 0x79, + 0x00, 0x28, 0x0A, 0xD0, 0x71, 0x1E, 0x88, 0x42, + 0x07, 0xD0, 0x68, 0x46, 0x00, 0x78, 0x00, 0x28, + 0x03, 0xD0, 0x11, 0x99, 0x49, 0x1E, 0x88, 0x42, + 0x01, 0xD1, 0x01, 0x20, 0x00, 0xE0, 0x00, 0x20, + 0x6D, 0x1C, 0x20, 0x76, 0xED, 0xB2, 0x03, 0x2D, + 0x00, 0xD2, 0x13, 0xE7, 0x09, 0xE0, 0x0E, 0x49, + 0x00, 0x20, 0x08, 0x70, 0x16, 0x48, 0x40, 0x88, + 0x0A, 0x28, 0x02, 0xD9, 0x00, 0x20, 0x13, 0xB0, + 0xF0, 0xBD, 0x08, 0x48, 0x00, 0x78, 0x03, 0x28, + 0x03, 0xD3, 0x07, 0x49, 0x00, 0x20, 0x08, 0x70, + 0x01, 0xE0, 0x05, 0x48, 0x05, 0x70, 0x01, 0x20, + 0xF1, 0xE7, 0x00, 0x00, 0x24, 0x00, 0x00, 0x20, + 0xB8, 0x02, 0x00, 0x20, 0x73, 0x04, 0x00, 0x20, + 0xA0, 0x04, 0x00, 0x20, 0x28, 0x00, 0x00, 0x20, + 0x32, 0x00, 0x00, 0x20, 0x00, 0x80, 0xFF, 0xFF, + 0xCC, 0x00, 0x00, 0x20, 0x42, 0x00, 0x00, 0x20, + 0x2E, 0x00, 0x00, 0x20, 0xCD, 0x00, 0x00, 0x20, + 0x44, 0x00, 0x00, 0x20, 0x2F, 0x00, 0x00, 0x20, + 0x84, 0x04, 0x00, 0x20, 0xF0, 0xB5, 0x8E, 0x46, + 0x02, 0x21, 0x4B, 0x00, 0xC3, 0x5E, 0x00, 0x2B, + 0x03, 0xDD, 0x49, 0x1E, 0x09, 0xB2, 0x00, 0x29, + 0xF7, 0xDA, 0x49, 0x1C, 0x00, 0x27, 0x0B, 0xB2, + 0x9C, 0x46, 0x3C, 0x46, 0x01, 0x25, 0x0E, 0xE0, + 0x5E, 0x00, 0x86, 0x5F, 0x00, 0x2E, 0x02, 0xDC, + 0x5B, 0x1E, 0x1B, 0xB2, 0x09, 0xE0, 0x31, 0x46, + 0x69, 0x43, 0x6D, 0x1C, 0xCF, 0x19, 0x34, 0x19, + 0x5B, 0x1C, 0x2D, 0xB2, 0x1B, 0xB2, 0x05, 0x2B, + 0xEE, 0xDB, 0x61, 0x46, 0x59, 0x1A, 0x49, 0x1C, + 0x66, 0x08, 0x63, 0x46, 0x00, 0x25, 0x51, 0x61, + 0x06, 0xE0, 0x59, 0x00, 0x41, 0x5E, 0x4D, 0x19, + 0xB5, 0x42, 0x03, 0xD8, 0x5B, 0x1C, 0x1B, 0xB2, + 0x05, 0x2B, 0xF6, 0xDB, 0x5E, 0x00, 0x81, 0x5F, + 0x69, 0x1A, 0x11, 0x60, 0x80, 0x5F, 0x50, 0x60, + 0x60, 0x1B, 0xD4, 0x60, 0x90, 0x60, 0x60, 0x46, + 0x18, 0x1A, 0x60, 0x43, 0x38, 0x1A, 0x10, 0x61, + 0x70, 0x46, 0x00, 0x78, 0x9B, 0x1E, 0xC0, 0x18, + 0x71, 0x46, 0x08, 0x70, 0x20, 0xB2, 0xF0, 0xBD, + 0xF8, 0xB5, 0x8D, 0x4E, 0x00, 0x27, 0x30, 0x78, + 0x3C, 0x46, 0x00, 0x28, 0x01, 0xD0, 0x03, 0xF0, + 0xE7, 0xF9, 0x00, 0x20, 0x05, 0x46, 0x89, 0x4A, + 0x0D, 0xE0, 0x34, 0x21, 0x41, 0x43, 0x89, 0x18, + 0x0B, 0x79, 0x1B, 0x06, 0x01, 0xD5, 0xCD, 0x71, + 0x03, 0xE0, 0xCB, 0x79, 0x5B, 0x1C, 0xCB, 0x71, + 0x01, 0x27, 0x40, 0x1C, 0xC0, 0xB2, 0x11, 0x78, + 0x81, 0x42, 0xEE, 0xD8, 0x7F, 0x49, 0x30, 0x78, + 0x09, 0x78, 0x88, 0x42, 0x01, 0xD1, 0x00, 0x2F, + 0x02, 0xD0, 0x03, 0xF0, 0x63, 0xFD, 0x0E, 0xE0, + 0xB0, 0x70, 0x00, 0x20, 0x34, 0x21, 0x41, 0x43, + 0x89, 0x19, 0x8D, 0x71, 0x40, 0x1C, 0xCD, 0x71, + 0xC0, 0xB2, 0x0D, 0x72, 0x03, 0x28, 0xF5, 0xD3, + 0x74, 0x48, 0x75, 0x70, 0x45, 0x70, 0x30, 0x78, + 0xB1, 0x78, 0x88, 0x42, 0x01, 0xD0, 0x03, 0xF0, + 0x61, 0xFB, 0x02, 0xF0, 0x89, 0xFE, 0x70, 0x49, + 0x70, 0x4A, 0x08, 0x88, 0x10, 0x80, 0x84, 0x46, + 0x00, 0x20, 0x08, 0x80, 0x6A, 0x49, 0x6E, 0x4D, + 0x0A, 0x78, 0x2B, 0xE0, 0x34, 0x23, 0x43, 0x43, + 0x5E, 0x18, 0x69, 0x4B, 0xF1, 0x69, 0x1F, 0x88, + 0xB9, 0x42, 0x00, 0xD9, 0x19, 0x80, 0x61, 0x46, + 0x00, 0x29, 0x0B, 0xD1, 0x29, 0x68, 0xB0, 0x31, + 0x4F, 0x7A, 0x09, 0x7A, 0x3F, 0x02, 0x0F, 0x43, + 0x19, 0x88, 0x8F, 0x42, 0x02, 0xD2, 0x63, 0x4B, + 0x01, 0x21, 0x19, 0x70, 0x29, 0x68, 0x20, 0x36, + 0xA0, 0x31, 0x33, 0x7D, 0x49, 0x78, 0x8B, 0x42, + 0x17, 0xD9, 0x5F, 0x4B, 0x01, 0x24, 0x19, 0x78, + 0x49, 0x1C, 0xC9, 0xB2, 0x19, 0x70, 0x02, 0x29, + 0x02, 0xD9, 0x5C, 0x4B, 0x01, 0x21, 0x19, 0x70, + 0x40, 0x1C, 0xC0, 0xB2, 0x52, 0x49, 0x82, 0x42, + 0xD0, 0xD8, 0x59, 0x49, 0x00, 0x2C, 0x08, 0x78, + 0x06, 0xD0, 0x64, 0x28, 0x08, 0xD2, 0x40, 0x1C, + 0x05, 0xE0, 0x00, 0x21, 0x52, 0x4B, 0xEE, 0xE7, + 0x00, 0x28, 0x01, 0xD0, 0x40, 0x1E, 0x08, 0x70, + 0x03, 0x20, 0x81, 0x1A, 0x34, 0x20, 0x48, 0x4E, + 0x41, 0x43, 0x42, 0x43, 0x90, 0x19, 0x00, 0x1D, + 0xFF, 0xF7, 0x6F, 0xF9, 0x00, 0x24, 0x45, 0x4F, + 0x0F, 0xE0, 0x38, 0x78, 0x34, 0x21, 0x00, 0x19, + 0x48, 0x43, 0xC1, 0x19, 0x30, 0x78, 0x34, 0x22, + 0x00, 0x19, 0x50, 0x43, 0x80, 0x19, 0x09, 0x1D, + 0x00, 0x1D, 0xFF, 0xF7, 0x45, 0xF9, 0x64, 0x1C, + 0xE4, 0xB2, 0x71, 0x78, 0xA1, 0x42, 0xEC, 0xD8, + 0x32, 0x78, 0x00, 0x20, 0x53, 0x18, 0x0E, 0xE0, + 0x34, 0x22, 0x42, 0x43, 0x91, 0x19, 0x06, 0x22, + 0x42, 0x43, 0xD2, 0x19, 0xA0, 0x32, 0x4C, 0x89, + 0x14, 0x80, 0x8C, 0x89, 0x54, 0x80, 0xC9, 0x89, + 0x40, 0x1C, 0x91, 0x80, 0xC0, 0xB2, 0x31, 0x46, + 0x83, 0x42, 0xED, 0xD8, 0x00, 0x20, 0x37, 0x4C, + 0x01, 0x22, 0x20, 0x80, 0x0C, 0xE0, 0x34, 0x26, + 0x46, 0x43, 0x76, 0x18, 0x36, 0x79, 0x77, 0x06, + 0x7F, 0x0E, 0x16, 0x46, 0xBE, 0x40, 0x27, 0x88, + 0x3E, 0x43, 0x40, 0x1C, 0x26, 0x80, 0xC0, 0xB2, + 0x83, 0x42, 0xF0, 0xD8, 0x23, 0x88, 0x00, 0x20, + 0x16, 0x46, 0x32, 0x46, 0x82, 0x40, 0x1A, 0x42, + 0x05, 0xD0, 0x40, 0x1C, 0xC0, 0xB2, 0x02, 0x28, + 0xF7, 0xD3, 0x00, 0x26, 0x37, 0xE0, 0x28, 0x4A, + 0x10, 0x70, 0xFA, 0xE7, 0x31, 0x46, 0x34, 0x22, + 0x51, 0x43, 0x0C, 0x18, 0x27, 0x46, 0x20, 0x37, + 0x38, 0x7C, 0x02, 0x28, 0x29, 0xD2, 0x0C, 0x23, + 0x0A, 0x22, 0x2E, 0x21, 0x2C, 0x20, 0xE3, 0x5E, + 0xA2, 0x5E, 0x61, 0x5E, 0x20, 0x5E, 0x04, 0xF0, + 0x95, 0xF9, 0x29, 0x68, 0xB0, 0x31, 0xCA, 0x7B, + 0x8B, 0x7B, 0x11, 0x02, 0x19, 0x43, 0x81, 0x42, + 0x14, 0xD2, 0x38, 0x7C, 0x2C, 0x22, 0x0A, 0x23, + 0xA2, 0x5E, 0x02, 0x21, 0xE3, 0x5E, 0x09, 0x1A, + 0x4A, 0x43, 0x43, 0x43, 0xD2, 0x18, 0x52, 0x10, + 0x62, 0x81, 0x2E, 0x22, 0xA2, 0x5E, 0x4A, 0x43, + 0x0C, 0x21, 0x61, 0x5E, 0x41, 0x43, 0x50, 0x18, + 0x40, 0x10, 0xA0, 0x81, 0x38, 0x7C, 0x40, 0x1C, + 0x38, 0x74, 0x76, 0x1C, 0xF6, 0xB2, 0x02, 0x48, + 0x01, 0x78, 0xB1, 0x42, 0xC6, 0xD8, 0xF8, 0xBD, + 0xA0, 0x04, 0x00, 0x20, 0x40, 0x05, 0x00, 0x20, + 0x34, 0x00, 0x00, 0x20, 0x36, 0x00, 0x00, 0x20, + 0xB8, 0x02, 0x00, 0x20, 0x48, 0x00, 0x00, 0x20, + 0x46, 0x00, 0x00, 0x20, 0x47, 0x00, 0x00, 0x20, + 0x2A, 0x00, 0x00, 0x20, 0x3E, 0x00, 0x00, 0x20, + 0x31, 0x00, 0x00, 0x20, 0xF1, 0xB5, 0x59, 0x48, + 0x8A, 0xB0, 0x00, 0x68, 0x08, 0x90, 0x1B, 0x30, + 0x57, 0x4B, 0x09, 0x90, 0x00, 0x20, 0x18, 0x5E, + 0xC1, 0x0F, 0x08, 0x18, 0xC0, 0x03, 0x01, 0x0C, + 0x08, 0x98, 0x00, 0x91, 0x90, 0x30, 0x41, 0x7B, + 0x02, 0x7B, 0x08, 0x02, 0x10, 0x43, 0x00, 0x99, + 0x07, 0x90, 0x88, 0x42, 0x00, 0xD9, 0x00, 0x90, + 0x4F, 0x48, 0x4E, 0x4A, 0x02, 0x80, 0x4F, 0x49, + 0xD0, 0x43, 0x08, 0x80, 0x4E, 0x49, 0x00, 0x23, + 0x0A, 0x80, 0x49, 0x49, 0x1E, 0x46, 0x08, 0x80, + 0x4C, 0x49, 0x0B, 0x70, 0x01, 0x21, 0xC9, 0x03, + 0x74, 0x00, 0x01, 0xA8, 0x01, 0x53, 0x48, 0x1E, + 0x03, 0xA9, 0x08, 0x53, 0x00, 0x20, 0x01, 0x46, + 0x35, 0x46, 0x1C, 0xE0, 0x09, 0x9A, 0x52, 0x5D, + 0x41, 0x2A, 0x16, 0xD0, 0x0A, 0x9A, 0x6B, 0x00, + 0xD2, 0x5E, 0x01, 0xAB, 0x49, 0x1C, 0x1F, 0x5F, + 0x80, 0x18, 0xC9, 0xB2, 0x97, 0x42, 0x00, 0xDA, + 0x1A, 0x53, 0x03, 0xAB, 0x1F, 0x5F, 0x97, 0x42, + 0x00, 0xDD, 0x1A, 0x53, 0x00, 0x9B, 0x9A, 0x42, + 0x03, 0xDD, 0x3A, 0x4B, 0x1A, 0x78, 0x52, 0x1C, + 0x1A, 0x70, 0x2D, 0x1D, 0xED, 0xB2, 0x30, 0x2D, + 0xE0, 0xD3, 0x00, 0x29, 0x02, 0xD0, 0xFF, 0xF7, + 0x4B, 0xF8, 0x00, 0xE0, 0x00, 0x20, 0x05, 0xA9, + 0x08, 0x53, 0x2D, 0x49, 0x01, 0xAD, 0x00, 0x22, + 0x28, 0x5F, 0x8A, 0x5E, 0x90, 0x42, 0x00, 0xDD, + 0x08, 0x80, 0x2D, 0x49, 0x03, 0xAF, 0x00, 0x22, + 0x38, 0x5F, 0x8A, 0x5E, 0x90, 0x42, 0x00, 0xDA, + 0x08, 0x80, 0x76, 0x1C, 0xF6, 0xB2, 0x04, 0x2E, + 0xB8, 0xD3, 0x07, 0x98, 0x26, 0x49, 0x43, 0x08, + 0x00, 0x20, 0x08, 0x5E, 0x26, 0x4A, 0x83, 0x42, + 0x06, 0xDA, 0x1F, 0x49, 0x00, 0x23, 0xCB, 0x5E, + 0x19, 0x1A, 0x07, 0x98, 0x81, 0x42, 0x06, 0xDB, + 0x08, 0x98, 0x80, 0x30, 0x81, 0x7B, 0x1F, 0x48, + 0x00, 0x78, 0x81, 0x42, 0x01, 0xD9, 0x01, 0x20, + 0x00, 0xE0, 0x00, 0x20, 0x10, 0x70, 0x00, 0x24, + 0x05, 0xAE, 0x60, 0x00, 0x31, 0x5E, 0x00, 0x91, + 0x3B, 0x5E, 0x2A, 0x5E, 0x21, 0x46, 0x0A, 0x98, + 0x00, 0xF0, 0x5C, 0xF9, 0x64, 0x1C, 0xE4, 0xB2, + 0x04, 0x2C, 0xF2, 0xD3, 0x0D, 0x48, 0x00, 0x68, + 0x90, 0x30, 0x41, 0x7B, 0x02, 0x7B, 0x08, 0x02, + 0x10, 0x43, 0x0E, 0x49, 0x00, 0x22, 0x8A, 0x5E, + 0x82, 0x42, 0x0D, 0xDA, 0x0F, 0x48, 0x41, 0x88, + 0x0A, 0x29, 0x09, 0xD9, 0x40, 0x88, 0x0B, 0x28, + 0x03, 0xD1, 0x80, 0x21, 0x0C, 0x48, 0xFF, 0xF7, + 0x20, 0xF8, 0x00, 0x20, 0x0B, 0xB0, 0xF0, 0xBD, + 0x01, 0x20, 0xFB, 0xE7, 0xB8, 0x02, 0x00, 0x20, + 0x7C, 0x04, 0x00, 0x20, 0xFF, 0x7F, 0x00, 0x00, + 0x76, 0x04, 0x00, 0x20, 0x78, 0x04, 0x00, 0x20, + 0x7A, 0x04, 0x00, 0x20, 0x8F, 0x02, 0x00, 0x20, + 0x9A, 0x01, 0x00, 0x20, 0x84, 0x04, 0x00, 0x20, + 0xF4, 0x05, 0x00, 0x20, 0xF0, 0xB5, 0x00, 0x20, + 0x41, 0x1F, 0x8C, 0x46, 0x1E, 0x49, 0x05, 0x24, + 0x09, 0x78, 0xC9, 0x07, 0x36, 0xD1, 0x1D, 0x49, + 0x09, 0x68, 0x90, 0x31, 0x4A, 0x7B, 0x0B, 0x7B, + 0x11, 0x02, 0x1B, 0x4A, 0x19, 0x43, 0x13, 0x5E, + 0x99, 0x42, 0x06, 0xDA, 0x19, 0x49, 0x09, 0x88, + 0x00, 0x29, 0x01, 0xD0, 0x0A, 0x24, 0x00, 0xE0, + 0x5A, 0x24, 0x17, 0x4F, 0x17, 0x4D, 0x00, 0x26, + 0x17, 0x4A, 0x41, 0x00, 0x52, 0x5E, 0xC9, 0x19, + 0x00, 0x23, 0xCB, 0x5E, 0x9A, 0x42, 0x02, 0xDD, + 0x2A, 0x5C, 0x52, 0x1C, 0x03, 0xE0, 0x9A, 0x42, + 0x03, 0xDA, 0x2A, 0x5C, 0x52, 0x1E, 0x2A, 0x54, + 0x00, 0xE0, 0x2E, 0x54, 0x2A, 0x56, 0xA2, 0x42, + 0x02, 0xDB, 0x0A, 0x88, 0x52, 0x1C, 0x03, 0xE0, + 0x62, 0x45, 0x03, 0xDC, 0x0A, 0x88, 0x52, 0x1E, + 0x0A, 0x80, 0x2E, 0x54, 0x40, 0x1C, 0xC0, 0xB2, + 0x30, 0x28, 0xDD, 0xD3, 0xF0, 0xBD, 0x00, 0x00, + 0xEB, 0x02, 0x00, 0x20, 0xB8, 0x02, 0x00, 0x20, + 0x78, 0x04, 0x00, 0x20, 0xF0, 0x02, 0x00, 0x20, + 0x00, 0x00, 0x01, 0x20, 0x0C, 0x03, 0x00, 0x20, + 0xD4, 0x00, 0x00, 0x20, 0xFE, 0xB5, 0x04, 0x46, + 0x37, 0x48, 0x00, 0x78, 0x00, 0x28, 0x46, 0xD0, + 0x1E, 0x20, 0x00, 0x90, 0x40, 0x42, 0x01, 0x90, + 0x00, 0x29, 0x12, 0xD0, 0x33, 0x48, 0x00, 0x78, + 0x15, 0x28, 0x40, 0xD0, 0x32, 0x4B, 0x00, 0x20, + 0x41, 0x00, 0x5A, 0x5A, 0x40, 0x1C, 0xD5, 0x00, + 0xAA, 0x1A, 0x65, 0x5A, 0xC0, 0xB2, 0x52, 0x19, + 0x12, 0xB2, 0xD2, 0x10, 0x5A, 0x52, 0x30, 0x28, + 0xF2, 0xD3, 0x00, 0x25, 0x00, 0x21, 0x0A, 0x46, + 0x08, 0x46, 0x2A, 0x4E, 0x2B, 0x18, 0x36, 0x68, + 0x9E, 0x19, 0xF6, 0x7E, 0x41, 0x2E, 0x19, 0xD0, + 0x5F, 0x00, 0x25, 0x4B, 0xE6, 0x5F, 0xDB, 0x5F, + 0xB4, 0x46, 0xF3, 0x1A, 0xDE, 0x0F, 0xF3, 0x18, + 0x5B, 0x10, 0x00, 0x9E, 0x1B, 0xB2, 0xB3, 0x42, + 0x02, 0xDA, 0x66, 0x46, 0xF6, 0x1A, 0xE6, 0x53, + 0x00, 0x9E, 0xB3, 0x42, 0x04, 0xDA, 0x01, 0x9E, + 0xB3, 0x42, 0x01, 0xDD, 0xD2, 0x18, 0x12, 0xB2, + 0x49, 0x1C, 0xC9, 0xB2, 0x40, 0x1C, 0xC0, 0xB2, + 0x03, 0x28, 0xDA, 0xD3, 0x00, 0x29, 0x21, 0xD0, + 0x01, 0x29, 0x0A, 0xD0, 0x0A, 0xE0, 0x0F, 0x20, + 0x00, 0x90, 0x40, 0x42, 0xB7, 0xE7, 0x60, 0x22, + 0x21, 0x46, 0x11, 0x48, 0xFE, 0xF7, 0x48, 0xFF, + 0xC7, 0xE7, 0x02, 0x21, 0x10, 0x46, 0xFE, 0xF7, + 0x2F, 0xFF, 0x02, 0xB2, 0x0D, 0x4B, 0x00, 0x20, + 0x1E, 0x68, 0x29, 0x18, 0x8E, 0x19, 0xF6, 0x7E, + 0x41, 0x2E, 0x03, 0xD0, 0x49, 0x00, 0x66, 0x5A, + 0xB6, 0x1A, 0x66, 0x52, 0x40, 0x1C, 0xC0, 0xB2, + 0x03, 0x28, 0xF1, 0xD3, 0x2D, 0x1D, 0xED, 0xB2, + 0x30, 0x2D, 0xAF, 0xD3, 0xFE, 0xBD, 0x00, 0x00, + 0x85, 0x02, 0x00, 0x20, 0x70, 0x04, 0x00, 0x20, + 0x3C, 0x03, 0x00, 0x20, 0xB8, 0x02, 0x00, 0x20, + 0x10, 0xB5, 0x21, 0x49, 0x1F, 0x48, 0xC8, 0x61, + 0x20, 0x48, 0x81, 0x68, 0x01, 0x22, 0x52, 0x02, + 0x11, 0x43, 0x81, 0x60, 0x1E, 0x4C, 0x20, 0x68, + 0x30, 0x21, 0x88, 0x43, 0x20, 0x60, 0x1D, 0x48, + 0x00, 0x78, 0x00, 0x28, 0x11, 0xD0, 0x1C, 0xA0, + 0x03, 0xF0, 0xA4, 0xFA, 0x1C, 0x48, 0x01, 0x68, + 0x4A, 0x06, 0x90, 0x21, 0x00, 0x2A, 0x02, 0x68, + 0x02, 0xDA, 0x0A, 0x43, 0x02, 0x60, 0x10, 0xBD, + 0x8A, 0x43, 0x02, 0x60, 0x17, 0x49, 0x41, 0x61, + 0x10, 0xBD, 0x17, 0xA0, 0x03, 0xF0, 0x92, 0xFA, + 0x05, 0x21, 0x17, 0x48, 0x09, 0x07, 0x48, 0x62, + 0x80, 0x07, 0x41, 0x6A, 0x82, 0x13, 0x11, 0x43, + 0x41, 0x62, 0x14, 0x49, 0x0A, 0x68, 0x03, 0x12, + 0x1A, 0x43, 0x0A, 0x60, 0xE2, 0x68, 0x09, 0x68, + 0x0A, 0x43, 0xE2, 0x60, 0x01, 0x68, 0x02, 0x14, + 0x11, 0x43, 0x01, 0x60, 0x0E, 0x49, 0x40, 0x14, + 0x08, 0x60, 0x10, 0xBD, 0x0B, 0x0B, 0x0B, 0x0B, + 0x40, 0x00, 0x00, 0x50, 0x00, 0x06, 0x00, 0x50, + 0x00, 0x10, 0x00, 0x50, 0xCF, 0x00, 0x00, 0x20, + 0x41, 0x53, 0x49, 0x0D, 0x0A, 0x00, 0x00, 0x00, + 0x00, 0x11, 0x00, 0x50, 0x16, 0x00, 0x03, 0x00, + 0x41, 0x53, 0x54, 0x0D, 0x0A, 0x00, 0x00, 0x00, + 0xF1, 0x2A, 0x02, 0x00, 0x50, 0x02, 0x00, 0x20, + 0x00, 0xE1, 0x00, 0xE0, 0xFF, 0xB5, 0x04, 0x46, + 0x55, 0x48, 0x1D, 0x46, 0x02, 0x68, 0x00, 0x21, + 0x17, 0x46, 0x1B, 0x32, 0x94, 0x46, 0x3A, 0x46, + 0x80, 0x32, 0x53, 0x7A, 0x16, 0x7A, 0x1A, 0x06, + 0x12, 0x14, 0x03, 0x23, 0x32, 0x43, 0xDB, 0x03, + 0x5A, 0x43, 0x12, 0x14, 0x96, 0x46, 0x4D, 0x4A, + 0x83, 0xB0, 0x16, 0x78, 0x3A, 0x46, 0x90, 0x32, + 0x57, 0x7B, 0x12, 0x7B, 0x3F, 0x02, 0x3A, 0x43, + 0x08, 0x46, 0x00, 0x2E, 0x03, 0xD1, 0x48, 0x4B, + 0x1B, 0x78, 0x14, 0x2B, 0x01, 0xD2, 0x53, 0x08, + 0x00, 0xE0, 0x93, 0x08, 0x00, 0x93, 0x45, 0x4B, + 0x76, 0x46, 0x1E, 0x80, 0x0C, 0x9B, 0x00, 0x2B, + 0x07, 0xDA, 0x43, 0x4E, 0x00, 0x27, 0xF7, 0x5F, + 0x00, 0x9E, 0xB7, 0x42, 0x01, 0xDA, 0x53, 0x42, + 0x01, 0xE0, 0x53, 0x08, 0x5B, 0x42, 0x0C, 0x9F, + 0x1B, 0xB2, 0x05, 0x9E, 0x01, 0x93, 0xF6, 0x1B, + 0x3B, 0x46, 0xEF, 0x1B, 0x00, 0x2E, 0x00, 0xDA, + 0x76, 0x42, 0x00, 0x2F, 0x00, 0xDA, 0x7F, 0x42, + 0xBE, 0x42, 0x01, 0xDA, 0x00, 0x2B, 0x15, 0xDD, + 0x00, 0x9B, 0x0C, 0x9E, 0xDF, 0x00, 0xF3, 0x1B, + 0xAB, 0x42, 0x06, 0xDD, 0x00, 0x2D, 0x04, 0xDA, + 0x05, 0x9B, 0x5B, 0x19, 0xD2, 0x18, 0x52, 0x10, + 0x01, 0xE0, 0x00, 0x9A, 0xAA, 0x18, 0x13, 0xB2, + 0x72, 0x46, 0x73, 0x45, 0x00, 0xDD, 0x13, 0x46, + 0x04, 0x9A, 0x19, 0xE0, 0x05, 0x9B, 0x9A, 0x1A, + 0x13, 0xB2, 0x72, 0x46, 0x52, 0x42, 0x93, 0x42, + 0x00, 0xDA, 0x13, 0xB2, 0x04, 0x9A, 0x1F, 0xE0, + 0x66, 0x46, 0xB6, 0x5C, 0x41, 0x2E, 0x09, 0xD0, + 0x56, 0x00, 0xA6, 0x5F, 0x9E, 0x42, 0x05, 0xDA, + 0x01, 0x9F, 0xBE, 0x42, 0x02, 0xDD, 0x30, 0x18, + 0x49, 0x1C, 0xC9, 0xB2, 0x12, 0x1D, 0xD2, 0xB2, + 0x30, 0x2A, 0xED, 0xD3, 0x0E, 0xE0, 0x66, 0x46, + 0xB6, 0x5C, 0x41, 0x2E, 0x06, 0xD0, 0x56, 0x00, + 0xA6, 0x5F, 0x9E, 0x42, 0x02, 0xDD, 0x30, 0x18, + 0x49, 0x1C, 0xC9, 0xB2, 0x12, 0x1D, 0xD2, 0xB2, + 0x30, 0x2A, 0xF0, 0xD3, 0x00, 0x29, 0x02, 0xD0, + 0xFE, 0xF7, 0x22, 0xFE, 0x01, 0x46, 0x04, 0x98, + 0x05, 0xE0, 0x42, 0x00, 0xA3, 0x5A, 0x5B, 0x1A, + 0x00, 0x1D, 0xA3, 0x52, 0xC0, 0xB2, 0x30, 0x28, + 0xF7, 0xD3, 0x0E, 0x4A, 0x00, 0x23, 0x68, 0x1A, + 0xD3, 0x5E, 0x00, 0xB2, 0x98, 0x42, 0x00, 0xDA, + 0x10, 0x80, 0x05, 0x98, 0x00, 0x22, 0x40, 0x1A, + 0x09, 0x49, 0x00, 0xB2, 0x8A, 0x5E, 0x90, 0x42, + 0x00, 0xDD, 0x08, 0x80, 0x07, 0xB0, 0xF0, 0xBD, + 0xB8, 0x02, 0x00, 0x20, 0x9A, 0x01, 0x00, 0x20, + 0xEA, 0x02, 0x00, 0x20, 0xEE, 0x02, 0x00, 0x20, + 0x7C, 0x04, 0x00, 0x20, 0x76, 0x04, 0x00, 0x20, + 0x78, 0x04, 0x00, 0x20, 0xF8, 0xB5, 0x06, 0x46, + 0x0D, 0x46, 0x30, 0x23, 0xFF, 0xF7, 0x80, 0xF8, + 0x04, 0x00, 0x28, 0xD0, 0x19, 0x4F, 0x00, 0x2A, + 0x21, 0xD0, 0x00, 0x20, 0xFE, 0xF7, 0xCA, 0xFE, + 0x02, 0xE0, 0x98, 0x00, 0x29, 0x58, 0x21, 0x50, + 0x18, 0x46, 0x5B, 0x1E, 0xDB, 0xB2, 0x00, 0x28, + 0xF7, 0xD1, 0x38, 0x68, 0x01, 0x88, 0x31, 0x43, + 0x01, 0x80, 0x11, 0x49, 0x09, 0x68, 0xCA, 0x79, + 0x8B, 0x79, 0x11, 0x02, 0x19, 0x43, 0x41, 0x80, + 0xC1, 0x21, 0x89, 0x00, 0x0D, 0x48, 0xFF, 0xF7, + 0xDF, 0xF8, 0x0D, 0x49, 0xC8, 0x63, 0x01, 0x20, + 0xFE, 0xF7, 0xAC, 0xFE, 0x0C, 0xE0, 0x38, 0x68, + 0x00, 0x88, 0x30, 0x40, 0x03, 0xD1, 0xF8, 0xBD, + 0x98, 0x00, 0x21, 0x58, 0x29, 0x50, 0x18, 0x46, + 0x5B, 0x1E, 0xDB, 0xB2, 0x00, 0x28, 0xF7, 0xD1, + 0x01, 0x20, 0xF8, 0xBD, 0x08, 0x00, 0x00, 0x20, + 0xB8, 0x02, 0x00, 0x20, 0x78, 0x7C, 0x00, 0x00, + 0x40, 0x7F, 0x00, 0x00, 0x70, 0xB5, 0x17, 0x48, + 0x5A, 0x25, 0x01, 0x78, 0x16, 0x48, 0x00, 0x29, + 0x25, 0xD0, 0x09, 0x21, 0x01, 0x70, 0x15, 0x4C, + 0x01, 0x21, 0xE0, 0x89, 0x89, 0x02, 0x88, 0x42, + 0x01, 0xD0, 0x03, 0x20, 0x60, 0x71, 0x11, 0x48, + 0x30, 0x21, 0x28, 0x30, 0xFE, 0xF7, 0xC9, 0xFD, + 0x00, 0x20, 0x60, 0x72, 0xA0, 0x71, 0x60, 0x62, + 0x16, 0x21, 0x21, 0x72, 0x0C, 0x49, 0x20, 0x71, + 0x08, 0x70, 0xA5, 0x81, 0x60, 0x81, 0x60, 0x8A, + 0x09, 0x21, 0x09, 0x03, 0x08, 0x43, 0x60, 0x82, + 0x03, 0x20, 0x40, 0x02, 0xE0, 0x61, 0x0D, 0x20, + 0xC0, 0x01, 0x20, 0x62, 0x70, 0xBD, 0x05, 0x70, + 0xD9, 0xE7, 0x00, 0x00, 0x85, 0x02, 0x00, 0x20, + 0xB5, 0x02, 0x00, 0x20, 0xE4, 0x02, 0x00, 0x20, + 0x87, 0x02, 0x00, 0x20, 0x0E, 0x48, 0x03, 0x21, + 0x41, 0x71, 0x0E, 0x49, 0x41, 0x61, 0x0D, 0x49, + 0x60, 0x31, 0x81, 0x61, 0x01, 0x21, 0x01, 0x70, + 0x07, 0x22, 0x42, 0x70, 0x0A, 0x4B, 0x05, 0x22, + 0x1A, 0x70, 0x0A, 0x4B, 0x1A, 0x70, 0x0A, 0x4B, + 0x55, 0x22, 0xDA, 0x70, 0x04, 0x22, 0x02, 0x82, + 0x00, 0x22, 0xC2, 0x70, 0x09, 0x22, 0x12, 0x03, + 0x42, 0x82, 0x81, 0x70, 0x70, 0x47, 0x00, 0x00, + 0xE4, 0x02, 0x00, 0x20, 0x00, 0x00, 0x04, 0x20, + 0x85, 0x02, 0x00, 0x20, 0x71, 0x04, 0x00, 0x20, + 0x90, 0x02, 0x00, 0x20, 0x04, 0x22, 0x0F, 0x49, + 0x0C, 0x28, 0x10, 0xD0, 0x8B, 0x05, 0x0D, 0x28, + 0x08, 0x6A, 0x10, 0xD0, 0x18, 0x43, 0x08, 0x62, + 0x88, 0x6A, 0x10, 0x43, 0x88, 0x62, 0x0A, 0x4A, + 0x01, 0x20, 0x10, 0x70, 0xC8, 0x68, 0xC8, 0x60, + 0x88, 0x6A, 0x88, 0x62, 0x70, 0x47, 0x08, 0x6A, + 0x40, 0x00, 0x40, 0x08, 0x00, 0xE0, 0x18, 0x43, + 0x08, 0x62, 0x88, 0x6A, 0x90, 0x43, 0x88, 0x62, + 0xF0, 0xE7, 0x00, 0x00, 0x00, 0x06, 0x00, 0x50, + 0x9B, 0x01, 0x00, 0x20, 0x00, 0xB5, 0x08, 0x49, + 0x0A, 0x28, 0x05, 0xD0, 0x07, 0x48, 0x00, 0x0C, + 0x48, 0x63, 0x07, 0x48, 0x08, 0x63, 0x00, 0xBD, + 0x06, 0x48, 0x00, 0x68, 0x08, 0x62, 0x0D, 0x20, + 0xFF, 0xF7, 0xCC, 0xFF, 0x00, 0xBD, 0x00, 0x00, + 0x00, 0x06, 0x00, 0x50, 0xBC, 0x02, 0x00, 0x20, + 0xCC, 0x02, 0x00, 0x20, 0xC0, 0x02, 0x00, 0x20, + 0x10, 0xB5, 0x1B, 0x49, 0x00, 0x20, 0x03, 0x00, + 0xFE, 0xF7, 0xA0, 0xFD, 0x0C, 0x07, 0x0A, 0x0E, + 0x26, 0x26, 0x11, 0x14, 0x17, 0x1A, 0x1D, 0x20, + 0x23, 0x26, 0x16, 0x4A, 0x0A, 0x80, 0x1E, 0xE0, + 0x14, 0x4A, 0x12, 0x1D, 0x4A, 0x80, 0x1A, 0xE0, + 0x13, 0x4A, 0x8A, 0x80, 0x17, 0xE0, 0x13, 0x4A, + 0x4A, 0x81, 0x14, 0xE0, 0x12, 0x4A, 0x8A, 0x81, + 0x11, 0xE0, 0x12, 0x4A, 0xCA, 0x81, 0x0E, 0xE0, + 0x11, 0x4A, 0x0A, 0x82, 0x0B, 0xE0, 0x11, 0x4A, + 0x4A, 0x82, 0x08, 0xE0, 0x10, 0x4A, 0x8A, 0x82, + 0x05, 0xE0, 0x10, 0x4A, 0xCA, 0x82, 0x02, 0xE0, + 0x0F, 0x4A, 0x43, 0x00, 0xCA, 0x52, 0x40, 0x1C, + 0x80, 0xB2, 0x0C, 0x28, 0xCF, 0xD3, 0x0D, 0xA0, + 0x03, 0xF0, 0x88, 0xF8, 0x10, 0xBD, 0x00, 0x00, + 0xCC, 0x02, 0x00, 0x20, 0x90, 0x02, 0x00, 0x20, + 0x88, 0x02, 0x00, 0x20, 0x34, 0x01, 0x00, 0x20, + 0xE0, 0x06, 0x00, 0x20, 0xE4, 0x06, 0x00, 0x20, + 0xD8, 0x06, 0x00, 0x20, 0xC0, 0x02, 0x00, 0x20, + 0xBC, 0x02, 0x00, 0x20, 0x9C, 0x01, 0x00, 0x20, + 0xC4, 0x02, 0x00, 0x20, 0x49, 0x32, 0x43, 0x20, + 0x4F, 0x4B, 0x0D, 0x0A, 0x00, 0x00, 0x00, 0x00, + 0x70, 0xB5, 0x01, 0x25, 0xCA, 0x07, 0x0A, 0xD0, + 0x00, 0x20, 0x70, 0xBD, 0x93, 0x00, 0xC4, 0x58, + 0x66, 0x1C, 0x02, 0xD0, 0x1B, 0x18, 0x5B, 0x68, + 0x23, 0x60, 0x92, 0x1C, 0x92, 0xB2, 0x8A, 0x42, + 0xF4, 0xD3, 0x28, 0x46, 0x70, 0xBD, 0x00, 0x00, + 0x10, 0xB5, 0x1D, 0x49, 0x0A, 0x68, 0x1C, 0x48, + 0x40, 0x30, 0x02, 0x61, 0x4A, 0x68, 0x42, 0x61, + 0x8A, 0x68, 0x82, 0x61, 0xC9, 0x68, 0xC1, 0x61, + 0x82, 0x69, 0xF0, 0x21, 0x8A, 0x43, 0x82, 0x61, + 0x82, 0x69, 0x0A, 0x43, 0x82, 0x61, 0x41, 0x69, + 0x49, 0x04, 0x04, 0xD5, 0x41, 0x69, 0x01, 0x22, + 0x52, 0x03, 0x89, 0x1A, 0x41, 0x61, 0x10, 0x48, + 0x40, 0x38, 0x01, 0x68, 0x01, 0x22, 0x12, 0x06, + 0x11, 0x43, 0x01, 0x60, 0x30, 0x21, 0x0D, 0x48, + 0xFF, 0xF7, 0xC6, 0xFF, 0x0B, 0x48, 0x40, 0x21, + 0xC0, 0x30, 0xFF, 0xF7, 0xC1, 0xFF, 0x0B, 0x20, + 0xFE, 0xF7, 0xD4, 0xFC, 0x03, 0x20, 0xFE, 0xF7, + 0xD1, 0xFC, 0x00, 0x20, 0xFE, 0xF7, 0xCE, 0xFC, + 0x05, 0x20, 0xFE, 0xF7, 0xCB, 0xFC, 0x09, 0x20, + 0xFE, 0xF7, 0xC8, 0xFC, 0x01, 0x20, 0x10, 0xBD, + 0x40, 0x14, 0x00, 0x50, 0x98, 0x5A, 0x00, 0x00, + 0x0E, 0x4A, 0x00, 0x21, 0x11, 0x60, 0x0E, 0x4A, + 0x20, 0x21, 0x11, 0x60, 0x5A, 0x28, 0x13, 0xD0, + 0x0C, 0x48, 0x01, 0x23, 0x00, 0x05, 0x00, 0x0D, + 0x5B, 0x03, 0xC0, 0x18, 0x05, 0x22, 0x12, 0x07, + 0x10, 0x62, 0x90, 0x00, 0xC2, 0x68, 0x0A, 0x43, + 0xC2, 0x60, 0x05, 0x4A, 0x80, 0x3A, 0x11, 0x60, + 0x41, 0x68, 0x19, 0x43, 0x41, 0x60, 0x70, 0x47, + 0xFF, 0x20, 0xEA, 0xE7, 0xD0, 0x00, 0x00, 0x20, + 0x80, 0xE1, 0x00, 0xE0, 0x24, 0x09, 0x00, 0x00, + 0x10, 0xB5, 0x17, 0x4A, 0x11, 0x78, 0x81, 0x42, + 0x29, 0xD0, 0x16, 0x49, 0x0B, 0x68, 0x01, 0x24, + 0xA4, 0x02, 0x23, 0x43, 0x0B, 0x60, 0x10, 0x70, + 0x05, 0x22, 0x40, 0x24, 0x12, 0x49, 0x12, 0x07, + 0x80, 0x23, 0x00, 0x28, 0x09, 0xD0, 0x08, 0x68, + 0x40, 0x06, 0x0C, 0xD4, 0x08, 0x68, 0x20, 0x43, + 0x08, 0x60, 0xD0, 0x68, 0x98, 0x43, 0xD0, 0x60, + 0x05, 0xE0, 0xD0, 0x68, 0x18, 0x43, 0xD0, 0x60, + 0x08, 0x68, 0xA0, 0x43, 0x08, 0x60, 0x08, 0x68, + 0x18, 0x43, 0x08, 0x60, 0xD0, 0x68, 0x01, 0x21, + 0xC9, 0x03, 0x08, 0x43, 0xD0, 0x60, 0xE1, 0x20, + 0x00, 0x02, 0x01, 0xF0, 0x61, 0xF8, 0x10, 0xBD, + 0x04, 0x00, 0x00, 0x20, 0x00, 0x10, 0x00, 0x50, + 0x00, 0x11, 0x00, 0x50, 0x10, 0xB5, 0x10, 0x4C, + 0xA0, 0x79, 0x00, 0x28, 0x18, 0xD1, 0x00, 0xF0, + 0xA5, 0xFF, 0x00, 0xF0, 0xA7, 0xFE, 0xFF, 0xF7, + 0xF7, 0xFC, 0x0C, 0x48, 0x01, 0x78, 0x0C, 0x48, + 0x00, 0x29, 0x02, 0xD0, 0x09, 0x21, 0x09, 0x02, + 0x00, 0xE0, 0x0A, 0x49, 0x01, 0x60, 0x01, 0x20, + 0xA0, 0x71, 0xE0, 0x71, 0x08, 0x48, 0x00, 0x78, + 0xC0, 0x07, 0x02, 0xD0, 0x00, 0xF0, 0x0E, 0xF8, + 0x10, 0xBD, 0x00, 0xF0, 0x77, 0xFE, 0x10, 0xBD, + 0x84, 0x04, 0x00, 0x20, 0xCF, 0x00, 0x00, 0x20, + 0x00, 0x10, 0x00, 0x50, 0x04, 0x08, 0x00, 0x00, + 0xCE, 0x00, 0x00, 0x20, 0x10, 0xB5, 0x1D, 0x4C, + 0xA0, 0x79, 0x00, 0x28, 0x35, 0xD0, 0x1C, 0xA0, + 0x02, 0xF0, 0x88, 0xFF, 0x00, 0x20, 0xA0, 0x71, + 0xE0, 0x71, 0x1E, 0x48, 0x81, 0x68, 0x01, 0x22, + 0x52, 0x02, 0x91, 0x43, 0x81, 0x60, 0x1C, 0x48, + 0x01, 0x68, 0x92, 0x00, 0x91, 0x43, 0x01, 0x60, + 0x1A, 0x48, 0x01, 0x68, 0x10, 0x22, 0x91, 0x43, + 0x01, 0x60, 0x01, 0x68, 0x80, 0x22, 0x11, 0x43, + 0x01, 0x60, 0x17, 0x49, 0x41, 0x61, 0x17, 0x48, + 0x00, 0x78, 0x00, 0x28, 0x15, 0xD1, 0x05, 0x20, + 0x00, 0x07, 0x41, 0x6A, 0x92, 0x02, 0x91, 0x43, + 0x41, 0x62, 0x41, 0x6A, 0x09, 0x22, 0x12, 0x05, + 0x11, 0x43, 0x41, 0x62, 0x80, 0x00, 0xC2, 0x68, + 0x41, 0x14, 0x8A, 0x43, 0xC2, 0x60, 0x02, 0x68, + 0x03, 0x14, 0x9A, 0x43, 0x02, 0x60, 0x0C, 0x48, + 0x01, 0x60, 0x10, 0xBD, 0x84, 0x04, 0x00, 0x20, + 0x4C, 0x65, 0x61, 0x76, 0x65, 0x20, 0x41, 0x75, + 0x74, 0x6F, 0x53, 0x63, 0x61, 0x6E, 0x0D, 0x0A, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x50, + 0x00, 0x10, 0x00, 0x50, 0x00, 0x11, 0x00, 0x50, + 0xC8, 0x03, 0x60, 0x00, 0xCF, 0x00, 0x00, 0x20, + 0x80, 0xE1, 0x00, 0xE0, 0x00, 0xB5, 0x08, 0x49, + 0x83, 0x20, 0x08, 0x70, 0x07, 0x49, 0x00, 0x20, + 0x08, 0x70, 0x07, 0x48, 0x00, 0x68, 0x07, 0x49, + 0x40, 0x05, 0x40, 0x0F, 0x08, 0x73, 0x5A, 0x20, + 0xFF, 0xF7, 0x16, 0xFF, 0x00, 0xBD, 0x00, 0x00, + 0x84, 0x04, 0x00, 0x20, 0xCE, 0x00, 0x00, 0x20, + 0x00, 0x11, 0x00, 0x50, 0x50, 0x02, 0x00, 0x20, + 0xF0, 0xB5, 0x04, 0x46, 0x8F, 0xB0, 0xA6, 0x48, + 0x0A, 0x90, 0xA5, 0x48, 0xA6, 0x4D, 0x60, 0x30, + 0x0B, 0x90, 0xA4, 0x48, 0x03, 0x90, 0xA5, 0x48, + 0x05, 0x95, 0x01, 0x68, 0x22, 0x46, 0xA4, 0xA0, + 0x02, 0xF0, 0x10, 0xFF, 0x03, 0x98, 0x08, 0x90, + 0xA5, 0x48, 0xA6, 0x4A, 0x00, 0x21, 0x09, 0x95, + 0x8B, 0x00, 0x08, 0x9D, 0x49, 0x1C, 0xE8, 0x50, + 0x09, 0x9D, 0x89, 0xB2, 0xEA, 0x50, 0x18, 0x29, + 0xF6, 0xD3, 0x01, 0x20, 0x80, 0x02, 0x06, 0x90, + 0x20, 0x46, 0x07, 0x94, 0x50, 0x30, 0x00, 0xB2, + 0x50, 0x3C, 0x0C, 0x90, 0x20, 0xB2, 0x0D, 0x90, + 0x00, 0xF0, 0x6E, 0xFC, 0x00, 0xF0, 0xE2, 0xFE, + 0x00, 0xF0, 0x6E, 0xF9, 0x98, 0x48, 0x00, 0x78, + 0xC0, 0x07, 0x18, 0xD1, 0x01, 0x90, 0x00, 0x20, + 0x08, 0x9A, 0x81, 0x00, 0x53, 0x58, 0x0A, 0x9A, + 0x40, 0x1C, 0x53, 0x50, 0x09, 0x9A, 0x80, 0xB2, + 0x53, 0x58, 0x0B, 0x9A, 0x18, 0x28, 0x53, 0x50, + 0xF2, 0xD3, 0x00, 0xF0, 0xB7, 0xFD, 0x00, 0xF0, + 0xC9, 0xFE, 0x00, 0xF0, 0x55, 0xF9, 0x8C, 0x48, + 0x00, 0x78, 0xC0, 0x07, 0x02, 0xD0, 0x00, 0x20, + 0x0F, 0xB0, 0xF0, 0xBD, 0x3B, 0x46, 0x89, 0xA0, + 0x00, 0x9A, 0x01, 0x99, 0x02, 0xF0, 0xCA, 0xFE, + 0x00, 0x25, 0x2D, 0xE0, 0x00, 0x2D, 0x2B, 0xD0, + 0x01, 0x98, 0x05, 0x28, 0x73, 0xD9, 0x30, 0x2F, + 0x71, 0xD2, 0x7A, 0x4C, 0x05, 0x98, 0x40, 0x3C, + 0x04, 0x90, 0x60, 0x6B, 0x03, 0x26, 0x36, 0x05, + 0x01, 0x21, 0xB0, 0x43, 0x49, 0x05, 0x40, 0x18, + 0x60, 0x63, 0x00, 0xF0, 0x8F, 0xFD, 0x00, 0xF0, + 0xA1, 0xFE, 0x00, 0xF0, 0x2D, 0xF9, 0x60, 0x6B, + 0x01, 0x21, 0xB0, 0x43, 0x09, 0x05, 0x40, 0x18, + 0x60, 0x63, 0x00, 0x27, 0x00, 0x26, 0x79, 0x48, + 0x00, 0x68, 0x80, 0x19, 0xC0, 0x7E, 0x41, 0x28, + 0x0E, 0xD0, 0x70, 0x00, 0x04, 0x99, 0x02, 0x90, + 0x0C, 0x5A, 0x16, 0x2E, 0x12, 0xD0, 0x1A, 0xE0, + 0x00, 0x98, 0x30, 0x28, 0x77, 0xD0, 0x03, 0x98, + 0x04, 0x90, 0x00, 0x20, 0x00, 0x90, 0xE9, 0xE7, + 0x00, 0x2D, 0x02, 0xD0, 0x7F, 0x1C, 0xBF, 0xB2, + 0x68, 0xE0, 0x00, 0x98, 0x40, 0x1C, 0x80, 0xB2, + 0x00, 0x90, 0x63, 0xE0, 0x6A, 0x48, 0x2C, 0x23, + 0x00, 0x68, 0x22, 0x46, 0xC3, 0x5E, 0x31, 0x46, + 0x68, 0xA0, 0x02, 0xF0, 0x7F, 0xFE, 0x01, 0x98, + 0x0A, 0x28, 0x0C, 0xD2, 0x64, 0x49, 0x02, 0x98, + 0x09, 0x68, 0x09, 0x5E, 0x07, 0x98, 0x81, 0x42, + 0x01, 0xDA, 0x06, 0x98, 0x84, 0x43, 0x06, 0x98, + 0x40, 0x08, 0x04, 0x43, 0x32, 0xE0, 0x5E, 0x48, + 0x02, 0x99, 0x00, 0x68, 0x41, 0x5E, 0x00, 0x29, + 0x01, 0xDB, 0x08, 0x46, 0x00, 0xE0, 0x48, 0x42, + 0x00, 0xB2, 0x40, 0x30, 0xC2, 0x17, 0x52, 0x0E, + 0x10, 0x18, 0x0D, 0x9A, 0xC0, 0x11, 0x91, 0x42, + 0x10, 0xDA, 0x21, 0x05, 0x06, 0xD5, 0x20, 0x1A, + 0x84, 0xB2, 0x01, 0x20, 0xC0, 0x02, 0x84, 0x42, + 0x11, 0xD3, 0x17, 0xE0, 0x84, 0x42, 0x03, 0xDD, + 0x20, 0x1A, 0x84, 0xB2, 0x12, 0xE0, 0x2E, 0xE0, + 0x00, 0x24, 0x0F, 0xE0, 0x0C, 0x9A, 0x91, 0x42, + 0x15, 0xDD, 0x21, 0x05, 0x05, 0xD5, 0x20, 0x18, + 0x84, 0xB2, 0x4D, 0x48, 0x04, 0xE0, 0x04, 0x46, + 0x04, 0xE0, 0x20, 0x18, 0x84, 0xB2, 0x4B, 0x48, + 0x84, 0x42, 0xF8, 0xD8, 0x00, 0x2D, 0x0F, 0xD0, + 0x01, 0x20, 0xC0, 0x02, 0x04, 0x43, 0x05, 0x99, + 0x02, 0x98, 0x0C, 0x52, 0x0E, 0xE0, 0x00, 0x2D, + 0x02, 0xD0, 0x7F, 0x1C, 0xBF, 0xB2, 0xF3, 0xE7, + 0x00, 0x98, 0x40, 0x1C, 0x80, 0xB2, 0x00, 0x90, + 0x03, 0x99, 0x02, 0x98, 0x0C, 0x52, 0x01, 0x98, + 0x05, 0x28, 0xE9, 0xD9, 0x76, 0x1C, 0xB6, 0xB2, + 0x30, 0x2E, 0x00, 0xD2, 0x77, 0xE7, 0x6D, 0x1C, + 0xED, 0xB2, 0x02, 0x2D, 0x00, 0xD2, 0x51, 0xE7, + 0x06, 0x98, 0x40, 0x08, 0x06, 0x90, 0x38, 0xA0, + 0x02, 0xF0, 0x14, 0xFE, 0x00, 0x98, 0x30, 0x28, + 0x03, 0xD1, 0x30, 0x2F, 0x01, 0xD1, 0x40, 0x20, + 0x01, 0x90, 0x01, 0x98, 0x40, 0x1C, 0x80, 0xB2, + 0x01, 0x90, 0x40, 0x28, 0x00, 0xD8, 0x1A, 0xE7, + 0x1C, 0x4C, 0x40, 0x3C, 0x60, 0x6B, 0x03, 0x25, + 0x2D, 0x05, 0x01, 0x21, 0xA8, 0x43, 0x09, 0x05, + 0x40, 0x18, 0x60, 0x63, 0x00, 0xF0, 0xD6, 0xFC, + 0x00, 0xF0, 0xE8, 0xFD, 0x00, 0xF0, 0x74, 0xF8, + 0x21, 0x4E, 0x01, 0x21, 0x30, 0x68, 0x00, 0xF0, + 0x31, 0xFE, 0x60, 0x6B, 0x01, 0x21, 0xA8, 0x43, + 0x49, 0x05, 0x40, 0x18, 0x60, 0x63, 0x00, 0xF0, + 0xC5, 0xFC, 0x00, 0xF0, 0xD7, 0xFD, 0x00, 0xF0, + 0x63, 0xF8, 0x01, 0x21, 0x30, 0x68, 0x00, 0xF0, + 0x21, 0xFE, 0x60, 0x6B, 0x28, 0x43, 0x60, 0x63, + 0x00, 0xF0, 0xB8, 0xFC, 0x00, 0xF0, 0xCA, 0xFD, + 0x00, 0xF0, 0x56, 0xF8, 0x01, 0x21, 0x30, 0x68, + 0x00, 0xF0, 0x14, 0xFE, 0x01, 0x20, 0xFF, 0xE6, + 0x00, 0x20, 0x00, 0x50, 0xF4, 0x05, 0x00, 0x20, + 0x00, 0x00, 0x01, 0x20, 0xC0, 0x10, 0x00, 0x50, + 0x5B, 0x43, 0x46, 0x42, 0x3A, 0x25, 0x78, 0x3A, + 0x25, 0x64, 0x5D, 0x0D, 0x0A, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x04, 0x00, 0x0C, 0x00, 0x0C, + 0xCE, 0x00, 0x00, 0x20, 0x5B, 0x25, 0x64, 0x3A, + 0x25, 0x64, 0x3A, 0x25, 0x64, 0x5D, 0x20, 0x0D, + 0x0A, 0x00, 0x00, 0x00, 0xB8, 0x02, 0x00, 0x20, + 0xF8, 0x02, 0x00, 0x20, 0x25, 0x64, 0x2C, 0x30, + 0x78, 0x25, 0x78, 0x2C, 0x25, 0x64, 0x2C, 0x00, + 0xFF, 0x0F, 0x00, 0x00, 0xFF, 0x07, 0x00, 0x00, + 0x0D, 0x0A, 0x00, 0x00, 0x10, 0xB5, 0x0B, 0x49, + 0x30, 0x24, 0x00, 0x28, 0x0A, 0x4A, 0x0B, 0x4B, + 0x08, 0x68, 0x06, 0xD0, 0x20, 0x43, 0x08, 0x60, + 0x09, 0x48, 0x10, 0x60, 0x08, 0x48, 0x60, 0x30, + 0x05, 0xE0, 0xA0, 0x43, 0x08, 0x60, 0x07, 0x48, + 0x10, 0x60, 0x06, 0x48, 0x60, 0x30, 0x18, 0x60, + 0x10, 0xBD, 0x00, 0x00, 0x00, 0x10, 0x00, 0x50, + 0xF8, 0x02, 0x00, 0x20, 0xFC, 0x02, 0x00, 0x20, + 0x00, 0x00, 0x04, 0x20, 0x00, 0x10, 0x04, 0x20, + 0xF0, 0xB5, 0x11, 0x48, 0x01, 0x68, 0x11, 0x4D, + 0x89, 0x06, 0x30, 0x22, 0x60, 0x35, 0x00, 0x29, + 0x0E, 0x4B, 0x0F, 0x4C, 0x0F, 0x4E, 0x10, 0x4F, + 0x01, 0x68, 0x0A, 0xDA, 0x91, 0x43, 0x01, 0x60, + 0x23, 0x60, 0x0E, 0x48, 0x35, 0x60, 0x07, 0x60, + 0x0B, 0x48, 0x0D, 0x49, 0x60, 0x30, 0x08, 0x60, + 0xF0, 0xBD, 0x11, 0x43, 0x01, 0x60, 0x08, 0x48, + 0x27, 0x60, 0x60, 0x30, 0x30, 0x60, 0x07, 0x48, + 0x03, 0x60, 0x07, 0x48, 0x05, 0x60, 0xF0, 0xBD, + 0x00, 0x10, 0x00, 0x50, 0x00, 0x00, 0x04, 0x20, + 0x64, 0x02, 0x00, 0x20, 0x68, 0x02, 0x00, 0x20, + 0x00, 0x10, 0x04, 0x20, 0xF8, 0x02, 0x00, 0x20, + 0xFC, 0x02, 0x00, 0x20, 0x00, 0xB5, 0x09, 0x48, + 0x00, 0x78, 0x00, 0x28, 0x08, 0xD0, 0x01, 0x28, + 0x09, 0xD0, 0x02, 0x28, 0x03, 0xD1, 0x06, 0x49, + 0x04, 0x20, 0x00, 0xF0, 0x65, 0xF8, 0x00, 0xBD, + 0x04, 0x49, 0x01, 0x20, 0xF9, 0xE7, 0x04, 0x49, + 0x02, 0x20, 0xF6, 0xE7, 0x71, 0x04, 0x00, 0x20, + 0xFC, 0x03, 0x00, 0x20, 0x9C, 0x03, 0x00, 0x20, + 0xEC, 0x06, 0x00, 0x20, 0xF8, 0xB5, 0x07, 0x46, + 0x00, 0xF0, 0xAA, 0xFA, 0x21, 0x4D, 0x60, 0x21, + 0x28, 0x46, 0xFE, 0xF7, 0xAE, 0xF9, 0x00, 0x24, + 0x00, 0xF0, 0x18, 0xFD, 0xFF, 0xF7, 0xA4, 0xFF, + 0xFE, 0xF7, 0xF4, 0xFA, 0x00, 0x2C, 0x0C, 0xD0, + 0x1B, 0x49, 0x00, 0x20, 0x0E, 0x68, 0x42, 0x00, + 0x51, 0x19, 0x0B, 0x88, 0xB2, 0x5A, 0x40, 0x1C, + 0x9A, 0x18, 0xC0, 0xB2, 0x0A, 0x80, 0x30, 0x28, + 0xF5, 0xD3, 0x64, 0x1C, 0xE4, 0xB2, 0x05, 0x2C, + 0xE6, 0xD3, 0x00, 0x20, 0x41, 0x00, 0x49, 0x19, + 0x00, 0x22, 0x8A, 0x5E, 0x40, 0x1C, 0x92, 0x10, + 0xC0, 0xB2, 0x0A, 0x80, 0x30, 0x28, 0xF5, 0xD3, + 0x39, 0x46, 0x0E, 0xA0, 0x02, 0xF0, 0x02, 0xFD, + 0x01, 0x21, 0x28, 0x46, 0x00, 0xF0, 0x3E, 0xFD, + 0x01, 0x20, 0xFE, 0xF7, 0x3D, 0xFB, 0x0C, 0x4C, + 0x20, 0x78, 0x38, 0x42, 0x08, 0xD1, 0x0B, 0x48, + 0x60, 0x22, 0x29, 0x46, 0x00, 0x68, 0xFE, 0xF7, + 0x5B, 0xF9, 0x20, 0x78, 0x38, 0x43, 0x20, 0x70, + 0x01, 0x20, 0xF8, 0xBD, 0x00, 0x00, 0x01, 0x20, + 0xF8, 0x02, 0x00, 0x20, 0x42, 0x43, 0x5F, 0x4F, + 0x4B, 0x5F, 0x25, 0x64, 0x0D, 0x0A, 0x00, 0x00, + 0x98, 0x01, 0x00, 0x20, 0x6C, 0x02, 0x00, 0x20, + 0xF0, 0xB5, 0x21, 0x48, 0x00, 0x22, 0x00, 0x68, + 0x20, 0x4C, 0xA0, 0x42, 0x28, 0xD8, 0x20, 0x48, + 0x00, 0x78, 0x04, 0x28, 0x27, 0xD1, 0x1F, 0x4D, + 0x1F, 0x4C, 0x2D, 0x68, 0x00, 0x20, 0x2E, 0x18, + 0xF6, 0x7E, 0x41, 0x2E, 0x08, 0xD0, 0x43, 0x00, + 0x1F, 0x19, 0x00, 0x26, 0xBE, 0x5F, 0xCB, 0x5E, + 0xF3, 0x1A, 0x00, 0xD5, 0x5B, 0x42, 0x1B, 0xB2, + 0x9A, 0x42, 0x00, 0xDA, 0x1A, 0x46, 0x40, 0x1C, + 0xC0, 0xB2, 0x30, 0x28, 0xEB, 0xD3, 0x60, 0x20, + 0x40, 0x5D, 0x90, 0x35, 0x40, 0x1C, 0x50, 0x43, + 0x6A, 0x7B, 0x2B, 0x7B, 0x00, 0x11, 0x12, 0x02, + 0x00, 0xB2, 0x1A, 0x43, 0x82, 0x42, 0x03, 0xDA, + 0x0E, 0x49, 0x00, 0x20, 0x08, 0x70, 0xF0, 0xBD, + 0x00, 0x20, 0x42, 0x00, 0x8B, 0x5E, 0x00, 0x26, + 0xDD, 0x00, 0xEB, 0x1A, 0x15, 0x19, 0xAE, 0x5F, + 0x40, 0x1C, 0x9B, 0x19, 0xDB, 0x10, 0xC0, 0xB2, + 0x8B, 0x52, 0x30, 0x28, 0xF1, 0xD3, 0xF0, 0xBD, + 0x08, 0x03, 0x00, 0x20, 0x40, 0x77, 0x1B, 0x00, + 0x87, 0x02, 0x00, 0x20, 0xB8, 0x02, 0x00, 0x20, + 0x00, 0x00, 0x01, 0x20, 0x98, 0x01, 0x00, 0x20, + 0x70, 0xB5, 0x21, 0x48, 0x21, 0x49, 0x00, 0x78, + 0x00, 0x28, 0x06, 0xD0, 0x01, 0x28, 0x08, 0xD0, + 0x02, 0x28, 0x16, 0xD0, 0x05, 0x28, 0x2F, 0xD1, + 0x34, 0xE0, 0x01, 0x25, 0x2C, 0x46, 0x1C, 0x48, + 0x02, 0xE0, 0x02, 0x25, 0x1B, 0x48, 0x2C, 0x46, + 0x08, 0x60, 0x00, 0x22, 0x1A, 0x49, 0x28, 0x46, + 0xFF, 0xF7, 0xD4, 0xFA, 0x00, 0x28, 0x18, 0xD0, + 0x18, 0x48, 0x00, 0x78, 0x00, 0x28, 0x04, 0xD0, + 0x13, 0xE0, 0x04, 0x25, 0x2C, 0x46, 0x16, 0x48, + 0xEE, 0xE7, 0x16, 0x48, 0x41, 0x6B, 0x03, 0x22, + 0x12, 0x05, 0x11, 0x43, 0x41, 0x63, 0x14, 0x48, + 0x00, 0xF0, 0x30, 0xFA, 0x00, 0x28, 0x0B, 0xD1, + 0x20, 0x46, 0xFF, 0xF7, 0x27, 0xFF, 0x00, 0x28, + 0x06, 0xD1, 0x01, 0x22, 0x21, 0x46, 0x28, 0x46, + 0x00, 0xF0, 0x1E, 0xF8, 0x00, 0x28, 0x04, 0xD0, + 0x0C, 0x48, 0x00, 0x78, 0xC0, 0x07, 0x01, 0xD0, + 0x00, 0x20, 0x70, 0xBD, 0x01, 0x20, 0x70, 0xBD, + 0x85, 0x02, 0x00, 0x20, 0x6C, 0x02, 0x00, 0x20, + 0x9C, 0x03, 0x00, 0x20, 0xEC, 0x06, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x50, 0xE7, 0x02, 0x00, 0x20, + 0xFC, 0x03, 0x00, 0x20, 0x80, 0x10, 0x00, 0x50, + 0x00, 0x00, 0x01, 0x20, 0xCE, 0x00, 0x00, 0x20, + 0xF7, 0xB5, 0x07, 0x46, 0xFE, 0xF7, 0xD0, 0xFB, + 0x14, 0x48, 0x00, 0x25, 0x05, 0x70, 0x78, 0x07, + 0x1D, 0xD0, 0x01, 0x21, 0x02, 0x20, 0x00, 0xF0, + 0x33, 0xFB, 0x11, 0x4C, 0x60, 0x6B, 0x03, 0x26, + 0x36, 0x05, 0x01, 0x21, 0xB0, 0x43, 0x09, 0x05, + 0x40, 0x18, 0x60, 0x63, 0x00, 0x20, 0xFF, 0xF7, + 0xFB, 0xFC, 0x00, 0x28, 0x0D, 0xD0, 0x01, 0x98, + 0xFF, 0xF7, 0xE4, 0xFE, 0x01, 0x22, 0x09, 0x49, + 0x38, 0x46, 0xFF, 0xF7, 0x73, 0xFA, 0x08, 0x48, + 0x05, 0x70, 0x08, 0x48, 0x05, 0x70, 0x01, 0x20, + 0xFE, 0xBD, 0x60, 0x6B, 0x30, 0x43, 0x60, 0x63, + 0x00, 0x20, 0xFE, 0xBD, 0x82, 0x02, 0x00, 0x20, + 0x80, 0x10, 0x00, 0x50, 0x00, 0x20, 0x00, 0x50, + 0xE7, 0x02, 0x00, 0x20, 0xE8, 0x02, 0x00, 0x20, + 0xF0, 0xB5, 0xB4, 0x48, 0x01, 0x22, 0x23, 0x23, + 0x13, 0x24, 0x92, 0x02, 0x5B, 0x01, 0xA4, 0x01, + 0x05, 0x46, 0x1C, 0xC5, 0x29, 0x21, 0x49, 0x01, + 0xC1, 0x60, 0xAF, 0x48, 0x07, 0x68, 0x38, 0x46, + 0x40, 0x30, 0x45, 0x7C, 0x06, 0x7C, 0x2D, 0x04, + 0x36, 0x02, 0x35, 0x43, 0x0C, 0x35, 0x15, 0x60, + 0x45, 0x7C, 0x06, 0x7C, 0x2D, 0x04, 0x36, 0x02, + 0x35, 0x43, 0x0C, 0x35, 0x55, 0x60, 0x01, 0x25, + 0x95, 0x60, 0xD5, 0x60, 0x05, 0x7D, 0xBC, 0x46, + 0x6E, 0x1E, 0x6D, 0x08, 0x6D, 0x1E, 0xF6, 0x05, + 0xED, 0x05, 0xF6, 0x09, 0xED, 0x0D, 0x2E, 0x43, + 0x16, 0x61, 0xA0, 0x4D, 0x55, 0x61, 0x03, 0x25, + 0x95, 0x61, 0x03, 0x26, 0x36, 0x02, 0x00, 0x25, + 0x16, 0x62, 0xD5, 0x61, 0x06, 0x7E, 0xD6, 0x62, + 0x06, 0x7E, 0x87, 0x7E, 0xF6, 0x19, 0x16, 0x63, + 0x87, 0x7E, 0x06, 0x7E, 0x7F, 0x00, 0xF6, 0x19, + 0x56, 0x63, 0x95, 0x63, 0x06, 0x7E, 0xBC, 0x36, + 0xD6, 0x63, 0x06, 0x7E, 0x87, 0x7E, 0xF6, 0x19, + 0xBC, 0x36, 0x16, 0x64, 0x07, 0x7E, 0x86, 0x7E, + 0x95, 0x64, 0x76, 0x00, 0xBC, 0x36, 0xBE, 0x19, + 0x56, 0x64, 0x67, 0x46, 0x55, 0x62, 0x8E, 0x4E, + 0x50, 0x37, 0x96, 0x62, 0xBC, 0x46, 0xFE, 0x7B, + 0xBF, 0x7B, 0x36, 0x02, 0x3E, 0x43, 0xF6, 0x1C, + 0xB7, 0x05, 0x8A, 0x4E, 0xBF, 0x0D, 0xBE, 0x19, + 0xD6, 0x64, 0x89, 0x4E, 0x16, 0x65, 0x46, 0x7C, + 0x0C, 0x3E, 0xF7, 0xB2, 0x05, 0x26, 0x76, 0x02, + 0xBE, 0x19, 0x56, 0x65, 0x06, 0x7D, 0x36, 0x02, + 0x21, 0x36, 0x96, 0x65, 0xD5, 0x65, 0x42, 0x7C, + 0x06, 0x7C, 0x12, 0x04, 0x36, 0x02, 0x32, 0x43, + 0x0C, 0x32, 0x1A, 0x60, 0x42, 0x7C, 0x06, 0x7C, + 0x12, 0x04, 0x36, 0x02, 0x32, 0x43, 0x0C, 0x32, + 0x5A, 0x60, 0x01, 0x22, 0x9A, 0x60, 0xDA, 0x60, + 0x42, 0x7D, 0x56, 0x1E, 0x52, 0x08, 0x52, 0x1E, + 0xF6, 0x05, 0xD2, 0x05, 0xF7, 0x09, 0xD2, 0x0D, + 0x17, 0x43, 0x72, 0x4A, 0x1F, 0x61, 0x5A, 0x61, + 0x03, 0x22, 0xDD, 0x61, 0x9A, 0x61, 0x12, 0x02, + 0x1A, 0x62, 0x02, 0x7E, 0xDA, 0x62, 0x02, 0x7E, + 0xC6, 0x7E, 0x92, 0x19, 0x1A, 0x63, 0xC6, 0x7E, + 0x02, 0x7E, 0x76, 0x00, 0x92, 0x19, 0x9D, 0x63, + 0x5A, 0x63, 0x02, 0x7E, 0x67, 0x46, 0xBC, 0x32, + 0xDA, 0x63, 0x02, 0x7E, 0xC6, 0x7E, 0x92, 0x19, + 0xBC, 0x32, 0x1A, 0x64, 0xC6, 0x7E, 0x02, 0x7E, + 0x76, 0x00, 0xBC, 0x36, 0x9D, 0x64, 0x92, 0x19, + 0x5D, 0x62, 0x5A, 0x64, 0x60, 0x4A, 0x9A, 0x62, + 0xFA, 0x7B, 0xBE, 0x7B, 0x12, 0x02, 0x32, 0x43, + 0xD2, 0x1C, 0x96, 0x05, 0x5D, 0x4A, 0xB6, 0x0D, + 0xB2, 0x18, 0xDA, 0x64, 0x5C, 0x4A, 0x1A, 0x65, + 0x42, 0x7C, 0x0C, 0x3A, 0xD6, 0xB2, 0x05, 0x22, + 0x52, 0x02, 0xB2, 0x18, 0x5A, 0x65, 0x42, 0x7D, + 0xDD, 0x65, 0x12, 0x02, 0x21, 0x32, 0x9A, 0x65, + 0xC2, 0x7C, 0x83, 0x7C, 0x12, 0x04, 0x1B, 0x02, + 0x1A, 0x43, 0x0C, 0x32, 0x23, 0x46, 0x1A, 0x60, + 0xC2, 0x7C, 0x83, 0x7C, 0x12, 0x04, 0x1B, 0x02, + 0x1A, 0x43, 0x0C, 0x32, 0x23, 0x46, 0x5A, 0x60, + 0x82, 0x7D, 0x52, 0x1E, 0xD3, 0x05, 0xDB, 0x0D, + 0x22, 0x46, 0x93, 0x60, 0x82, 0x7D, 0x52, 0x08, + 0x52, 0x1E, 0xD2, 0x05, 0x23, 0x46, 0xD2, 0x0D, + 0x1D, 0x61, 0xDA, 0x60, 0x47, 0x4A, 0x5A, 0x61, + 0x26, 0x46, 0x01, 0x23, 0xF5, 0x61, 0xB3, 0x61, + 0x13, 0x12, 0x33, 0x62, 0x43, 0x7E, 0xF3, 0x62, + 0x43, 0x7E, 0x06, 0x7F, 0x9B, 0x19, 0x26, 0x46, + 0x33, 0x63, 0x06, 0x7F, 0x43, 0x7E, 0x76, 0x00, + 0x9B, 0x19, 0x26, 0x46, 0xB5, 0x63, 0xF5, 0x63, + 0x35, 0x64, 0x73, 0x63, 0x65, 0x64, 0x38, 0x4B, + 0xA5, 0x64, 0x3F, 0x33, 0x65, 0x62, 0xA3, 0x62, + 0x36, 0x4B, 0xDB, 0x1C, 0xE3, 0x64, 0x36, 0x4B, + 0x23, 0x65, 0xC6, 0x7C, 0x0C, 0x3E, 0xF7, 0xB2, + 0x05, 0x26, 0x76, 0x02, 0xBF, 0x19, 0x67, 0x65, + 0x87, 0x7D, 0x7F, 0x08, 0x3F, 0x02, 0x21, 0x37, + 0xA7, 0x65, 0xE5, 0x65, 0xC4, 0x7C, 0x80, 0x7C, + 0x24, 0x04, 0x00, 0x02, 0x04, 0x43, 0x0C, 0x34, + 0x27, 0x48, 0x0C, 0x60, 0x00, 0x68, 0x40, 0x30, + 0xC4, 0x7C, 0x87, 0x7C, 0x24, 0x04, 0x3F, 0x02, + 0x3C, 0x43, 0x0C, 0x34, 0x4C, 0x60, 0xC4, 0x7D, + 0x64, 0x1E, 0xE4, 0x05, 0xE4, 0x0D, 0x8C, 0x60, + 0xC4, 0x7D, 0x0D, 0x61, 0x64, 0x08, 0x64, 0x1E, + 0xE4, 0x05, 0xE4, 0x0D, 0xCC, 0x60, 0x4A, 0x61, + 0x01, 0x22, 0xCD, 0x61, 0x8A, 0x61, 0x12, 0x02, + 0x0A, 0x62, 0x42, 0x7E, 0xCA, 0x62, 0x42, 0x7E, + 0x44, 0x7F, 0x12, 0x19, 0x0A, 0x63, 0x44, 0x7F, + 0x42, 0x7E, 0x64, 0x00, 0x12, 0x19, 0x8D, 0x63, + 0x4A, 0x63, 0x42, 0x7E, 0xBC, 0x32, 0xCA, 0x63, + 0x42, 0x7E, 0x44, 0x7F, 0x12, 0x19, 0xBC, 0x32, + 0x0A, 0x64, 0x42, 0x7F, 0x44, 0x7E, 0x52, 0x00, + 0xBC, 0x32, 0xA2, 0x18, 0x8D, 0x64, 0x4A, 0x64, + 0x01, 0x22, 0x92, 0x04, 0x4A, 0x62, 0x0C, 0x4A, + 0x3F, 0x32, 0x8A, 0x62, 0x0B, 0x4A, 0x0B, 0x65, + 0xD2, 0x1C, 0xCA, 0x64, 0xC2, 0x7C, 0x0C, 0x3A, + 0xD2, 0xB2, 0x92, 0x19, 0x4A, 0x65, 0xC0, 0x7D, + 0xCD, 0x65, 0x40, 0x08, 0x00, 0x02, 0x21, 0x30, + 0x88, 0x65, 0xF0, 0xBD, 0x70, 0x02, 0x00, 0x20, + 0xB8, 0x02, 0x00, 0x20, 0x03, 0x00, 0x03, 0x00, + 0x00, 0x3F, 0x3F, 0x3F, 0x00, 0x30, 0xBC, 0x00, + 0x00, 0x26, 0x31, 0x00, 0x01, 0x00, 0x01, 0x00, + 0x10, 0xB5, 0x00, 0xF0, 0x73, 0xFA, 0x00, 0xF0, + 0x75, 0xF9, 0x01, 0x20, 0x80, 0x07, 0x41, 0x68, + 0x04, 0x22, 0x11, 0x43, 0x41, 0x60, 0x00, 0x20, + 0xFF, 0xF7, 0xD4, 0xFC, 0x09, 0x48, 0x01, 0x78, + 0x09, 0x48, 0x00, 0x29, 0x04, 0xD0, 0x01, 0x68, + 0xFF, 0x22, 0x01, 0x32, 0x11, 0x43, 0x01, 0x60, + 0x01, 0x68, 0x01, 0x22, 0x92, 0x02, 0x91, 0x43, + 0x01, 0x60, 0x04, 0x49, 0x02, 0x20, 0x08, 0x72, + 0x10, 0xBD, 0x00, 0x00, 0xCF, 0x00, 0x00, 0x20, + 0x00, 0x10, 0x00, 0x50, 0x50, 0x02, 0x00, 0x20, + 0x10, 0xB5, 0x01, 0x20, 0x80, 0x07, 0x41, 0x68, + 0x04, 0x22, 0x11, 0x43, 0x41, 0x60, 0x09, 0x48, + 0x07, 0x49, 0x41, 0x60, 0x08, 0x49, 0x81, 0x60, + 0xFF, 0xF7, 0x52, 0xFE, 0x80, 0x21, 0x07, 0x48, + 0xFD, 0xF7, 0xCF, 0xFE, 0x80, 0x21, 0x06, 0x48, + 0xFD, 0xF7, 0xCB, 0xFE, 0x10, 0xBD, 0x00, 0x00, + 0x1F, 0x1F, 0x5F, 0x1F, 0x00, 0x10, 0x00, 0x50, + 0x1F, 0x1F, 0x1F, 0x1F, 0x4C, 0x07, 0x00, 0x20, + 0x9C, 0x01, 0x00, 0x20, 0x01, 0x21, 0x89, 0x07, + 0x0A, 0x15, 0x00, 0x28, 0x48, 0x69, 0x02, 0xD0, + 0x10, 0x43, 0x48, 0x61, 0x70, 0x47, 0x90, 0x43, + 0xFB, 0xE7, 0x00, 0x00, 0x10, 0xB5, 0x0F, 0x49, + 0x0A, 0x78, 0x0F, 0x49, 0x00, 0x2A, 0x09, 0x78, + 0x04, 0xD0, 0x01, 0x2A, 0x07, 0xD0, 0x02, 0x2A, + 0x12, 0xD1, 0x09, 0xE0, 0xC9, 0x07, 0x0F, 0xD0, + 0x60, 0x22, 0x0A, 0x49, 0x08, 0xE0, 0x89, 0x07, + 0x0A, 0xD5, 0x60, 0x22, 0x08, 0x49, 0x03, 0xE0, + 0x49, 0x07, 0x05, 0xD5, 0x60, 0x22, 0x07, 0x49, + 0xFD, 0xF7, 0x7E, 0xFE, 0x01, 0x20, 0x10, 0xBD, + 0x00, 0x20, 0x10, 0xBD, 0x85, 0x02, 0x00, 0x20, + 0x98, 0x01, 0x00, 0x20, 0x9C, 0x03, 0x00, 0x20, + 0xEC, 0x06, 0x00, 0x20, 0xFC, 0x03, 0x00, 0x20, + 0x10, 0xB5, 0x13, 0x49, 0x13, 0x4B, 0x09, 0x68, + 0x13, 0x4A, 0x40, 0x31, 0x00, 0x28, 0x0F, 0xD0, + 0x01, 0x28, 0x12, 0xD0, 0x02, 0x28, 0x15, 0xD0, + 0x03, 0x28, 0x08, 0xD1, 0x48, 0x7E, 0x84, 0x1E, + 0x1C, 0x80, 0x49, 0x7F, 0x4B, 0x00, 0xC9, 0x18, + 0x89, 0x1C, 0x40, 0x18, 0x10, 0x80, 0x10, 0xBD, + 0x08, 0x7E, 0x84, 0x1E, 0x1C, 0x80, 0x89, 0x7E, + 0xF4, 0xE7, 0x08, 0x7E, 0x84, 0x1E, 0x1C, 0x80, + 0xC9, 0x7E, 0xEF, 0xE7, 0x48, 0x7E, 0x84, 0x1E, + 0x1C, 0x80, 0x09, 0x7F, 0xEA, 0xE7, 0x00, 0x00, + 0xB8, 0x02, 0x00, 0x20, 0x60, 0x02, 0x00, 0x20, + 0x62, 0x02, 0x00, 0x20, 0xF8, 0xB5, 0x05, 0x46, + 0x23, 0x48, 0x00, 0x21, 0x01, 0x60, 0x41, 0x60, + 0x22, 0x49, 0x01, 0x20, 0x08, 0x70, 0x22, 0x48, + 0x22, 0x4C, 0x00, 0x78, 0x00, 0x28, 0x06, 0xD0, + 0x2D, 0x26, 0x09, 0x27, 0x01, 0x28, 0x10, 0xD0, + 0x02, 0x28, 0x1D, 0xD1, 0x21, 0xE0, 0x1E, 0xA0, + 0x02, 0xF0, 0xC0, 0xF9, 0x5A, 0x20, 0x00, 0x2D, + 0x20, 0x70, 0x01, 0xD0, 0x00, 0x20, 0x00, 0xE0, + 0x01, 0x20, 0x00, 0xF0, 0x23, 0xF9, 0x5A, 0x20, + 0x0C, 0xE0, 0x1A, 0xA0, 0x02, 0xF0, 0xB2, 0xF9, + 0x00, 0x2D, 0x02, 0xD0, 0x26, 0x70, 0x02, 0x20, + 0x01, 0xE0, 0x27, 0x70, 0x03, 0x20, 0x00, 0xF0, + 0x15, 0xF9, 0x01, 0x20, 0xFF, 0xF7, 0x90, 0xF9, + 0x05, 0x20, 0x14, 0x49, 0x00, 0x02, 0x08, 0x60, + 0xF8, 0xBD, 0x13, 0xA0, 0x02, 0xF0, 0x9E, 0xF9, + 0x00, 0x2D, 0x02, 0xD0, 0x26, 0x70, 0x02, 0x20, + 0x01, 0xE0, 0x27, 0x70, 0x03, 0x20, 0x00, 0xF0, + 0x01, 0xF9, 0x10, 0x48, 0x81, 0x6A, 0x10, 0x4A, + 0x11, 0x40, 0x81, 0x62, 0xE5, 0xE7, 0x00, 0x00, + 0x50, 0x02, 0x00, 0x20, 0xCF, 0x00, 0x00, 0x20, + 0x85, 0x02, 0x00, 0x20, 0xB5, 0x02, 0x00, 0x20, + 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x0D, 0x0A, + 0x00, 0x00, 0x00, 0x00, 0x49, 0x64, 0x6C, 0x65, + 0x0D, 0x0A, 0x00, 0x00, 0x00, 0x10, 0x00, 0x50, + 0x47, 0x65, 0x73, 0x74, 0x75, 0x72, 0x65, 0x0D, + 0x0A, 0x00, 0x00, 0x00, 0xC0, 0x11, 0x00, 0x50, + 0xFF, 0xFF, 0x00, 0xF8, 0x70, 0xB5, 0x1C, 0x4D, + 0x1C, 0x4C, 0x28, 0x70, 0x02, 0x46, 0x21, 0x78, + 0x1B, 0xA0, 0x02, 0xF0, 0x67, 0xF9, 0x1E, 0x49, + 0x01, 0x20, 0x08, 0x70, 0x00, 0xF0, 0x7E, 0xF9, + 0x00, 0xF0, 0x50, 0xF9, 0x00, 0xF0, 0x52, 0xF8, + 0x01, 0x20, 0xFD, 0xF7, 0x8D, 0xFE, 0xFF, 0xF7, + 0x09, 0xFC, 0x01, 0x20, 0xFF, 0xF7, 0x7E, 0xFF, + 0x00, 0xF0, 0xC6, 0xFF, 0xFE, 0xF7, 0xF6, 0xFF, + 0x28, 0x78, 0x20, 0x70, 0x05, 0x28, 0x12, 0xD0, + 0xFF, 0xF7, 0xBE, 0xFC, 0x00, 0x28, 0x0F, 0xD0, + 0x10, 0x48, 0x00, 0x7A, 0x00, 0x28, 0x03, 0xD1, + 0x00, 0x21, 0x02, 0x20, 0x00, 0xF0, 0x4C, 0xF8, + 0x00, 0x20, 0x00, 0xF0, 0x5B, 0xF9, 0x0C, 0x48, + 0x00, 0x78, 0xC0, 0x07, 0x01, 0xD0, 0x00, 0x20, + 0x70, 0xBD, 0x01, 0x20, 0x70, 0xBD, 0x00, 0x00, + 0x85, 0x02, 0x00, 0x20, 0x71, 0x04, 0x00, 0x20, + 0x53, 0x4D, 0x3D, 0x5B, 0x25, 0x64, 0x3A, 0x25, + 0x64, 0x5D, 0x0D, 0x0A, 0x00, 0x00, 0x00, 0x00, + 0x83, 0x02, 0x00, 0x20, 0x50, 0x02, 0x00, 0x20, + 0xCE, 0x00, 0x00, 0x20, 0x08, 0x49, 0x02, 0x20, + 0x08, 0x72, 0x08, 0x48, 0x01, 0x78, 0x08, 0x48, + 0x00, 0x29, 0x01, 0x68, 0x04, 0xD0, 0x01, 0x22, + 0x92, 0x02, 0x91, 0x43, 0x01, 0x60, 0x70, 0x47, + 0x01, 0x22, 0x11, 0x43, 0xFA, 0xE7, 0x00, 0x00, + 0x50, 0x02, 0x00, 0x20, 0xCF, 0x00, 0x00, 0x20, + 0x00, 0x10, 0x00, 0x50, 0x10, 0xB5, 0x08, 0x48, + 0x01, 0x68, 0x01, 0x22, 0x92, 0x02, 0x11, 0x43, + 0x01, 0x60, 0x00, 0x68, 0x05, 0x4C, 0xC0, 0x07, + 0x03, 0xD0, 0x02, 0x20, 0x20, 0x72, 0x00, 0xF0, + 0xED, 0xF8, 0x00, 0x20, 0x20, 0x72, 0x10, 0xBD, + 0x00, 0x10, 0x00, 0x50, 0x50, 0x02, 0x00, 0x20, + 0x70, 0xB5, 0x05, 0x00, 0x0E, 0x46, 0x16, 0xD0, + 0xFF, 0xF7, 0x6A, 0xFE, 0x00, 0x24, 0x6D, 0x1E, + 0x07, 0xE0, 0x00, 0xF0, 0xDB, 0xF8, 0xFF, 0xF7, + 0x67, 0xFB, 0xFF, 0xF7, 0xC3, 0xFF, 0x64, 0x1C, + 0xE4, 0xB2, 0xAC, 0x42, 0xF5, 0xDB, 0x00, 0xF0, + 0xD1, 0xF8, 0xFF, 0xF7, 0x5D, 0xFB, 0x00, 0x2E, + 0x02, 0xD0, 0xFF, 0xF7, 0xCF, 0xFF, 0x70, 0xBD, + 0xFF, 0xF7, 0xB4, 0xFF, 0x70, 0xBD, 0x00, 0x00, + 0x30, 0xB5, 0x01, 0x24, 0x1C, 0x4A, 0xA4, 0x07, + 0x23, 0x13, 0x1B, 0x49, 0x40, 0x32, 0x00, 0x28, + 0x21, 0xD0, 0x01, 0x28, 0x1F, 0xD0, 0x02, 0x28, + 0x01, 0xD0, 0x03, 0x28, 0x1A, 0xD1, 0x25, 0x68, + 0x1D, 0x43, 0x25, 0x60, 0x62, 0x23, 0x93, 0x61, + 0xD3, 0x61, 0x14, 0x4B, 0xCB, 0x63, 0x14, 0x49, + 0x09, 0x68, 0x40, 0x31, 0x02, 0x28, 0x48, 0x7E, + 0x17, 0xD0, 0x40, 0x1E, 0x40, 0x05, 0x40, 0x0D, + 0x10, 0x60, 0x4B, 0x7E, 0x48, 0x7F, 0x41, 0x00, + 0x40, 0x18, 0x80, 0x1C, 0x18, 0x18, 0x40, 0x05, + 0x40, 0x0D, 0x90, 0x60, 0x30, 0xBD, 0x20, 0x68, + 0x98, 0x43, 0x20, 0x60, 0x01, 0x20, 0x90, 0x61, + 0xD0, 0x61, 0x00, 0x20, 0xC8, 0x63, 0x10, 0x60, + 0xF3, 0xE7, 0x40, 0x1E, 0x40, 0x05, 0x40, 0x0D, + 0x10, 0x60, 0x4B, 0x7E, 0x08, 0x7F, 0xE6, 0xE7, + 0x80, 0x11, 0x00, 0x50, 0x01, 0x00, 0x01, 0x00, + 0xB8, 0x02, 0x00, 0x20, 0x70, 0xB5, 0x04, 0x46, + 0x81, 0x00, 0x25, 0x48, 0x41, 0x58, 0x25, 0x48, + 0x0A, 0x68, 0x42, 0x60, 0x4A, 0x68, 0x82, 0x60, + 0x8A, 0x68, 0x02, 0x61, 0xCA, 0x68, 0x42, 0x61, + 0x21, 0x4D, 0x08, 0x69, 0x28, 0x62, 0x1F, 0x4A, + 0x48, 0x69, 0xC0, 0x32, 0x90, 0x61, 0x88, 0x69, + 0xD0, 0x61, 0x48, 0x6A, 0x10, 0x62, 0x88, 0x6A, + 0x50, 0x62, 0x1A, 0x48, 0xCB, 0x69, 0x40, 0x38, + 0xC3, 0x60, 0x0B, 0x6A, 0x03, 0x61, 0xCB, 0x6A, + 0x93, 0x62, 0x0B, 0x6B, 0xD3, 0x63, 0x16, 0x4B, + 0x4E, 0x6B, 0x80, 0x3B, 0x1E, 0x61, 0x8E, 0x6B, + 0x5E, 0x62, 0xCE, 0x6B, 0xD6, 0x62, 0x0A, 0x6C, + 0x1A, 0x60, 0x4A, 0x6C, 0x5A, 0x61, 0x8A, 0x6C, + 0x9A, 0x62, 0xCA, 0x6C, 0xAA, 0x62, 0x0D, 0x4B, + 0x0A, 0x6D, 0x40, 0x33, 0x5A, 0x63, 0x0B, 0x4B, + 0x4A, 0x6D, 0x80, 0x33, 0x9A, 0x60, 0x0B, 0x4A, + 0x8B, 0x6D, 0x13, 0x60, 0xC9, 0x6D, 0x51, 0x60, + 0xC2, 0x68, 0x09, 0x49, 0x0A, 0x60, 0x00, 0x69, + 0x48, 0x60, 0x20, 0x46, 0xFF, 0xF7, 0x44, 0xFE, + 0x20, 0x46, 0xFF, 0xF7, 0x71, 0xFF, 0x70, 0xBD, + 0x70, 0x02, 0x00, 0x20, 0x40, 0x10, 0x00, 0x50, + 0xC0, 0x11, 0x00, 0x50, 0x00, 0x19, 0x00, 0x50, + 0x50, 0x02, 0x00, 0x20, 0x10, 0xB5, 0x0E, 0x4C, + 0x21, 0x78, 0x81, 0x42, 0x17, 0xD0, 0x20, 0x70, + 0x01, 0x46, 0x0C, 0xA0, 0x02, 0xF0, 0x32, 0xF8, + 0xFF, 0xF7, 0x24, 0xFF, 0x20, 0x78, 0xFF, 0xF7, + 0x55, 0xFE, 0xFF, 0xF7, 0xA9, 0xFA, 0x0B, 0x48, + 0x80, 0x79, 0x01, 0x28, 0x05, 0xD1, 0x0A, 0x48, + 0x01, 0x68, 0x01, 0x22, 0xD2, 0x02, 0x11, 0x43, + 0x01, 0x60, 0xFF, 0xF7, 0xFB, 0xFE, 0x10, 0xBD, + 0x74, 0x04, 0x00, 0x20, 0x54, 0x50, 0x20, 0x53, + 0x70, 0x65, 0x65, 0x64, 0x20, 0x25, 0x64, 0x0D, + 0x0A, 0x00, 0x00, 0x00, 0x84, 0x04, 0x00, 0x20, + 0x00, 0x10, 0x00, 0x50, 0x70, 0xB5, 0x05, 0x24, + 0x64, 0x04, 0xFD, 0xF7, 0xA9, 0xFF, 0x00, 0x22, + 0x0C, 0x49, 0x0D, 0x48, 0x0D, 0x4B, 0x05, 0xE0, + 0x05, 0x78, 0xED, 0x07, 0x01, 0xD0, 0x0A, 0x72, + 0x09, 0xE0, 0x64, 0x1E, 0x0D, 0x7A, 0x02, 0x2D, + 0x02, 0xD0, 0x1D, 0x68, 0xED, 0x07, 0x02, 0xD0, + 0x00, 0x2C, 0xF1, 0xD1, 0x01, 0xE0, 0x00, 0x2C, + 0x03, 0xD1, 0x0A, 0x72, 0x04, 0xA0, 0x01, 0xF0, + 0xF1, 0xFF, 0x70, 0xBD, 0x50, 0x02, 0x00, 0x20, + 0xCE, 0x00, 0x00, 0x20, 0x00, 0x10, 0x00, 0x50, + 0x57, 0x53, 0x46, 0x20, 0x54, 0x4F, 0x0D, 0x0A, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xB5, 0x03, 0x46, + 0x00, 0x20, 0x00, 0xF0, 0xF1, 0xF8, 0x05, 0x21, + 0x09, 0x07, 0xC8, 0x69, 0x00, 0x2B, 0x03, 0xD0, + 0xC0, 0x0B, 0xC0, 0x03, 0x05, 0x4A, 0x03, 0xE0, + 0xC0, 0x0B, 0x04, 0x4A, 0xC0, 0x03, 0xFC, 0x3A, + 0x80, 0x18, 0xC8, 0x61, 0x01, 0x20, 0x00, 0xF0, + 0xDF, 0xF8, 0x00, 0xBD, 0x50, 0x71, 0x00, 0x00, + 0x03, 0x49, 0x0A, 0x68, 0x10, 0x18, 0x0A, 0x68, + 0x90, 0x42, 0xFC, 0xD1, 0x70, 0x47, 0x00, 0x00, + 0x08, 0x03, 0x00, 0x20, 0xF8, 0xB5, 0x1B, 0x4E, + 0x05, 0x46, 0x0C, 0x46, 0x80, 0x21, 0x30, 0x46, + 0xFD, 0xF7, 0x3B, 0xFC, 0x00, 0x2C, 0x04, 0xD0, + 0x31, 0x46, 0x28, 0x46, 0x00, 0xF0, 0x5C, 0xFB, + 0x04, 0xE0, 0x80, 0x22, 0x29, 0x46, 0x30, 0x46, + 0xFD, 0xF7, 0x16, 0xFC, 0x29, 0x46, 0x12, 0xA0, + 0x01, 0xF0, 0xA8, 0xFF, 0x00, 0x25, 0x14, 0x4F, + 0x13, 0xE0, 0x00, 0x24, 0x08, 0xE0, 0x68, 0x43, + 0x00, 0x19, 0x40, 0x00, 0x31, 0x5E, 0x11, 0xA0, + 0x01, 0xF0, 0x9C, 0xFF, 0x64, 0x1C, 0xE4, 0xB2, + 0x38, 0x68, 0x00, 0x7E, 0xA0, 0x42, 0xF2, 0xD8, + 0x0E, 0xA0, 0x01, 0xF0, 0x93, 0xFF, 0x6D, 0x1C, + 0xED, 0xB2, 0x38, 0x68, 0x40, 0x7E, 0xA8, 0x42, + 0xE7, 0xD8, 0x0A, 0xA0, 0x01, 0xF0, 0x8A, 0xFF, + 0xF8, 0xBD, 0x00, 0x00, 0x4C, 0x00, 0x00, 0x20, + 0x49, 0x6D, 0x61, 0x67, 0x65, 0x3A, 0x30, 0x78, + 0x25, 0x78, 0x0D, 0x0A, 0x00, 0x00, 0x00, 0x00, + 0xB8, 0x02, 0x00, 0x20, 0x25, 0x36, 0x64, 0x2C, + 0x00, 0x00, 0x00, 0x00, 0x0D, 0x0A, 0x00, 0x00, + 0x08, 0x49, 0x8A, 0x78, 0x52, 0x1E, 0x8A, 0x70, + 0x4B, 0x78, 0x0A, 0x1D, 0xD2, 0x5C, 0x02, 0x70, + 0x48, 0x78, 0x40, 0x1C, 0x48, 0x70, 0x48, 0x78, + 0x10, 0x28, 0x01, 0xD1, 0x00, 0x20, 0x48, 0x70, + 0x70, 0x47, 0x00, 0x00, 0x5C, 0x04, 0x00, 0x20, + 0xF8, 0xB5, 0x01, 0x27, 0x05, 0x46, 0xBF, 0x07, + 0x38, 0x68, 0x08, 0x21, 0x08, 0x43, 0x38, 0x60, + 0x01, 0x23, 0x15, 0x48, 0x80, 0x22, 0x02, 0x60, + 0x14, 0x48, 0x00, 0x21, 0x81, 0x70, 0x01, 0x70, + 0x41, 0x70, 0xC3, 0x70, 0x12, 0x4B, 0x98, 0x68, + 0x02, 0x21, 0x88, 0x43, 0x98, 0x60, 0xF8, 0x68, + 0x10, 0x43, 0xF8, 0x60, 0x0F, 0x4C, 0x61, 0x61, + 0x0F, 0x48, 0x00, 0x68, 0xE9, 0x00, 0x46, 0x06, + 0x0E, 0x48, 0xFD, 0xF7, 0x7F, 0xFB, 0xE0, 0x60, + 0x30, 0x20, 0xA0, 0x60, 0x06, 0x49, 0x80, 0x20, + 0x80, 0x39, 0x08, 0x60, 0x08, 0x20, 0x78, 0x60, + 0xE0, 0x68, 0x68, 0x43, 0xC1, 0x00, 0x08, 0xA0, + 0x01, 0xF0, 0x30, 0xFF, 0xF8, 0xBD, 0x00, 0x00, + 0x80, 0xE1, 0x00, 0xE0, 0x5C, 0x04, 0x00, 0x20, + 0x40, 0x09, 0x00, 0x50, 0x00, 0x02, 0x00, 0x50, + 0x00, 0x11, 0x00, 0x50, 0x00, 0x36, 0x6E, 0x01, + 0x55, 0x41, 0x52, 0x54, 0x28, 0x25, 0x64, 0x29, + 0x21, 0x0D, 0x0A, 0x00, 0x70, 0xB5, 0x14, 0x4A, + 0x91, 0x78, 0x14, 0x4C, 0x0E, 0x29, 0x02, 0xD3, + 0x61, 0x68, 0x49, 0x07, 0xFC, 0xD5, 0x61, 0x69, + 0x02, 0x25, 0xA9, 0x43, 0x61, 0x61, 0x91, 0x78, + 0x00, 0x26, 0x10, 0x29, 0x0D, 0xD2, 0x0C, 0x49, + 0x13, 0x78, 0x09, 0x1D, 0xC8, 0x54, 0x10, 0x78, + 0x40, 0x1C, 0x10, 0x70, 0x10, 0x78, 0x10, 0x28, + 0x00, 0xD1, 0x16, 0x70, 0x90, 0x78, 0x40, 0x1C, + 0x90, 0x70, 0xD0, 0x78, 0x00, 0x28, 0x03, 0xD0, + 0xD6, 0x70, 0x20, 0x46, 0xFF, 0xF7, 0x80, 0xFF, + 0x60, 0x69, 0x28, 0x43, 0x60, 0x61, 0x70, 0xBD, + 0x5C, 0x04, 0x00, 0x20, 0x00, 0x02, 0x00, 0x50, + 0x01, 0x21, 0x89, 0x07, 0x00, 0xB5, 0x8A, 0x14, + 0x00, 0x28, 0x08, 0x68, 0x04, 0xD0, 0x10, 0x43, + 0x08, 0x60, 0xFD, 0xF7, 0x81, 0xFE, 0x00, 0xBD, + 0x90, 0x43, 0x08, 0x60, 0x00, 0xBD, 0x00, 0x00, + 0xFE, 0xB5, 0x63, 0x49, 0x61, 0x48, 0x09, 0x68, + 0x00, 0x78, 0x90, 0x31, 0x4A, 0x7B, 0x0B, 0x7B, + 0x11, 0x02, 0x19, 0x43, 0x5F, 0x4B, 0x00, 0x24, + 0x04, 0x25, 0x0F, 0x26, 0x5A, 0x22, 0x1C, 0x5F, + 0x00, 0x28, 0x01, 0xD0, 0x01, 0x28, 0x15, 0xD1, + 0x58, 0x4B, 0x02, 0x27, 0x38, 0x43, 0x18, 0x70, + 0x59, 0x48, 0x00, 0x78, 0x15, 0x28, 0x14, 0xD1, + 0x58, 0x48, 0x00, 0x23, 0xC3, 0x5E, 0x99, 0x42, + 0x0F, 0xDC, 0x13, 0x20, 0xC0, 0x43, 0x84, 0x42, + 0x01, 0xDA, 0x55, 0x48, 0x02, 0x70, 0x17, 0x20, + 0x54, 0x4B, 0x18, 0x70, 0x54, 0x48, 0x00, 0x27, + 0xC7, 0x5F, 0xB9, 0x42, 0x03, 0xDA, 0x04, 0x20, + 0x02, 0xE0, 0x00, 0x20, 0xF4, 0xE7, 0x02, 0x20, + 0x00, 0x90, 0x50, 0x48, 0x40, 0x88, 0x00, 0x28, + 0x67, 0xD0, 0x46, 0x48, 0x00, 0x78, 0x01, 0x90, + 0x83, 0x07, 0x4D, 0x48, 0x01, 0x78, 0x48, 0x1C, + 0xC0, 0xB2, 0x00, 0x2B, 0x06, 0xDA, 0x10, 0x2F, + 0x04, 0xDA, 0x02, 0x21, 0x00, 0x91, 0x48, 0x49, + 0x08, 0x70, 0x19, 0xE0, 0x47, 0x4B, 0x1B, 0x78, + 0xDB, 0x07, 0x11, 0xD0, 0x44, 0x49, 0x00, 0x20, + 0x08, 0x70, 0x13, 0x20, 0xC0, 0x43, 0x84, 0x42, + 0x05, 0xDA, 0x40, 0x48, 0x80, 0x88, 0x05, 0x28, + 0x01, 0xD2, 0x41, 0x48, 0x02, 0x80, 0x30, 0x21, + 0x40, 0x48, 0xFD, 0xF7, 0x02, 0xFB, 0x03, 0xE0, + 0x3B, 0x4A, 0x05, 0x29, 0x00, 0xD2, 0x10, 0x70, + 0x39, 0x48, 0x01, 0x78, 0x00, 0x98, 0x81, 0x42, + 0x37, 0xD3, 0x3B, 0x49, 0x10, 0x2F, 0x19, 0xDA, + 0x1D, 0x20, 0xC0, 0x43, 0x84, 0x42, 0x15, 0xDD, + 0x08, 0x78, 0xF0, 0x28, 0x01, 0xD2, 0x40, 0x1C, + 0x08, 0x70, 0xC0, 0xB2, 0x02, 0x28, 0x0F, 0xD9, + 0x2C, 0x49, 0x08, 0x78, 0x16, 0x28, 0x03, 0xD2, + 0x02, 0x28, 0x01, 0xD9, 0x17, 0x20, 0x08, 0x70, + 0x27, 0x49, 0x00, 0x20, 0x08, 0x70, 0x2C, 0x49, + 0x08, 0x80, 0x01, 0xE0, 0x00, 0x20, 0x08, 0x70, + 0x01, 0x98, 0x1E, 0x4A, 0x04, 0x28, 0x12, 0xD0, + 0x22, 0x49, 0x08, 0x78, 0x18, 0x28, 0x32, 0xD2, + 0x40, 0x1C, 0xC0, 0xB2, 0x08, 0x70, 0x10, 0x2F, + 0x02, 0xDB, 0x62, 0x42, 0xBA, 0x42, 0x13, 0xDD, + 0x1E, 0x4A, 0x12, 0x7A, 0x01, 0x2A, 0x05, 0xD9, + 0x03, 0x26, 0x02, 0x25, 0x04, 0xE0, 0xFE, 0xF7, + 0xB9, 0xFA, 0xFE, 0xBD, 0x01, 0x26, 0x35, 0x46, + 0x09, 0x22, 0xD2, 0x43, 0x94, 0x42, 0x03, 0xDD, + 0x02, 0x28, 0x01, 0xD9, 0x18, 0x20, 0x08, 0x70, + 0xC1, 0xB2, 0x2B, 0x46, 0x32, 0x46, 0x19, 0xA0, + 0x01, 0xF0, 0x2C, 0xFE, 0x1C, 0x4C, 0x1D, 0x4F, + 0x00, 0x20, 0x42, 0x00, 0x11, 0x19, 0x00, 0x23, + 0xCB, 0x5E, 0xBA, 0x5E, 0x73, 0x43, 0x9A, 0x18, + 0x2A, 0x41, 0x40, 0x1C, 0xC0, 0xB2, 0x0A, 0x80, + 0x30, 0x28, 0xF2, 0xD3, 0xFE, 0xBD, 0x04, 0x20, + 0x10, 0x70, 0xFE, 0xBD, 0x87, 0x02, 0x00, 0x20, + 0xB8, 0x02, 0x00, 0x20, 0x7A, 0x04, 0x00, 0x20, + 0x70, 0x04, 0x00, 0x20, 0x78, 0x04, 0x00, 0x20, + 0x75, 0x04, 0x00, 0x20, 0x8E, 0x02, 0x00, 0x20, + 0x7C, 0x04, 0x00, 0x20, 0x84, 0x04, 0x00, 0x20, + 0xED, 0x02, 0x00, 0x20, 0xEB, 0x02, 0x00, 0x20, + 0xF0, 0x02, 0x00, 0x20, 0x0C, 0x03, 0x00, 0x20, + 0xEA, 0x02, 0x00, 0x20, 0x46, 0x61, 0x73, 0x74, + 0x4B, 0x25, 0x64, 0x28, 0x25, 0x64, 0x3A, 0x25, + 0x64, 0x29, 0x0D, 0x0A, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x20, 0xD4, 0x00, 0x00, 0x20, + 0xF8, 0xB5, 0x3F, 0x4F, 0x01, 0x24, 0x38, 0x7B, + 0x3E, 0x49, 0x0A, 0x78, 0x3E, 0x4E, 0x3F, 0x4D, + 0x90, 0x42, 0x05, 0xD1, 0x28, 0x78, 0x81, 0x28, + 0x02, 0xD0, 0x30, 0x78, 0xC0, 0x07, 0x66, 0xD0, + 0x00, 0x20, 0x30, 0x70, 0x37, 0x48, 0x00, 0x78, + 0x07, 0x28, 0x0C, 0xD3, 0x38, 0x48, 0x00, 0x68, + 0x40, 0x05, 0x40, 0x0F, 0x38, 0x73, 0x01, 0x20, + 0xFF, 0xF7, 0x06, 0xFE, 0x00, 0x20, 0x30, 0x70, + 0x38, 0x7B, 0x02, 0x28, 0x18, 0xD0, 0xA8, 0x79, + 0x00, 0x28, 0x01, 0xD0, 0xFE, 0xF7, 0x36, 0xFE, + 0x38, 0x7B, 0x2C, 0x4F, 0x38, 0x70, 0x81, 0x20, + 0x28, 0x70, 0x2E, 0x4A, 0x01, 0x21, 0x10, 0x88, + 0x09, 0x03, 0x08, 0x43, 0x10, 0x80, 0x38, 0x78, + 0x03, 0x00, 0xFD, 0xF7, 0x9F, 0xFA, 0x07, 0x25, + 0x35, 0x35, 0x3B, 0x0B, 0x18, 0x0B, 0x3B, 0x00, + 0x83, 0x20, 0x28, 0x70, 0x05, 0x20, 0xFF, 0xF7, + 0x3D, 0xFC, 0xF8, 0xBD, 0x24, 0xA0, 0x01, 0xF0, + 0xA9, 0xFD, 0x01, 0x20, 0xFE, 0xF7, 0xB4, 0xFD, + 0x00, 0x20, 0xFF, 0xF7, 0x33, 0xFC, 0x00, 0x28, + 0x1C, 0xD0, 0x01, 0x20, 0x18, 0xE0, 0x20, 0xA0, + 0x01, 0xF0, 0x9C, 0xFD, 0x01, 0x20, 0xFE, 0xF7, + 0xA7, 0xFD, 0x01, 0x20, 0xFF, 0xF7, 0x26, 0xFC, + 0x00, 0x28, 0x0F, 0xD0, 0x04, 0x20, 0x0B, 0xE0, + 0x1B, 0xA0, 0x01, 0xF0, 0x8F, 0xFD, 0x00, 0x20, + 0xFE, 0xF7, 0x9A, 0xFD, 0x02, 0x20, 0xFF, 0xF7, + 0x19, 0xFC, 0x00, 0x28, 0x02, 0xD0, 0x02, 0x20, + 0x28, 0x70, 0x07, 0xE0, 0x00, 0x24, 0x05, 0xE0, + 0x83, 0x20, 0x28, 0x70, 0x05, 0x20, 0xFF, 0xF7, + 0x0D, 0xFC, 0x04, 0x46, 0x2A, 0x78, 0x39, 0x78, + 0x11, 0xA0, 0x01, 0xF0, 0x77, 0xFD, 0x30, 0x78, + 0xC0, 0x07, 0x02, 0xD0, 0x81, 0x20, 0x28, 0x70, + 0x00, 0x24, 0x20, 0x46, 0xF8, 0xBD, 0x00, 0x00, + 0x50, 0x02, 0x00, 0x20, 0xE5, 0x02, 0x00, 0x20, + 0xCE, 0x00, 0x00, 0x20, 0x84, 0x04, 0x00, 0x20, + 0x00, 0x11, 0x00, 0x50, 0xF6, 0x02, 0x00, 0x20, + 0x44, 0x41, 0x0D, 0x0A, 0x00, 0x00, 0x00, 0x00, + 0x44, 0x49, 0x0D, 0x0A, 0x00, 0x00, 0x00, 0x00, + 0x44, 0x53, 0x54, 0x42, 0x0D, 0x0A, 0x00, 0x00, + 0x44, 0x53, 0x50, 0x3D, 0x25, 0x64, 0x2C, 0x50, + 0x57, 0x52, 0x3D, 0x25, 0x64, 0x20, 0x0D, 0x0A, + 0x00, 0x00, 0x00, 0x00, 0x70, 0xB5, 0x0E, 0x4D, + 0x28, 0x68, 0xFE, 0xF7, 0xFB, 0xF8, 0x04, 0x00, + 0x07, 0xD0, 0x80, 0x21, 0x0B, 0x48, 0xFD, 0xF7, + 0xC4, 0xF9, 0x0A, 0x49, 0x28, 0x68, 0x00, 0xF0, + 0xE7, 0xF8, 0x09, 0x48, 0x00, 0x88, 0x81, 0x07, + 0x06, 0xD0, 0x00, 0x07, 0x04, 0xD5, 0x80, 0x21, + 0x04, 0x48, 0xFD, 0xF7, 0xB6, 0xF9, 0x00, 0x24, + 0xFF, 0xF7, 0x52, 0xFE, 0x20, 0x46, 0x70, 0xBD, + 0xF8, 0x02, 0x00, 0x20, 0x4C, 0x07, 0x00, 0x20, + 0xF6, 0x02, 0x00, 0x20, 0x70, 0xB5, 0x05, 0x46, + 0x01, 0x24, 0xFE, 0xF7, 0xFF, 0xFB, 0x00, 0x2D, + 0x09, 0xD0, 0xF0, 0x20, 0xFD, 0xF7, 0xD4, 0xF9, + 0xFE, 0xF7, 0xC6, 0xFC, 0xFE, 0xF7, 0x5C, 0xFC, + 0x00, 0x20, 0xFE, 0xF7, 0x25, 0xFD, 0xFE, 0xF7, + 0xD9, 0xFD, 0xFF, 0xF7, 0xB5, 0xFA, 0x20, 0x46, + 0x70, 0xBD, 0x00, 0x00, 0x70, 0xB5, 0x35, 0x49, + 0x08, 0x78, 0x00, 0x28, 0x01, 0xD0, 0x40, 0x1E, + 0x08, 0x70, 0x33, 0x4B, 0x33, 0x49, 0x18, 0x78, + 0x09, 0x78, 0x00, 0x25, 0x32, 0x4A, 0x33, 0x4C, + 0x00, 0x28, 0x07, 0xD1, 0x5E, 0x78, 0x00, 0x2E, + 0x04, 0xD1, 0x9B, 0x78, 0x00, 0x2B, 0x01, 0xD1, + 0x00, 0x29, 0x12, 0xD0, 0x65, 0x80, 0xA3, 0x88, + 0xE1, 0x26, 0xB6, 0x00, 0xB3, 0x42, 0x08, 0xD2, + 0x01, 0x43, 0x06, 0xD0, 0x2A, 0x49, 0x09, 0x78, + 0x04, 0x29, 0xA1, 0x88, 0x00, 0xD1, 0x49, 0x1C, + 0xA1, 0x80, 0x00, 0x28, 0x19, 0xD0, 0x15, 0x70, + 0x1C, 0xE0, 0xA0, 0x88, 0x00, 0x28, 0x05, 0xD0, + 0x20, 0x7A, 0xFF, 0x28, 0x02, 0xD2, 0x20, 0x7A, + 0x40, 0x1C, 0x20, 0x72, 0xA5, 0x80, 0x60, 0x88, + 0x01, 0x21, 0x09, 0x03, 0x88, 0x42, 0x02, 0xD2, + 0x60, 0x88, 0x40, 0x1C, 0x60, 0x80, 0x1D, 0x49, + 0x08, 0x78, 0x00, 0x28, 0x01, 0xD0, 0x40, 0x1E, + 0x08, 0x70, 0x10, 0x78, 0x00, 0x28, 0x01, 0xD0, + 0x40, 0x1E, 0x10, 0x70, 0x20, 0x78, 0x01, 0x28, + 0x16, 0xD0, 0x02, 0x28, 0x14, 0xD0, 0x04, 0x28, + 0x12, 0xD0, 0x82, 0x28, 0x12, 0xD1, 0x14, 0x4E, + 0x30, 0x78, 0x30, 0x28, 0x0E, 0xD1, 0x13, 0xA0, + 0x01, 0xF0, 0xB8, 0xFC, 0x05, 0x20, 0xFF, 0xF7, + 0xEB, 0xFC, 0xFD, 0xF7, 0x37, 0xFC, 0x13, 0xA0, + 0x01, 0xF0, 0xB0, 0xFC, 0x35, 0x70, 0x01, 0xE0, + 0x00, 0xF0, 0xDE, 0xF8, 0x14, 0x48, 0x00, 0x78, + 0xC0, 0x07, 0x01, 0xD0, 0x81, 0x20, 0x20, 0x70, + 0x70, 0xBD, 0x00, 0x00, 0x70, 0x04, 0x00, 0x20, + 0xA0, 0x04, 0x00, 0x20, 0x73, 0x04, 0x00, 0x20, + 0x72, 0x04, 0x00, 0x20, 0x84, 0x04, 0x00, 0x20, + 0xE5, 0x02, 0x00, 0x20, 0x75, 0x04, 0x00, 0x20, + 0x88, 0x02, 0x00, 0x20, 0x73, 0x6C, 0x65, 0x65, + 0x70, 0x20, 0x6D, 0x6F, 0x64, 0x65, 0x0D, 0x0A, + 0x00, 0x00, 0x00, 0x00, 0x6C, 0x65, 0x61, 0x76, + 0x65, 0x20, 0x73, 0x6C, 0x65, 0x65, 0x70, 0x20, + 0x6D, 0x6F, 0x64, 0x65, 0x0D, 0x0A, 0x00, 0x00, + 0xCE, 0x00, 0x00, 0x20, 0x70, 0xB5, 0x10, 0x4D, + 0x68, 0x78, 0x00, 0x07, 0x01, 0xD5, 0x00, 0xF0, + 0x45, 0xF9, 0x0E, 0x4C, 0x60, 0x22, 0x0E, 0x48, + 0x21, 0x68, 0xFD, 0xF7, 0xDD, 0xF8, 0xFD, 0xF7, + 0x2D, 0xFA, 0x68, 0x78, 0x96, 0x21, 0x08, 0x42, + 0x01, 0xD0, 0x00, 0xF0, 0x37, 0xF9, 0x09, 0x48, + 0x00, 0x78, 0x00, 0x06, 0x06, 0xD4, 0x01, 0x21, + 0x20, 0x68, 0xFE, 0xF7, 0x2F, 0xF9, 0x20, 0x68, + 0xFD, 0xF7, 0xF0, 0xFA, 0x70, 0xBD, 0x00, 0x00, + 0x88, 0x02, 0x00, 0x20, 0xF8, 0x02, 0x00, 0x20, + 0xD4, 0x00, 0x00, 0x20, 0x86, 0x02, 0x00, 0x20, + 0x30, 0xB5, 0x08, 0x4A, 0x14, 0x68, 0x1B, 0x34, + 0x00, 0x22, 0xA3, 0x5C, 0x41, 0x2B, 0x03, 0xD0, + 0x55, 0x00, 0x45, 0x5B, 0x5B, 0x00, 0xCD, 0x52, + 0x52, 0x1C, 0xD2, 0xB2, 0x30, 0x2A, 0xF4, 0xD3, + 0x30, 0xBD, 0x00, 0x00, 0xB8, 0x02, 0x00, 0x20, + 0x01, 0x20, 0x80, 0x07, 0x40, 0x69, 0x40, 0x05, + 0x01, 0xD5, 0x01, 0x20, 0x70, 0x47, 0x00, 0x20, + 0x70, 0x47, 0x00, 0x00, 0x10, 0xB5, 0xFF, 0xF7, + 0x25, 0xFC, 0x04, 0x48, 0x00, 0x78, 0x00, 0x28, + 0x02, 0xD1, 0x01, 0x20, 0xFF, 0xF7, 0xF2, 0xF9, + 0x10, 0xBD, 0x00, 0x00, 0x90, 0x02, 0x00, 0x20, + 0x36, 0x03, 0x35, 0x03, 0x2C, 0x03, 0x9C, 0x03, + 0xE9, 0x02, 0x28, 0x03, 0x29, 0x03, 0x1C, 0x03, + 0x9D, 0x03, 0x2F, 0x03, 0x4A, 0x03, 0x4D, 0x03, + 0xFF, 0x03, 0xFF, 0x03, 0xFF, 0x03, 0xFF, 0x03, + 0x3D, 0x03, 0x3C, 0x03, 0x2C, 0x03, 0x9C, 0x03, + 0xD3, 0x02, 0x28, 0x03, 0x29, 0x03, 0xD6, 0x02, + 0x9E, 0x03, 0x30, 0x03, 0x37, 0x03, 0x3A, 0x03, + 0xFF, 0x03, 0xFF, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x10, 0xB5, 0x00, 0x20, 0xFD, 0xF7, 0xCC, 0xFA, + 0x05, 0x49, 0x08, 0x20, 0x08, 0x70, 0x05, 0x49, + 0x06, 0x20, 0x08, 0x70, 0x01, 0x20, 0x04, 0x49, + 0x80, 0x02, 0x08, 0x80, 0x10, 0xBD, 0x00, 0x00, + 0xE5, 0x02, 0x00, 0x20, 0x71, 0x04, 0x00, 0x20, + 0xF2, 0x02, 0x00, 0x20, 0x06, 0x49, 0x01, 0x20, + 0x08, 0x70, 0x06, 0x49, 0x08, 0x20, 0x08, 0x70, + 0x05, 0x49, 0x06, 0x20, 0x08, 0x70, 0x01, 0x20, + 0x04, 0x49, 0x80, 0x02, 0x08, 0x80, 0x70, 0x47, + 0xE7, 0x02, 0x00, 0x20, 0xE5, 0x02, 0x00, 0x20, + 0x71, 0x04, 0x00, 0x20, 0xF2, 0x02, 0x00, 0x20, + 0xF8, 0xB5, 0x3E, 0x4C, 0x3E, 0x4A, 0x20, 0x88, + 0x3E, 0x4D, 0xC1, 0x04, 0x0C, 0xD5, 0x51, 0x10, + 0x88, 0x43, 0x20, 0x80, 0x29, 0x78, 0x00, 0x29, + 0x03, 0xD0, 0x01, 0x29, 0x01, 0xD0, 0x02, 0x29, + 0x65, 0xD1, 0x10, 0x43, 0x20, 0x80, 0x62, 0xE0, + 0x81, 0x04, 0x03, 0xD5, 0x90, 0x43, 0x20, 0x80, + 0xFE, 0xF7, 0xF8, 0xFB, 0x20, 0x88, 0x81, 0x07, + 0x0E, 0xD0, 0x01, 0x04, 0x57, 0xD5, 0x40, 0x04, + 0x40, 0x0C, 0x20, 0x80, 0x81, 0x07, 0x01, 0xD5, + 0x00, 0x20, 0x02, 0xE0, 0xC0, 0x07, 0x4E, 0xD0, + 0x01, 0x20, 0xFF, 0xF7, 0x67, 0xFB, 0x4A, 0xE0, + 0x00, 0x04, 0x5A, 0x21, 0x2A, 0x4F, 0x2B, 0x4E, + 0x00, 0x28, 0x05, 0xDA, 0x38, 0x78, 0x00, 0x28, + 0x00, 0xD0, 0x31, 0x80, 0x00, 0x20, 0x20, 0x80, + 0x27, 0x48, 0x40, 0x88, 0x00, 0x28, 0x19, 0xD0, + 0x26, 0x48, 0x00, 0x78, 0x04, 0x28, 0x15, 0xD3, + 0x25, 0x4A, 0x00, 0x20, 0x10, 0x5E, 0x27, 0x22, + 0xD2, 0x43, 0x90, 0x42, 0x0E, 0xDB, 0x23, 0x48, + 0x00, 0x68, 0x90, 0x30, 0x42, 0x7B, 0x03, 0x7B, + 0x10, 0x02, 0x18, 0x43, 0x42, 0x00, 0x80, 0x18, + 0x1F, 0x4A, 0x00, 0x23, 0xD3, 0x5E, 0x80, 0x08, + 0x98, 0x42, 0x01, 0xDA, 0x31, 0x80, 0x04, 0xE0, + 0x30, 0x88, 0x00, 0x28, 0x1B, 0xD0, 0x5A, 0x28, + 0x08, 0xD1, 0x38, 0x78, 0x00, 0x28, 0x05, 0xD1, + 0x18, 0xA0, 0x01, 0xF0, 0x6B, 0xFB, 0x01, 0x20, + 0xFF, 0xF7, 0x2C, 0xFB, 0x28, 0x78, 0x02, 0x28, + 0x04, 0xD1, 0x30, 0x88, 0x5A, 0x28, 0x01, 0xD1, + 0x2D, 0x20, 0x30, 0x80, 0x30, 0x88, 0x40, 0x1E, + 0x80, 0xB2, 0x30, 0x80, 0x39, 0x78, 0x00, 0x29, + 0x01, 0xD0, 0x00, 0x28, 0x02, 0xD0, 0xFD, 0xF7, + 0x2B, 0xF9, 0xF8, 0xBD, 0x0D, 0xA0, 0x01, 0xF0, + 0x51, 0xFB, 0xA5, 0xE7, 0xF6, 0x02, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x00, 0x85, 0x02, 0x00, 0x20, + 0x74, 0x04, 0x00, 0x20, 0x7E, 0x04, 0x00, 0x20, + 0x84, 0x04, 0x00, 0x20, 0x87, 0x02, 0x00, 0x20, + 0x76, 0x04, 0x00, 0x20, 0xB8, 0x02, 0x00, 0x20, + 0x78, 0x04, 0x00, 0x20, 0x53, 0x50, 0x55, 0x0D, + 0x0A, 0x00, 0x00, 0x00, 0x53, 0x50, 0x44, 0x0D, + 0x0A, 0x00, 0x00, 0x00, 0xF8, 0xB5, 0x55, 0x4E, + 0x30, 0x78, 0x00, 0x28, 0x7E, 0xD1, 0x70, 0x78, + 0x44, 0x08, 0x70, 0x88, 0x64, 0x00, 0x00, 0x0A, + 0xC0, 0x07, 0x03, 0xD0, 0x70, 0x88, 0x00, 0x0A, + 0x00, 0x02, 0x04, 0x43, 0x30, 0x78, 0x00, 0x28, + 0x01, 0xD1, 0x70, 0x88, 0x04, 0x43, 0x70, 0x78, + 0xC0, 0x07, 0x6B, 0xD1, 0x4A, 0x48, 0x42, 0x2C, + 0x41, 0x69, 0x2F, 0xD0, 0x0F, 0xDC, 0x49, 0x4F, + 0x08, 0x2C, 0x2F, 0xD0, 0x05, 0xDC, 0x02, 0x2C, + 0x1A, 0xD0, 0x04, 0x2C, 0x7C, 0xD1, 0x46, 0x4F, + 0x16, 0xE0, 0x10, 0x2C, 0x26, 0xD0, 0x20, 0x2C, + 0x76, 0xD1, 0x80, 0x22, 0x5B, 0xE0, 0x50, 0x2C, + 0x04, 0xD0, 0x05, 0xDC, 0x44, 0x2C, 0x1B, 0xD0, + 0x48, 0x2C, 0x6D, 0xD1, 0x87, 0x69, 0x05, 0xE0, + 0xFF, 0x3C, 0x09, 0x3C, 0x01, 0xD0, 0x08, 0x2C, + 0x66, 0xD1, 0x0F, 0x46, 0x00, 0x2F, 0x63, 0xD0, + 0x80, 0x21, 0x3A, 0x48, 0xFC, 0xF7, 0x75, 0xFF, + 0x39, 0x48, 0x00, 0x78, 0x00, 0x06, 0x5C, 0xD5, + 0x60, 0x22, 0x39, 0x46, 0x35, 0x48, 0xFC, 0xF7, + 0x53, 0xFF, 0x5A, 0xE0, 0x35, 0x4F, 0xEF, 0xE7, + 0x35, 0x4F, 0xED, 0xE7, 0x60, 0x22, 0x35, 0x48, + 0xFC, 0xF7, 0x4A, 0xFF, 0x33, 0x4D, 0x20, 0x07, + 0x0A, 0xD5, 0x2F, 0x20, 0x41, 0x00, 0xCB, 0x19, + 0x6A, 0x5A, 0x1B, 0x88, 0x40, 0x1E, 0xD2, 0x1A, + 0x40, 0xB2, 0x6A, 0x52, 0x00, 0x28, 0xF5, 0xDA, + 0x00, 0x21, 0x2C, 0x48, 0xFD, 0xF7, 0x9A, 0xFF, + 0x2A, 0x48, 0xFD, 0xF7, 0x5B, 0xF9, 0x26, 0x48, + 0x00, 0x78, 0x00, 0x06, 0x04, 0xD5, 0x60, 0x22, + 0x26, 0x49, 0x27, 0x48, 0xFC, 0xF7, 0x2C, 0xFF, + 0x24, 0x48, 0xFD, 0xF7, 0x73, 0xFE, 0x20, 0x07, + 0x0A, 0xD5, 0x2F, 0x20, 0x41, 0x00, 0xCB, 0x19, + 0x6A, 0x5A, 0x1B, 0x88, 0x40, 0x1E, 0xD2, 0x18, + 0x40, 0xB2, 0x6A, 0x52, 0x00, 0x28, 0xF5, 0xDA, + 0x19, 0x48, 0x00, 0xE0, 0x1C, 0xE0, 0x00, 0x78, + 0x00, 0x06, 0x05, 0xD5, 0x60, 0x22, 0x19, 0x49, + 0x14, 0x48, 0xFC, 0xF7, 0x11, 0xFF, 0x0F, 0xE0, + 0x18, 0x49, 0x12, 0x4B, 0x0A, 0x68, 0x00, 0x20, + 0x11, 0x18, 0xC9, 0x7E, 0x41, 0x29, 0x03, 0xD0, + 0x44, 0x00, 0x2C, 0x5B, 0x49, 0x00, 0x5C, 0x52, + 0x40, 0x1C, 0x40, 0xB2, 0x30, 0x28, 0xF3, 0xDB, + 0x70, 0x78, 0x01, 0x21, 0x08, 0x43, 0x70, 0x70, + 0xF8, 0xBD, 0x08, 0x49, 0x38, 0x46, 0xFF, 0xF7, + 0x37, 0xFE, 0x71, 0x78, 0x01, 0x20, 0x01, 0x43, + 0x71, 0x70, 0xF8, 0xBD, 0x88, 0x02, 0x00, 0x20, + 0xE4, 0x02, 0x00, 0x20, 0x00, 0x00, 0x01, 0x20, + 0x00, 0x20, 0x00, 0x50, 0x9C, 0x01, 0x00, 0x20, + 0x86, 0x02, 0x00, 0x20, 0x00, 0x00, 0x02, 0x20, + 0x00, 0x30, 0x00, 0x50, 0x4C, 0x07, 0x00, 0x20, + 0x4C, 0x00, 0x00, 0x20, 0xB8, 0x02, 0x00, 0x20, + 0xF7, 0xB5, 0x45, 0x49, 0x84, 0xB0, 0x00, 0x28, + 0x13, 0xD0, 0x44, 0x48, 0x00, 0x88, 0x84, 0xB2, + 0x05, 0x98, 0x00, 0x28, 0x03, 0xD0, 0x90, 0x68, + 0x01, 0x26, 0x10, 0x60, 0x00, 0xE0, 0x26, 0x46, + 0x20, 0x20, 0x03, 0x90, 0x08, 0x68, 0x28, 0x27, + 0x80, 0x30, 0x00, 0x7C, 0x02, 0x90, 0x3C, 0x48, + 0x13, 0xE0, 0x3C, 0x48, 0x00, 0x88, 0x84, 0xB2, + 0x05, 0x98, 0x00, 0x28, 0x03, 0xD0, 0x90, 0x68, + 0x01, 0x26, 0x10, 0x60, 0x00, 0xE0, 0x26, 0x46, + 0x20, 0x20, 0x03, 0x90, 0x08, 0x68, 0x28, 0x27, + 0x80, 0x30, 0x40, 0x7C, 0x40, 0x08, 0x02, 0x90, + 0x33, 0x48, 0x05, 0x78, 0x10, 0x68, 0x00, 0x28, + 0x01, 0xDC, 0x01, 0x20, 0x10, 0x60, 0x53, 0x68, + 0x01, 0x46, 0xC0, 0x18, 0x00, 0x04, 0x40, 0x0C, + 0x83, 0x42, 0x01, 0xDB, 0x18, 0x46, 0x00, 0xE0, + 0x50, 0x60, 0x41, 0x18, 0x68, 0x43, 0xFC, 0xF7, + 0x87, 0xFE, 0x00, 0xB2, 0x00, 0x90, 0x29, 0x48, + 0x00, 0x88, 0xC1, 0x00, 0x40, 0x18, 0xC0, 0x08, + 0x28, 0x1A, 0x81, 0xB2, 0x00, 0x98, 0x01, 0x91, + 0x88, 0x42, 0x12, 0xDD, 0x09, 0x21, 0xE8, 0x08, + 0xFC, 0xF7, 0x76, 0xFE, 0x01, 0x06, 0x09, 0x0E, + 0x00, 0xD1, 0x01, 0x21, 0x01, 0x9A, 0x00, 0x98, + 0x80, 0x1A, 0xFC, 0xF7, 0x6D, 0xFE, 0x20, 0x30, + 0xC0, 0xB2, 0x28, 0x28, 0x02, 0xD2, 0x07, 0x46, + 0x00, 0xE0, 0x03, 0x9F, 0x00, 0x98, 0x69, 0x00, + 0x47, 0x43, 0x08, 0x37, 0x38, 0x11, 0x08, 0x1A, + 0x00, 0xB2, 0x00, 0x28, 0x01, 0xDA, 0x00, 0x20, + 0x02, 0xE0, 0xA8, 0x42, 0x00, 0xDD, 0x28, 0x46, + 0x05, 0x99, 0x00, 0x29, 0x01, 0xD0, 0x30, 0x18, + 0x00, 0xE0, 0x30, 0x1A, 0x02, 0x99, 0x65, 0x08, + 0x49, 0x00, 0x40, 0x1B, 0x61, 0x1A, 0x60, 0x43, + 0x4A, 0x10, 0x80, 0x18, 0xFC, 0xF7, 0x48, 0xFE, + 0x28, 0x18, 0x01, 0x28, 0x01, 0xDA, 0x01, 0x20, + 0x02, 0xE0, 0xA0, 0x42, 0x00, 0xDD, 0x20, 0x46, + 0x40, 0x1E, 0x00, 0xB2, 0x07, 0xB0, 0xF0, 0xBD, + 0xB8, 0x02, 0x00, 0x20, 0x42, 0x00, 0x00, 0x20, + 0x2E, 0x00, 0x00, 0x20, 0x44, 0x00, 0x00, 0x20, + 0x2F, 0x00, 0x00, 0x20, 0x40, 0x00, 0x00, 0x20, + 0xF7, 0xB5, 0x1A, 0x49, 0x09, 0x68, 0x80, 0x31, + 0x00, 0x28, 0x06, 0xD0, 0x18, 0x48, 0x0E, 0x7C, + 0x00, 0x88, 0xCD, 0x7C, 0x84, 0xB2, 0x4F, 0x7D, + 0x05, 0xE0, 0x16, 0x48, 0x4E, 0x7C, 0x00, 0x88, + 0x0D, 0x7D, 0x8F, 0x7D, 0x84, 0xB2, 0x10, 0x69, + 0xD1, 0x68, 0x68, 0x43, 0x4A, 0x10, 0x80, 0x18, + 0xFC, 0xF7, 0x16, 0xFE, 0xC1, 0x19, 0x01, 0x98, + 0x40, 0x1E, 0x68, 0x43, 0x08, 0x18, 0x69, 0x08, + 0x40, 0x1A, 0x65, 0x08, 0x71, 0x00, 0x40, 0x1B, + 0x61, 0x1A, 0x60, 0x43, 0x4A, 0x10, 0x80, 0x18, + 0xFC, 0xF7, 0x06, 0xFE, 0x28, 0x18, 0x01, 0x28, + 0x01, 0xDA, 0x01, 0x20, 0x02, 0xE0, 0xA0, 0x42, + 0x00, 0xDD, 0x20, 0x46, 0x40, 0x1E, 0x00, 0xB2, + 0xFE, 0xBD, 0x00, 0x00, 0xB8, 0x02, 0x00, 0x20, + 0x42, 0x00, 0x00, 0x20, 0x44, 0x00, 0x00, 0x20, + 0x05, 0x48, 0x00, 0x68, 0x90, 0x30, 0x41, 0x7B, + 0x02, 0x7B, 0x08, 0x02, 0x10, 0x43, 0x03, 0x4A, + 0x02, 0x21, 0x11, 0x80, 0x00, 0xB2, 0x70, 0x47, + 0xB8, 0x02, 0x00, 0x20, 0x26, 0x00, 0x00, 0x20, + 0x70, 0xB5, 0x1A, 0x4D, 0x16, 0x20, 0x28, 0x70, + 0x68, 0x78, 0x29, 0x46, 0x00, 0x24, 0x14, 0x31, + 0x02, 0x28, 0x0C, 0xD8, 0x00, 0x28, 0x05, 0xD1, + 0xA8, 0x78, 0x00, 0x28, 0x02, 0xD0, 0x08, 0x46, + 0x1C, 0x30, 0x04, 0x70, 0x48, 0x88, 0x00, 0x28, + 0x01, 0xD1, 0x03, 0x20, 0x68, 0x71, 0xAC, 0x70, + 0x2C, 0x81, 0xEC, 0x80, 0xAC, 0x81, 0x6C, 0x81, + 0x4C, 0x80, 0x8C, 0x80, 0x0C, 0x72, 0x0C, 0x48, + 0x44, 0x70, 0x84, 0x70, 0x04, 0x70, 0x09, 0x48, + 0xB4, 0x21, 0xD0, 0x30, 0xFC, 0xF7, 0xE9, 0xFD, + 0x06, 0x48, 0xA0, 0x21, 0x30, 0x30, 0xFC, 0xF7, + 0xE4, 0xFD, 0xEC, 0x70, 0x5A, 0x20, 0xE8, 0x81, + 0x2C, 0x71, 0x2C, 0x61, 0x01, 0xF0, 0x66, 0xFC, + 0x70, 0xBD, 0x00, 0x00, 0x70, 0x04, 0x00, 0x20, + 0x90, 0x02, 0x00, 0x20, 0xFF, 0xB5, 0xA5, 0xB0, + 0x18, 0xB2, 0x17, 0x90, 0x00, 0x20, 0x0D, 0x90, + 0x25, 0x98, 0x01, 0x78, 0x03, 0x91, 0x26, 0x98, + 0x03, 0x78, 0xFE, 0x4A, 0x04, 0x93, 0x10, 0x68, + 0x01, 0x46, 0x90, 0x31, 0xCB, 0x7A, 0x89, 0x7A, + 0x1B, 0x02, 0x0B, 0x43, 0xA0, 0x21, 0x1C, 0x93, + 0x0B, 0x56, 0x00, 0x21, 0x1F, 0x93, 0x18, 0x91, + 0x03, 0x7E, 0x07, 0x93, 0x41, 0x7E, 0x1A, 0x91, + 0x03, 0x99, 0x59, 0x43, 0x04, 0x9B, 0xC9, 0x18, + 0x89, 0xB2, 0x16, 0x91, 0x55, 0x21, 0x0A, 0x91, + 0x38, 0x21, 0x13, 0x91, 0x16, 0x99, 0xF0, 0x4B, + 0x49, 0x00, 0x22, 0x91, 0x59, 0x5E, 0x1C, 0x9B, + 0x99, 0x42, 0x04, 0xDA, 0x0A, 0x23, 0x59, 0x43, + 0x49, 0x11, 0x11, 0x91, 0x07, 0xE0, 0xCB, 0x00, + 0xC9, 0x18, 0x49, 0x11, 0x11, 0x91, 0x40, 0x21, + 0x0A, 0x91, 0x2A, 0x21, 0x13, 0x91, 0xB0, 0x30, + 0x41, 0x7A, 0x02, 0x7A, 0x08, 0x02, 0xE5, 0x49, + 0x10, 0x43, 0x0A, 0x88, 0xE4, 0x49, 0x90, 0x42, + 0x02, 0xD2, 0x08, 0x78, 0x01, 0x28, 0x03, 0xD0, + 0xE2, 0x48, 0x00, 0x78, 0x00, 0x28, 0x0B, 0xD0, + 0x0A, 0x98, 0x42, 0x00, 0x80, 0x18, 0xC0, 0x05, + 0x00, 0x0E, 0x0A, 0x90, 0x13, 0x98, 0x42, 0x00, + 0x80, 0x18, 0xC0, 0x05, 0x00, 0x0E, 0x13, 0x90, + 0x07, 0x9A, 0x04, 0x98, 0x10, 0x1A, 0x40, 0x1E, + 0x23, 0x90, 0x16, 0x98, 0x02, 0x90, 0x00, 0x20, + 0x08, 0x90, 0x0C, 0x90, 0x32, 0x21, 0xD6, 0x48, + 0xFC, 0xF7, 0x6F, 0xFD, 0x0A, 0x21, 0xD5, 0x48, + 0xFC, 0xF7, 0x6B, 0xFD, 0x0A, 0x21, 0xD4, 0x48, + 0xFC, 0xF7, 0x67, 0xFD, 0xD2, 0x48, 0x69, 0x46, + 0x00, 0x1D, 0x12, 0x90, 0xCF, 0x48, 0x00, 0x1D, + 0x09, 0x90, 0x00, 0x20, 0x48, 0x70, 0xCA, 0x48, + 0x00, 0x78, 0x01, 0x28, 0x01, 0xD9, 0x02, 0x20, + 0x00, 0xE0, 0x0E, 0x20, 0x08, 0x70, 0x88, 0x70, + 0x01, 0x20, 0x00, 0x24, 0x0E, 0x90, 0x2C, 0xE0, + 0x00, 0x2C, 0x2A, 0xD0, 0x0E, 0x98, 0x00, 0x28, + 0x03, 0x98, 0x13, 0xD0, 0x84, 0x42, 0x06, 0xD9, + 0x16, 0x98, 0x00, 0x24, 0x02, 0x90, 0x0E, 0x94, + 0x68, 0x46, 0x01, 0x70, 0x6D, 0xE1, 0x07, 0x99, + 0x02, 0x98, 0x40, 0x1A, 0x80, 0xB2, 0x02, 0x90, + 0x02, 0x20, 0x00, 0x1B, 0x41, 0x00, 0xBB, 0x48, + 0x08, 0x18, 0x0C, 0xE0, 0x01, 0x19, 0x1A, 0x98, + 0x81, 0x42, 0x77, 0xD0, 0x07, 0x99, 0x02, 0x98, + 0x40, 0x18, 0x80, 0xB2, 0x02, 0x90, 0xB5, 0x48, + 0x61, 0x00, 0x08, 0x18, 0x00, 0x1D, 0x69, 0x46, + 0x09, 0x90, 0x08, 0x78, 0x48, 0x70, 0x00, 0x20, + 0x08, 0x70, 0x00, 0x20, 0x19, 0x90, 0x15, 0x90, + 0x12, 0xE0, 0x00, 0x28, 0x10, 0xD0, 0x04, 0x98, + 0x14, 0x90, 0x00, 0x20, 0x0F, 0x90, 0x68, 0x46, + 0x40, 0x78, 0x01, 0x25, 0x08, 0x27, 0x18, 0x21, + 0x08, 0x40, 0x0B, 0x90, 0x14, 0x98, 0x02, 0x28, + 0x01, 0xD9, 0x02, 0x20, 0x14, 0x90, 0x17, 0xE1, + 0x23, 0x98, 0x00, 0x25, 0xC0, 0xB2, 0x14, 0x90, + 0x01, 0x20, 0x0F, 0x90, 0x68, 0x46, 0x40, 0x78, + 0x04, 0x27, 0x40, 0x07, 0x40, 0x0F, 0xEC, 0xE7, + 0x0F, 0x98, 0x00, 0x28, 0x01, 0xD0, 0x10, 0x95, + 0x01, 0xE0, 0x68, 0x42, 0x10, 0x90, 0x0B, 0x98, + 0xB8, 0x43, 0x0B, 0x90, 0x10, 0x99, 0x02, 0x98, + 0x40, 0x18, 0x1B, 0x90, 0x40, 0x00, 0x92, 0x49, + 0x1D, 0x90, 0x0E, 0x5E, 0x68, 0x46, 0x40, 0x78, + 0x1E, 0x90, 0x38, 0x42, 0x01, 0xD1, 0x00, 0x2C, + 0x7E, 0xD1, 0x00, 0x2E, 0x7C, 0xDD, 0xF0, 0x07, + 0x0B, 0xD1, 0x09, 0x98, 0x09, 0x99, 0x00, 0x88, + 0x80, 0x19, 0x08, 0x80, 0x10, 0x98, 0x12, 0x99, + 0x40, 0x00, 0x09, 0x5A, 0x12, 0x9A, 0x89, 0x19, + 0x11, 0x52, 0x11, 0x98, 0x86, 0x42, 0x6B, 0xDB, + 0xF0, 0x07, 0x69, 0xD1, 0x0A, 0x98, 0x70, 0x43, + 0x40, 0x11, 0x00, 0xB2, 0x20, 0x90, 0x13, 0x98, + 0x70, 0x43, 0x40, 0x11, 0x00, 0xB2, 0x21, 0x90, + 0x00, 0x20, 0x01, 0x90, 0x06, 0x90, 0x0E, 0x98, + 0x00, 0x28, 0x08, 0xD0, 0x00, 0x2C, 0x10, 0xD0, + 0x03, 0x98, 0x00, 0x1B, 0x00, 0x28, 0x0C, 0xDD, + 0x01, 0x20, 0x09, 0xE0, 0xFC, 0xE0, 0x00, 0x2C, + 0x07, 0xD0, 0x03, 0x98, 0x01, 0x19, 0x1A, 0x98, + 0x40, 0x1E, 0x81, 0x42, 0x01, 0xDA, 0x02, 0x20, + 0x01, 0x90, 0x0F, 0x98, 0x00, 0x28, 0x0A, 0xD0, + 0x00, 0x2D, 0x12, 0xD0, 0x04, 0x98, 0x41, 0x19, + 0x07, 0x98, 0x40, 0x1E, 0x81, 0x42, 0x0C, 0xDA, + 0x08, 0x21, 0x01, 0x98, 0x07, 0xE0, 0x00, 0x2D, + 0x07, 0xD0, 0x04, 0x98, 0x40, 0x1B, 0x00, 0x28, + 0x03, 0xDD, 0x01, 0x98, 0x04, 0x21, 0x08, 0x43, + 0x01, 0x90, 0x03, 0x98, 0x00, 0x1B, 0x00, 0x28, + 0x01, 0xDC, 0x01, 0x20, 0x06, 0xE0, 0x03, 0x98, + 0x01, 0x19, 0x1A, 0x98, 0x40, 0x1E, 0x81, 0x42, + 0x01, 0xDB, 0x02, 0x20, 0x06, 0x90, 0x04, 0x98, + 0x41, 0x19, 0x07, 0x98, 0x40, 0x1E, 0x81, 0x42, + 0x02, 0xDB, 0x08, 0x21, 0x06, 0x98, 0x05, 0xE0, + 0x04, 0x98, 0x40, 0x1B, 0x00, 0x28, 0x03, 0xDC, + 0x06, 0x98, 0x04, 0x21, 0x08, 0x43, 0x06, 0x90, + 0x20, 0x46, 0x28, 0x43, 0x4F, 0xD0, 0x00, 0x20, + 0x05, 0x90, 0x00, 0x2C, 0x14, 0xD0, 0x00, 0x2D, + 0x18, 0xD0, 0x41, 0x00, 0x3C, 0x20, 0xC8, 0x40, + 0x01, 0x99, 0x08, 0x40, 0x01, 0x07, 0x00, 0xE0, + 0x39, 0xE0, 0x09, 0x0F, 0x06, 0x98, 0x81, 0x43, + 0x1F, 0xD0, 0x1B, 0x98, 0x80, 0xB2, 0x00, 0xF0, + 0xFF, 0xFB, 0xC1, 0x07, 0x19, 0xD1, 0x0C, 0xE0, + 0x01, 0x46, 0x04, 0x20, 0xC8, 0x40, 0x81, 0x07, + 0x89, 0x0F, 0x03, 0xE0, 0x02, 0x21, 0x81, 0x40, + 0x0C, 0x20, 0x01, 0x40, 0x01, 0x98, 0x01, 0x43, + 0xE8, 0xE7, 0x18, 0x9A, 0x0D, 0x99, 0x11, 0x43, + 0x04, 0xD1, 0x21, 0x99, 0x88, 0x42, 0x01, 0xDD, + 0x01, 0x21, 0x18, 0x91, 0x20, 0x99, 0x88, 0x42, + 0x05, 0xDC, 0x05, 0x98, 0x40, 0x1C, 0xC0, 0xB2, + 0x05, 0x90, 0x03, 0x28, 0xC9, 0xD3, 0x05, 0x98, + 0x03, 0x28, 0x14, 0xD0, 0x09, 0x98, 0x09, 0x9A, + 0x01, 0x88, 0x70, 0x10, 0x09, 0x1A, 0x11, 0x80, + 0x10, 0x99, 0x12, 0x9A, 0x49, 0x00, 0x52, 0x5A, + 0x10, 0x1A, 0x12, 0x9A, 0x50, 0x52, 0x0B, 0x98, + 0x00, 0x28, 0x39, 0xD0, 0x0F, 0x98, 0x00, 0x28, + 0x2E, 0xD0, 0x7F, 0x08, 0x2E, 0xE0, 0x08, 0x99, + 0x1B, 0x98, 0x4A, 0x00, 0x2E, 0x49, 0x88, 0x52, + 0x08, 0x98, 0x40, 0x1C, 0x80, 0xB2, 0x08, 0x90, + 0x0C, 0x98, 0x86, 0x42, 0x00, 0xDD, 0x0C, 0x96, + 0x25, 0x48, 0x1D, 0x99, 0x42, 0x5A, 0x01, 0x21, + 0x0A, 0x43, 0x1D, 0x99, 0x42, 0x52, 0x69, 0x46, + 0x08, 0x78, 0x38, 0x43, 0x08, 0x70, 0x00, 0x2C, + 0x0D, 0xD0, 0x0F, 0x98, 0x00, 0x28, 0x1E, 0x98, + 0x05, 0xD0, 0x79, 0x08, 0x08, 0x43, 0x69, 0x46, + 0x48, 0x70, 0x00, 0x2D, 0x03, 0xD1, 0x79, 0x00, + 0x08, 0x43, 0x69, 0x46, 0x48, 0x70, 0x19, 0x98, + 0x40, 0x1C, 0xC0, 0xB2, 0x19, 0x90, 0xCD, 0xE7, + 0x78, 0x06, 0x07, 0x0E, 0x6D, 0x1C, 0xED, 0xB2, + 0x14, 0x98, 0x85, 0x42, 0x00, 0xD8, 0xEF, 0xE6, + 0x15, 0x98, 0x40, 0x1C, 0xC0, 0xB2, 0x15, 0x90, + 0x02, 0x28, 0x00, 0xD2, 0xC9, 0xE6, 0x00, 0x2C, + 0x02, 0xD1, 0x69, 0x46, 0x08, 0x78, 0x88, 0x70, + 0x19, 0x98, 0x00, 0x28, 0x01, 0xD0, 0x02, 0x2C, + 0x09, 0xD1, 0x0E, 0x98, 0x00, 0x28, 0x1F, 0xD0, + 0x16, 0x98, 0x00, 0x24, 0x69, 0x46, 0x0E, 0x94, + 0x02, 0x90, 0x88, 0x78, 0x08, 0x70, 0x68, 0x46, + 0x81, 0x78, 0x64, 0x1C, 0xE4, 0xB2, 0x02, 0x2C, + 0x10, 0xE0, 0x00, 0x00, 0xB8, 0x02, 0x00, 0x20, + 0x4C, 0x07, 0x00, 0x20, 0x34, 0x00, 0x00, 0x20, + 0x40, 0x05, 0x00, 0x20, 0x2A, 0x00, 0x00, 0x20, + 0x1C, 0x02, 0x00, 0x20, 0x1A, 0x00, 0x00, 0x20, + 0x10, 0x00, 0x00, 0x20, 0x00, 0xD8, 0x6B, 0xE6, + 0x08, 0x98, 0x01, 0x28, 0x1B, 0xD0, 0x1F, 0x99, + 0x88, 0x42, 0x18, 0xDA, 0x2C, 0x48, 0x00, 0x68, + 0xB0, 0x30, 0x41, 0x7A, 0x02, 0x7A, 0x08, 0x02, + 0x2A, 0x49, 0x10, 0x43, 0x09, 0x88, 0x88, 0x42, + 0x03, 0xD2, 0x29, 0x48, 0x00, 0x78, 0x01, 0x28, + 0x09, 0xD0, 0x0D, 0x98, 0x01, 0x28, 0x06, 0xD0, + 0x18, 0x98, 0x00, 0x28, 0x03, 0xD0, 0x0D, 0x98, + 0x00, 0x28, 0x05, 0xD0, 0x2E, 0xE0, 0x17, 0x99, + 0x0C, 0x98, 0x88, 0x42, 0x2A, 0xDC, 0x38, 0xE0, + 0x01, 0x20, 0x0D, 0x90, 0x38, 0x20, 0x0A, 0x90, + 0x1E, 0x4A, 0x22, 0x98, 0x1C, 0x99, 0x10, 0x5E, + 0x88, 0x42, 0x05, 0xDA, 0x09, 0x21, 0x09, 0x03, + 0x48, 0x43, 0x00, 0x14, 0x11, 0x90, 0x06, 0xE0, + 0x05, 0x21, 0x49, 0x03, 0x48, 0x43, 0x00, 0x14, + 0x11, 0x90, 0x2A, 0x20, 0x0A, 0x90, 0x00, 0x20, + 0x15, 0x4B, 0x08, 0xE0, 0x41, 0x00, 0x59, 0x5A, + 0x49, 0x00, 0x54, 0x5A, 0x64, 0x08, 0x64, 0x00, + 0x40, 0x1C, 0x54, 0x52, 0x80, 0xB2, 0x08, 0x99, + 0x88, 0x42, 0xF3, 0xD3, 0x0D, 0x98, 0x02, 0x28, + 0x00, 0xD2, 0xF6, 0xE5, 0x27, 0x9A, 0x0D, 0x48, + 0x18, 0x32, 0x25, 0x99, 0xFD, 0xF7, 0x1E, 0xF9, + 0x0B, 0x48, 0x27, 0x9A, 0x26, 0x99, 0xFD, 0xF7, + 0x19, 0xF9, 0x17, 0x9A, 0x0C, 0x99, 0x91, 0x42, + 0x00, 0xDC, 0x08, 0x48, 0x29, 0xB0, 0xF0, 0xBD, + 0xB8, 0x02, 0x00, 0x20, 0x34, 0x00, 0x00, 0x20, + 0x40, 0x05, 0x00, 0x20, 0x4C, 0x07, 0x00, 0x20, + 0x1C, 0x02, 0x00, 0x20, 0x1A, 0x00, 0x00, 0x20, + 0x10, 0x00, 0x00, 0x20, 0x00, 0x80, 0xFF, 0xFF, + 0x06, 0x4A, 0x07, 0x4B, 0x00, 0x21, 0x00, 0x20, + 0x40, 0x1C, 0x90, 0x42, 0xFC, 0xDB, 0x49, 0x1C, + 0x99, 0x42, 0xF8, 0xDB, 0x03, 0x48, 0x01, 0x21, + 0x01, 0x60, 0x70, 0x47, 0x10, 0x27, 0x00, 0x00, + 0xB8, 0x0B, 0x00, 0x00, 0x04, 0x00, 0x00, 0x40, + 0xF0, 0xB5, 0x1A, 0x48, 0x8F, 0xB0, 0x01, 0x78, + 0x02, 0x29, 0x01, 0xD9, 0x02, 0x21, 0x01, 0x70, + 0x00, 0x25, 0x24, 0xE0, 0x68, 0x1C, 0xC4, 0xB2, + 0x0D, 0x90, 0x1A, 0xE0, 0x34, 0x21, 0x69, 0x43, + 0x34, 0x22, 0x0E, 0x18, 0x62, 0x43, 0x17, 0x18, + 0x31, 0x79, 0x38, 0x79, 0x81, 0x42, 0x0E, 0xD9, + 0x31, 0x1D, 0x34, 0x22, 0x68, 0x46, 0xFC, 0xF7, + 0x03, 0xFB, 0x39, 0x1D, 0x30, 0x1D, 0x34, 0x22, + 0xFC, 0xF7, 0xFE, 0xFA, 0x38, 0x1D, 0x34, 0x22, + 0x69, 0x46, 0xFC, 0xF7, 0xF9, 0xFA, 0x64, 0x1C, + 0xE4, 0xB2, 0x06, 0x48, 0x01, 0x78, 0xA1, 0x42, + 0xE0, 0xD8, 0x0D, 0x98, 0xC5, 0xB2, 0x03, 0x49, + 0x08, 0x78, 0xA8, 0x42, 0xD6, 0xD8, 0x0F, 0xB0, + 0xF0, 0xBD, 0x00, 0x00, 0xA0, 0x04, 0x00, 0x20, + 0x70, 0xB5, 0x7B, 0x24, 0x21, 0x48, 0x24, 0x02, + 0x04, 0x60, 0xF7, 0x20, 0xC0, 0x01, 0x20, 0x4A, + 0xC1, 0x7C, 0x11, 0x70, 0x1F, 0x4A, 0x01, 0x7D, + 0x11, 0x70, 0x1F, 0x4A, 0x41, 0x7D, 0x11, 0x70, + 0x1E, 0x49, 0x80, 0x7D, 0x08, 0x70, 0x60, 0x7C, + 0x21, 0x7C, 0x00, 0x06, 0x00, 0x14, 0x08, 0x43, + 0x1B, 0x49, 0x05, 0x46, 0x08, 0x80, 0xE0, 0x7C, + 0xA1, 0x7C, 0x00, 0x06, 0x00, 0x14, 0x08, 0x43, + 0x18, 0x49, 0x08, 0x80, 0x20, 0x7E, 0x61, 0x7E, + 0x48, 0x43, 0x17, 0x49, 0x08, 0x80, 0x17, 0x48, + 0xFC, 0xF7, 0xE3, 0xFA, 0x10, 0x21, 0xC8, 0x41, + 0x15, 0x49, 0x08, 0x60, 0x04, 0x22, 0x21, 0x1D, + 0x14, 0x48, 0xFC, 0xF7, 0xB5, 0xFA, 0x11, 0x49, + 0x06, 0x22, 0x89, 0x1F, 0x12, 0x48, 0xFC, 0xF7, + 0xAF, 0xFA, 0x60, 0x7D, 0x22, 0x7D, 0x01, 0x02, + 0x11, 0x43, 0x28, 0x46, 0x0A, 0x22, 0x50, 0x43, + 0xFC, 0xF7, 0x92, 0xFA, 0x0D, 0x49, 0x08, 0x80, + 0x70, 0xBD, 0x00, 0x00, 0xB8, 0x02, 0x00, 0x20, + 0x2C, 0x00, 0x00, 0x20, 0x2D, 0x00, 0x00, 0x20, + 0x2E, 0x00, 0x00, 0x20, 0x2F, 0x00, 0x00, 0x20, + 0x42, 0x00, 0x00, 0x20, 0x44, 0x00, 0x00, 0x20, + 0x3C, 0x00, 0x00, 0x20, 0x10, 0x7B, 0x00, 0x00, + 0xD8, 0x06, 0x00, 0x20, 0xE0, 0x06, 0x00, 0x20, + 0xE4, 0x06, 0x00, 0x20, 0x40, 0x00, 0x00, 0x20, + 0x64, 0x24, 0x00, 0x20, 0x56, 0x49, 0x08, 0x70, + 0xA9, 0xE0, 0x55, 0x48, 0x00, 0x78, 0x03, 0x00, + 0xFC, 0xF7, 0xFC, 0xFA, 0x07, 0x05, 0x10, 0x1E, + 0x18, 0x37, 0x48, 0x50, 0xA3, 0x00, 0x00, 0xF0, + 0xCB, 0xF9, 0x01, 0x28, 0x02, 0xD1, 0x4E, 0x49, + 0x08, 0x70, 0x02, 0xE0, 0x03, 0x20, 0x4C, 0x49, + 0x08, 0x70, 0x93, 0xE0, 0x00, 0xF0, 0xD6, 0xF8, + 0x01, 0x28, 0x02, 0xD1, 0x02, 0x20, 0x48, 0x49, + 0x08, 0x70, 0x8B, 0xE0, 0x00, 0xF0, 0xB2, 0xF9, + 0x00, 0x20, 0x45, 0x49, 0x08, 0x70, 0x85, 0xE0, + 0xFF, 0xF7, 0x06, 0xF8, 0x01, 0x28, 0x10, 0xD1, + 0xFF, 0xF7, 0xE0, 0xF8, 0x41, 0x48, 0x00, 0x78, + 0x80, 0x21, 0x08, 0x42, 0x03, 0xD0, 0x01, 0x20, + 0x3D, 0x49, 0x08, 0x70, 0x02, 0xE0, 0x04, 0x20, + 0x3B, 0x49, 0x08, 0x70, 0xFC, 0xF7, 0x74, 0xFD, + 0x02, 0xE0, 0x01, 0x20, 0x38, 0x49, 0x08, 0x70, + 0x6C, 0xE0, 0x00, 0xF0, 0xBB, 0xF9, 0x01, 0x28, + 0x0B, 0xD1, 0x36, 0x48, 0x00, 0x78, 0x80, 0x21, + 0x08, 0x42, 0x03, 0xD0, 0x01, 0x20, 0x32, 0x49, + 0x08, 0x70, 0x02, 0xE0, 0x05, 0x20, 0x30, 0x49, + 0x08, 0x70, 0x5B, 0xE0, 0x00, 0xF0, 0x6A, 0xF8, + 0x01, 0x28, 0x02, 0xD1, 0x06, 0x20, 0x2C, 0x49, + 0x08, 0x70, 0x53, 0xE0, 0x2C, 0x48, 0x00, 0x78, + 0x00, 0x28, 0x09, 0xD1, 0x01, 0x20, 0xFC, 0xF7, + 0xEF, 0xFA, 0x00, 0x20, 0x29, 0x49, 0x08, 0x70, + 0x01, 0x20, 0x25, 0x49, 0x08, 0x70, 0x45, 0xE0, + 0xFF, 0xF7, 0x72, 0xF9, 0x01, 0x28, 0x2E, 0xD1, + 0x24, 0x48, 0x00, 0x78, 0x00, 0x28, 0x2A, 0xD1, + 0x01, 0xF0, 0xAA, 0xFA, 0x22, 0x48, 0xC0, 0x78, + 0x01, 0x28, 0x04, 0xD0, 0x21, 0x48, 0x40, 0x78, + 0xC0, 0x07, 0xC0, 0x0F, 0x2D, 0xD0, 0x01, 0x25, + 0xAD, 0x07, 0xA9, 0x14, 0x28, 0x68, 0x08, 0x40, + 0x00, 0x0A, 0x0A, 0x21, 0x08, 0x43, 0x19, 0x49, + 0xC8, 0x70, 0xB4, 0x24, 0x1A, 0x48, 0x00, 0x78, + 0xFF, 0x28, 0x05, 0xDA, 0x18, 0x48, 0x00, 0x78, + 0x40, 0x1C, 0x17, 0x49, 0x08, 0x70, 0x02, 0xE0, + 0x01, 0x20, 0x15, 0x49, 0x08, 0x70, 0x14, 0x48, + 0x00, 0x78, 0x10, 0x49, 0x08, 0x70, 0x00, 0x20, + 0xFC, 0xF7, 0xB6, 0xFA, 0x0D, 0xE0, 0x00, 0x2C, + 0x05, 0xD0, 0x60, 0x1E, 0xC4, 0xB2, 0x01, 0x20, + 0xFC, 0xF7, 0xBE, 0xFB, 0x05, 0xE0, 0x00, 0x20, + 0x08, 0x49, 0x08, 0x70, 0x01, 0x20, 0xFC, 0xF7, + 0xA7, 0xFA, 0x01, 0x20, 0x02, 0x49, 0x08, 0x70, + 0x00, 0xE0, 0x00, 0xBF, 0x00, 0xBF, 0x54, 0xE7, + 0x80, 0x02, 0x00, 0x20, 0x84, 0x04, 0x00, 0x20, + 0xE4, 0x02, 0x00, 0x20, 0x90, 0x02, 0x00, 0x20, + 0xA0, 0x04, 0x00, 0x20, 0x88, 0x02, 0x00, 0x20, + 0x84, 0x02, 0x00, 0x20, 0x10, 0xB5, 0x18, 0x48, + 0x00, 0x78, 0x03, 0x00, 0xFC, 0xF7, 0x42, 0xFA, + 0x06, 0x28, 0x04, 0x0A, 0x16, 0x1C, 0x22, 0x28, + 0x01, 0xF0, 0xD2, 0xF8, 0x02, 0x20, 0x12, 0x49, + 0x08, 0x70, 0x1D, 0xE0, 0x01, 0xF0, 0xC0, 0xF8, + 0x00, 0x28, 0x03, 0xD0, 0x03, 0x20, 0x0E, 0x49, + 0x08, 0x70, 0x02, 0xE0, 0x05, 0x20, 0x0C, 0x49, + 0x08, 0x70, 0x11, 0xE0, 0x01, 0xF0, 0xB0, 0xF8, + 0x04, 0x20, 0x09, 0x49, 0x08, 0x70, 0x0B, 0xE0, + 0x01, 0xF0, 0x14, 0xF9, 0x05, 0x20, 0x06, 0x49, + 0x08, 0x70, 0x05, 0xE0, 0x01, 0xF0, 0x3E, 0xFA, + 0x01, 0x20, 0x03, 0x49, 0x08, 0x70, 0x10, 0xBD, + 0x00, 0xBF, 0x00, 0x20, 0xFB, 0xE7, 0x00, 0x00, + 0x0C, 0x00, 0x00, 0x20, 0x70, 0xB5, 0x63, 0x4C, + 0x20, 0x78, 0x00, 0x28, 0x41, 0xD0, 0x02, 0x20, + 0xFC, 0xF7, 0x66, 0xFB, 0x62, 0x88, 0x21, 0x78, + 0x5F, 0xA0, 0x00, 0xF0, 0x1F, 0xFD, 0x60, 0x88, + 0x61, 0x4A, 0x00, 0x25, 0x90, 0x42, 0x02, 0xD1, + 0xA5, 0x70, 0x28, 0x0A, 0xE0, 0x70, 0x20, 0x78, + 0x5E, 0x4E, 0x5F, 0x49, 0x44, 0x28, 0x75, 0xD0, + 0x13, 0xDC, 0x01, 0x22, 0x33, 0x28, 0x34, 0xD0, + 0x08, 0xDC, 0x20, 0x28, 0x74, 0xD0, 0x30, 0x28, + 0x28, 0xD0, 0x31, 0x28, 0x3D, 0xD0, 0x32, 0x28, + 0x3B, 0xD1, 0x27, 0xE0, 0x34, 0x28, 0x39, 0xD0, + 0x42, 0x28, 0x6F, 0xD0, 0x43, 0x28, 0x34, 0xD1, + 0x90, 0xE0, 0x60, 0x28, 0x78, 0xD0, 0x07, 0xDC, + 0x5D, 0x26, 0x5A, 0x28, 0x0B, 0xD0, 0x5C, 0x28, + 0x0C, 0xD0, 0x5D, 0x28, 0x29, 0xD1, 0x0D, 0xE0, + 0x62, 0x28, 0x65, 0xD0, 0x70, 0x28, 0x53, 0xD0, + 0x71, 0x28, 0x7A, 0xD1, 0x71, 0xE0, 0xFF, 0xF7, + 0xEF, 0xF8, 0x01, 0xE0, 0xFF, 0xF7, 0x02, 0xF9, + 0x26, 0x70, 0x73, 0xE0, 0xC8, 0x78, 0x00, 0x28, + 0x6F, 0xD0, 0x6F, 0xE0, 0x45, 0x49, 0x82, 0x20, + 0x08, 0x70, 0x6B, 0xE0, 0x60, 0x88, 0x44, 0x49, + 0x54, 0xE0, 0x60, 0x88, 0x08, 0x82, 0xA5, 0x70, + 0x28, 0x0A, 0xE0, 0x70, 0x08, 0x8A, 0x04, 0x28, + 0x5F, 0xD1, 0xFF, 0xF7, 0x8D, 0xF8, 0x00, 0x28, + 0x02, 0xD1, 0x01, 0x20, 0xFC, 0xF7, 0xFC, 0xF9, + 0x35, 0x70, 0x56, 0xE0, 0x60, 0x88, 0xC0, 0x07, + 0x04, 0xD0, 0x48, 0x8A, 0x39, 0x4A, 0x80, 0x08, + 0x80, 0x00, 0x15, 0xE0, 0x60, 0x88, 0x80, 0x07, + 0x05, 0xD5, 0x48, 0x8A, 0x35, 0x4A, 0x80, 0x08, + 0x80, 0x00, 0x52, 0x1C, 0x0C, 0xE0, 0x60, 0x88, + 0xC0, 0x04, 0x48, 0x8A, 0x04, 0xD5, 0x80, 0x09, + 0x30, 0x4A, 0x80, 0x01, 0xD2, 0x1C, 0x03, 0xE0, + 0x80, 0x09, 0x80, 0x01, 0x01, 0x22, 0xD2, 0x03, + 0x10, 0x43, 0x48, 0x82, 0x60, 0x88, 0xC0, 0x05, + 0x03, 0xD5, 0x48, 0x8A, 0x08, 0x22, 0x10, 0x43, + 0x48, 0x82, 0x0D, 0x72, 0xA5, 0x70, 0x28, 0x0A, + 0xE0, 0x70, 0x2A, 0xE0, 0x30, 0xE0, 0x03, 0xE0, + 0x25, 0x70, 0x25, 0x48, 0x05, 0x70, 0x25, 0xE0, + 0x60, 0x88, 0x00, 0x28, 0xC8, 0x79, 0x03, 0xD0, + 0x10, 0x43, 0x03, 0xE0, 0x20, 0xE0, 0x0B, 0xE0, + 0x40, 0x08, 0x40, 0x00, 0xC8, 0x71, 0x18, 0xE0, + 0xC8, 0x69, 0x02, 0x80, 0x60, 0x30, 0xC2, 0x83, + 0x60, 0x88, 0x1C, 0x49, 0x08, 0x70, 0xE1, 0xE7, + 0x48, 0x89, 0xA0, 0x70, 0x00, 0x0A, 0xE0, 0x70, + 0x19, 0x48, 0x00, 0x68, 0x60, 0x30, 0x00, 0x78, + 0x04, 0xE0, 0x71, 0x20, 0xA0, 0x70, 0x00, 0x20, + 0xE0, 0x70, 0x0B, 0x20, 0x20, 0x71, 0x00, 0x0A, + 0x60, 0x71, 0x25, 0x70, 0x01, 0x20, 0x70, 0xBD, + 0x0A, 0x70, 0xFB, 0xE7, 0x0D, 0x70, 0xF9, 0xE7, + 0x01, 0x20, 0xFC, 0xF7, 0x99, 0xF9, 0x35, 0x70, + 0xF4, 0xE7, 0x00, 0x00, 0x88, 0x02, 0x00, 0x20, + 0x43, 0x4D, 0x44, 0x3A, 0x30, 0x78, 0x25, 0x78, + 0x2C, 0x30, 0x78, 0x25, 0x78, 0x0D, 0x0A, 0x00, + 0xAA, 0x55, 0x00, 0x00, 0x90, 0x02, 0x00, 0x20, + 0xE4, 0x02, 0x00, 0x20, 0x84, 0x04, 0x00, 0x20, + 0x2B, 0x00, 0x00, 0x20, 0x01, 0x80, 0x00, 0x00, + 0x80, 0x02, 0x00, 0x20, 0x86, 0x02, 0x00, 0x20, + 0xB8, 0x02, 0x00, 0x20, 0x10, 0xB5, 0x01, 0x20, + 0x00, 0x06, 0xFC, 0xF7, 0x64, 0xF9, 0x10, 0xBD, + 0x10, 0xB5, 0x00, 0x24, 0x00, 0xF0, 0x46, 0xFE, + 0x01, 0xF0, 0x0E, 0xF8, 0x01, 0x20, 0xFE, 0xF7, + 0x11, 0xFF, 0x04, 0x46, 0x00, 0x2C, 0x04, 0xD1, + 0x05, 0xA0, 0x00, 0xF0, 0x33, 0xFC, 0x00, 0x20, + 0x10, 0xBD, 0x06, 0x49, 0x06, 0x48, 0x81, 0x70, + 0x09, 0x0A, 0xC1, 0x70, 0x01, 0x20, 0xF7, 0xE7, + 0x49, 0x4E, 0x49, 0x54, 0x20, 0x4E, 0x47, 0x0D, + 0x0A, 0x00, 0x00, 0x00, 0xAA, 0x55, 0x00, 0x00, + 0x88, 0x02, 0x00, 0x20, 0x10, 0xB5, 0x1C, 0x48, + 0x00, 0x78, 0x01, 0x28, 0x02, 0xD0, 0x02, 0x28, + 0x2E, 0xD1, 0x1E, 0xE0, 0xFE, 0xF7, 0xDE, 0xFF, + 0xFD, 0xF7, 0x92, 0xFE, 0x17, 0x48, 0x00, 0x7A, + 0x00, 0x28, 0x04, 0xD1, 0x01, 0x20, 0x16, 0x49, + 0x08, 0x70, 0x00, 0x20, 0x10, 0xBD, 0xFC, 0xF7, + 0xD9, 0xF9, 0xFE, 0xF7, 0x87, 0xFF, 0x13, 0x48, + 0x00, 0x78, 0x80, 0x21, 0x08, 0x42, 0x04, 0xD0, + 0x06, 0x20, 0x0F, 0x49, 0x08, 0x70, 0x00, 0x20, + 0xF0, 0xE7, 0x02, 0x20, 0x0A, 0x49, 0x08, 0x70, + 0x0E, 0xE0, 0xFE, 0xF7, 0xAB, 0xFE, 0x00, 0x28, + 0x03, 0xD0, 0x01, 0x20, 0x06, 0x49, 0x08, 0x70, + 0xE4, 0xE7, 0x01, 0x20, 0x04, 0x49, 0x08, 0x70, + 0x06, 0x20, 0x05, 0x49, 0x08, 0x70, 0x00, 0xBF, + 0x00, 0xBF, 0x00, 0x20, 0xDA, 0xE7, 0x00, 0x00, + 0x81, 0x02, 0x00, 0x20, 0x50, 0x02, 0x00, 0x20, + 0x80, 0x02, 0x00, 0x20, 0x86, 0x02, 0x00, 0x20, + 0x0A, 0x07, 0x01, 0xD5, 0x40, 0x1C, 0x02, 0xE0, + 0x4A, 0x07, 0x01, 0xD5, 0x40, 0x1E, 0x80, 0xB2, + 0x07, 0x4A, 0xCB, 0x07, 0x12, 0x68, 0x02, 0xD0, + 0x11, 0x7E, 0x40, 0x1A, 0x03, 0xE0, 0x89, 0x07, + 0x02, 0xD5, 0x11, 0x7E, 0x08, 0x18, 0x80, 0xB2, + 0x02, 0x49, 0x40, 0x00, 0x08, 0x5E, 0x70, 0x47, + 0xB8, 0x02, 0x00, 0x20, 0x4C, 0x07, 0x00, 0x20, + 0xF0, 0xB5, 0x41, 0x18, 0x15, 0x4C, 0x16, 0x4D, + 0x8C, 0x46, 0x0E, 0xE0, 0x29, 0x78, 0x01, 0x23, + 0x22, 0x88, 0x05, 0xE0, 0x1E, 0x46, 0x8E, 0x40, + 0x16, 0x42, 0x09, 0xD0, 0x49, 0x1C, 0xC9, 0xB2, + 0x02, 0x29, 0xF7, 0xD3, 0x40, 0x1C, 0x29, 0x70, + 0xC0, 0xB2, 0x84, 0x45, 0xEE, 0xD8, 0xF0, 0xBD, + 0x34, 0x27, 0x06, 0x46, 0x7E, 0x43, 0x0B, 0x4F, + 0xF6, 0x19, 0x1F, 0x46, 0x31, 0x71, 0x8F, 0x40, + 0x73, 0x72, 0x17, 0x43, 0x27, 0x80, 0x73, 0x71, + 0x73, 0x89, 0x33, 0x82, 0xB2, 0x89, 0x72, 0x82, + 0xB3, 0x85, 0xF2, 0x85, 0xFF, 0x22, 0x20, 0x36, + 0x72, 0x75, 0xE3, 0xE7, 0x3E, 0x00, 0x00, 0x20, + 0x31, 0x00, 0x00, 0x20, 0xA0, 0x04, 0x00, 0x20, + 0xF0, 0xB5, 0x95, 0xB0, 0x00, 0x20, 0x0C, 0x90, + 0x12, 0x90, 0x30, 0x21, 0x68, 0x46, 0xFC, 0xF7, + 0x08, 0xF8, 0xC9, 0x48, 0x01, 0x78, 0x40, 0x78, + 0x08, 0x18, 0x01, 0x06, 0x09, 0x0E, 0xC7, 0x48, + 0x0E, 0x91, 0x01, 0xD0, 0x00, 0x27, 0x99, 0xE0, + 0x01, 0x78, 0x00, 0x20, 0xFF, 0xF7, 0xB4, 0xFF, + 0x15, 0xB0, 0xF0, 0xBD, 0x34, 0x20, 0x39, 0x46, + 0x41, 0x43, 0xC0, 0x48, 0x00, 0x25, 0x08, 0x18, + 0x0F, 0x90, 0x00, 0x79, 0xFF, 0x28, 0x7D, 0xD1, + 0x38, 0x01, 0x12, 0x9E, 0x10, 0x90, 0x75, 0xE0, + 0x10, 0x9A, 0x68, 0x46, 0x14, 0x18, 0x60, 0x19, + 0xFF, 0x21, 0x13, 0x90, 0x41, 0x70, 0xA8, 0x00, + 0x20, 0x18, 0xB7, 0x49, 0x11, 0x90, 0x41, 0x60, + 0x0F, 0x98, 0x0C, 0x21, 0x41, 0x5E, 0x0A, 0x22, + 0x8C, 0x46, 0x82, 0x5E, 0x06, 0x20, 0x31, 0x46, + 0x41, 0x43, 0xAF, 0x48, 0x00, 0x23, 0x08, 0x18, + 0xA0, 0x30, 0x02, 0x21, 0xC3, 0x5E, 0x41, 0x5E, + 0x18, 0x46, 0x63, 0x46, 0x01, 0xF0, 0x52, 0xF8, + 0x0D, 0x90, 0xAC, 0x48, 0x01, 0x88, 0x00, 0x29, + 0x07, 0xD0, 0xAB, 0x48, 0x00, 0x88, 0x88, 0x42, + 0x03, 0xD9, 0xFB, 0xF7, 0x7B, 0xFF, 0xC0, 0xB2, + 0x00, 0xE0, 0x00, 0x20, 0x34, 0x21, 0xA2, 0x4A, + 0x71, 0x43, 0x89, 0x18, 0xC9, 0x69, 0xA6, 0x4B, + 0x4A, 0x00, 0xA4, 0x49, 0x1B, 0x78, 0x09, 0x78, + 0x40, 0x1C, 0x59, 0x43, 0x41, 0x43, 0x50, 0x18, + 0x9D, 0x49, 0x88, 0x42, 0x01, 0xD9, 0x08, 0x46, + 0x03, 0xE0, 0xA0, 0x49, 0x88, 0x42, 0x00, 0xD2, + 0x08, 0x46, 0x9F, 0x49, 0x09, 0x68, 0xB0, 0x31, + 0x4A, 0x7A, 0x0B, 0x7A, 0x11, 0x02, 0x98, 0x4A, + 0x19, 0x43, 0x12, 0x88, 0x91, 0x42, 0x04, 0xD2, + 0x91, 0x49, 0x09, 0x78, 0x01, 0x29, 0x00, 0xD1, + 0x91, 0x48, 0x0D, 0x99, 0x81, 0x42, 0x1B, 0xD8, + 0x08, 0x46, 0x11, 0x99, 0x6D, 0x1C, 0x48, 0x60, + 0x13, 0x98, 0xED, 0xB2, 0x46, 0x70, 0x01, 0x2D, + 0x12, 0xD9, 0x68, 0x1E, 0x0D, 0xE0, 0x81, 0x00, + 0x62, 0x18, 0x94, 0x46, 0x52, 0x68, 0x63, 0x58, + 0x9A, 0x42, 0x05, 0xD2, 0x22, 0x18, 0x52, 0x78, + 0x22, 0x54, 0x62, 0x46, 0x52, 0x68, 0x62, 0x50, + 0x40, 0x1E, 0x00, 0x06, 0x00, 0x0E, 0xEE, 0xD1, + 0x76, 0x1C, 0xF6, 0xB2, 0x0E, 0x98, 0x86, 0x42, + 0x86, 0xD3, 0x00, 0xE0, 0x08, 0xE0, 0x10, 0x98, + 0x69, 0x46, 0x0D, 0x54, 0x00, 0x2D, 0x03, 0xD0, + 0x0C, 0x98, 0x40, 0x1C, 0xC0, 0xB2, 0x0C, 0x90, + 0x7F, 0x1C, 0xFF, 0xB2, 0x77, 0x48, 0x00, 0x78, + 0x84, 0x46, 0xB8, 0x42, 0x00, 0xD9, 0x65, 0xE7, + 0x0C, 0x98, 0x00, 0x28, 0x7D, 0xD0, 0x01, 0x28, + 0x59, 0xD9, 0x00, 0x24, 0x55, 0xE0, 0x60, 0x1C, + 0xC3, 0xB2, 0x0C, 0x90, 0x4D, 0xE0, 0x26, 0x01, + 0x69, 0x46, 0x72, 0x18, 0x1D, 0x01, 0x69, 0x18, + 0x50, 0x78, 0x4F, 0x78, 0xB8, 0x42, 0x42, 0xD1, + 0x50, 0x68, 0x4F, 0x68, 0xB8, 0x42, 0x1F, 0xD2, + 0x00, 0x20, 0x0C, 0xE0, 0x0A, 0x18, 0x56, 0x78, + 0x97, 0x78, 0x57, 0x70, 0x96, 0x70, 0x82, 0x00, + 0x8A, 0x18, 0x97, 0x68, 0x56, 0x68, 0x40, 0x1C, + 0x57, 0x60, 0xC0, 0xB2, 0x96, 0x60, 0x6A, 0x46, + 0x52, 0x5D, 0x52, 0x1E, 0x82, 0x42, 0xED, 0xDC, + 0x6A, 0x46, 0x50, 0x5D, 0x00, 0x28, 0x04, 0xD0, + 0x40, 0x1E, 0x00, 0x06, 0x00, 0x0E, 0x50, 0x55, + 0x21, 0xD1, 0xFF, 0x20, 0x48, 0x70, 0x1E, 0xE0, + 0x00, 0x20, 0x0C, 0xE0, 0x11, 0x18, 0x4D, 0x78, + 0x8F, 0x78, 0x4F, 0x70, 0x8D, 0x70, 0x81, 0x00, + 0x51, 0x18, 0x8F, 0x68, 0x4D, 0x68, 0x40, 0x1C, + 0x4F, 0x60, 0xC0, 0xB2, 0x8D, 0x60, 0x69, 0x46, + 0x89, 0x5D, 0x49, 0x1E, 0x81, 0x42, 0xED, 0xDC, + 0x69, 0x46, 0x88, 0x5D, 0x00, 0x28, 0x04, 0xD0, + 0x40, 0x1E, 0x00, 0x06, 0x00, 0x0E, 0x88, 0x55, + 0x01, 0xD1, 0xFF, 0x20, 0x50, 0x70, 0x5B, 0x1C, + 0xDB, 0xB2, 0x9C, 0x45, 0xAF, 0xD8, 0x0C, 0x98, + 0xC4, 0xB2, 0xA4, 0x45, 0xA7, 0xD8, 0x00, 0x26, + 0x6E, 0xE0, 0x32, 0x01, 0x68, 0x46, 0x10, 0x18, + 0x0D, 0x90, 0x40, 0x78, 0xFF, 0x28, 0x29, 0xD0, + 0x34, 0x22, 0x50, 0x43, 0x3E, 0x4A, 0x12, 0x23, + 0x85, 0x18, 0x34, 0x20, 0x70, 0x43, 0x44, 0x18, + 0x10, 0x22, 0xAA, 0x5E, 0x22, 0x82, 0xEB, 0x5E, + 0x63, 0x82, 0x0C, 0x21, 0x0A, 0x20, 0x61, 0x5E, + 0x20, 0x5E, 0x00, 0xF0, 0x6B, 0xFF, 0x0C, 0x90, + 0x3D, 0x48, 0x07, 0x68, 0x38, 0x46, 0xA7, 0x30, + 0x00, 0xE0, 0x52, 0xE0, 0xFB, 0xF7, 0xE5, 0xFE, + 0x01, 0x46, 0x0C, 0x98, 0x81, 0x42, 0x0E, 0xD2, + 0x38, 0x46, 0xB0, 0x30, 0xC1, 0x79, 0x82, 0x79, + 0x08, 0x02, 0x31, 0x49, 0x10, 0x43, 0x09, 0x88, + 0x88, 0x42, 0x04, 0xD9, 0x01, 0x21, 0x30, 0x46, + 0xFF, 0xF7, 0x82, 0xFE, 0x36, 0xE0, 0x0D, 0x98, + 0x41, 0x68, 0x68, 0x7A, 0xC8, 0x28, 0x01, 0xD2, + 0x40, 0x1C, 0x60, 0x72, 0xE8, 0x69, 0x40, 0x18, + 0x40, 0x08, 0xE0, 0x61, 0x28, 0x79, 0x20, 0x71, + 0x03, 0x21, 0x61, 0x71, 0x80, 0x21, 0x08, 0x43, + 0x28, 0x71, 0xA8, 0x79, 0xA0, 0x71, 0xE8, 0x79, + 0xE0, 0x71, 0x28, 0x7A, 0x20, 0x72, 0x38, 0x46, + 0xA3, 0x30, 0xFB, 0xF7, 0xB6, 0xFE, 0x01, 0x46, + 0x0C, 0x98, 0x81, 0x42, 0x03, 0xD2, 0x60, 0x89, + 0x20, 0x82, 0xA0, 0x89, 0x60, 0x82, 0x60, 0x7A, + 0x02, 0x28, 0x07, 0xD1, 0x30, 0x20, 0x00, 0x5D, + 0x00, 0x28, 0x03, 0xD1, 0x60, 0x89, 0xA8, 0x85, + 0xA0, 0x89, 0xE8, 0x85, 0xA8, 0x8D, 0xA0, 0x85, + 0xE8, 0x8D, 0xE0, 0x85, 0x20, 0x35, 0x68, 0x7D, + 0x20, 0x34, 0x60, 0x75, 0x76, 0x1C, 0xF6, 0xB2, + 0x0C, 0x49, 0x08, 0x78, 0xB0, 0x42, 0x8C, 0xD8, + 0x8E, 0xE6, 0x00, 0x24, 0x09, 0x4D, 0x0B, 0xE0, + 0x34, 0x20, 0x60, 0x43, 0x40, 0x19, 0x00, 0x79, + 0xFF, 0x28, 0x03, 0xD1, 0x01, 0x21, 0x20, 0x46, + 0xFF, 0xF7, 0x36, 0xFE, 0x64, 0x1C, 0xE4, 0xB2, + 0x28, 0x78, 0xA0, 0x42, 0xF0, 0xD8, 0x7B, 0xE6, + 0x40, 0x05, 0x00, 0x20, 0xA0, 0x04, 0x00, 0x20, + 0xFF, 0xFF, 0x07, 0x00, 0x36, 0x00, 0x00, 0x20, + 0x34, 0x00, 0x00, 0x20, 0x2C, 0x00, 0x00, 0x20, + 0x2D, 0x00, 0x00, 0x20, 0xFF, 0xFF, 0x00, 0x00, + 0xB8, 0x02, 0x00, 0x20, 0xF0, 0xB5, 0x00, 0x24, + 0x63, 0x49, 0xA9, 0xB0, 0x02, 0x20, 0x08, 0x70, + 0x62, 0x48, 0x25, 0x46, 0x86, 0x78, 0x97, 0xE0, + 0x34, 0x21, 0x60, 0x48, 0x71, 0x43, 0x09, 0x18, + 0x08, 0x7A, 0x00, 0x28, 0x19, 0xD1, 0x5E, 0x4A, + 0x0A, 0x20, 0x08, 0x5E, 0x12, 0x78, 0x90, 0x42, + 0x11, 0xDB, 0x5C, 0x4F, 0x00, 0x23, 0xFB, 0x5E, + 0x9A, 0x1A, 0x90, 0x42, 0x0B, 0xDC, 0x5A, 0x4A, + 0x0C, 0x20, 0x08, 0x5E, 0x12, 0x78, 0x90, 0x42, + 0x05, 0xDB, 0x58, 0x4F, 0x00, 0x23, 0xFB, 0x5E, + 0x9A, 0x1A, 0x90, 0x42, 0x01, 0xDD, 0x02, 0x20, + 0x08, 0x72, 0x88, 0x79, 0x4F, 0x4A, 0x00, 0x90, + 0x00, 0x28, 0x06, 0xD0, 0x52, 0x4B, 0x52, 0x78, + 0x1B, 0x78, 0x00, 0x20, 0x9C, 0x46, 0x96, 0x46, + 0x66, 0xE0, 0x4F, 0x4F, 0x38, 0x78, 0x00, 0x19, + 0x03, 0x28, 0x63, 0xD2, 0x01, 0x22, 0x8A, 0x71, + 0x34, 0x22, 0x50, 0x43, 0xC0, 0x19, 0x09, 0x1D, + 0x00, 0x1D, 0xFB, 0xF7, 0x05, 0xFE, 0x39, 0x78, + 0x34, 0x22, 0x09, 0x19, 0x51, 0x43, 0x00, 0x20, + 0xC9, 0x19, 0x48, 0x71, 0x45, 0x49, 0x64, 0x1C, + 0x08, 0x70, 0x45, 0x49, 0xE4, 0xB2, 0x08, 0x70, + 0x4C, 0xE0, 0x0A, 0x79, 0x80, 0x23, 0x1A, 0x43, + 0x63, 0x46, 0x1B, 0x18, 0x34, 0x27, 0x7B, 0x43, + 0x3D, 0x4F, 0xDB, 0x19, 0x1B, 0x79, 0x9A, 0x42, + 0x3C, 0xD1, 0x3E, 0x48, 0x00, 0x78, 0x00, 0x28, + 0x04, 0xD1, 0x0F, 0x46, 0x20, 0x37, 0x38, 0x7D, + 0x46, 0x28, 0x02, 0xD9, 0x00, 0x20, 0x88, 0x71, + 0x34, 0xE0, 0x00, 0x98, 0x40, 0x1C, 0xC0, 0xB2, + 0x88, 0x71, 0x0A, 0x7E, 0x00, 0x2A, 0x05, 0xD0, + 0x02, 0x28, 0x03, 0xD1, 0x4A, 0x89, 0x4A, 0x84, + 0x8A, 0x89, 0x8A, 0x84, 0x28, 0x4A, 0x0B, 0x7A, + 0x12, 0x78, 0x9A, 0x18, 0x90, 0x42, 0x0F, 0xD3, + 0x00, 0x20, 0x88, 0x71, 0x83, 0x20, 0x38, 0x70, + 0x34, 0x20, 0x68, 0x43, 0x01, 0xAA, 0x80, 0x18, + 0x09, 0x1D, 0x00, 0x1D, 0x34, 0x22, 0xFB, 0xF7, + 0xBF, 0xFD, 0x6D, 0x1C, 0xED, 0xB2, 0x11, 0xE0, + 0x60, 0x46, 0x23, 0x4A, 0x00, 0x19, 0x34, 0x23, + 0x58, 0x43, 0x80, 0x18, 0x09, 0x1D, 0x00, 0x1D, + 0x1A, 0x46, 0xFB, 0xF7, 0xB1, 0xFD, 0x64, 0x1C, + 0xE4, 0xB2, 0x03, 0xE0, 0x40, 0x1C, 0xC0, 0xB2, + 0x86, 0x45, 0xB2, 0xD8, 0x76, 0x1C, 0xF6, 0xB2, + 0x14, 0x48, 0x01, 0x78, 0xB1, 0x42, 0x00, 0xD9, + 0x62, 0xE7, 0x06, 0x46, 0xB0, 0x78, 0x2A, 0x46, + 0x41, 0x19, 0x31, 0x70, 0x34, 0x21, 0x48, 0x43, + 0x4A, 0x43, 0x80, 0x19, 0x00, 0x1D, 0x02, 0xA9, + 0xFB, 0xF7, 0x96, 0xFD, 0x10, 0x4D, 0x00, 0x20, + 0x07, 0x46, 0x2B, 0x78, 0x0A, 0xE0, 0x19, 0x18, + 0x34, 0x22, 0x51, 0x43, 0x4A, 0x19, 0x12, 0x79, + 0x12, 0x06, 0x01, 0xD4, 0x89, 0x19, 0x8F, 0x71, + 0x40, 0x1C, 0xC0, 0xB2, 0x71, 0x78, 0x81, 0x42, + 0xF1, 0xD8, 0x74, 0x70, 0x29, 0xB0, 0xF0, 0xBD, + 0x30, 0x00, 0x00, 0x20, 0xA0, 0x04, 0x00, 0x20, + 0x2C, 0x00, 0x00, 0x20, 0x42, 0x00, 0x00, 0x20, + 0x2D, 0x00, 0x00, 0x20, 0x44, 0x00, 0x00, 0x20, + 0x40, 0x05, 0x00, 0x20, 0xCC, 0x00, 0x00, 0x20, + 0xCD, 0x00, 0x00, 0x20, 0x73, 0x04, 0x00, 0x20, + 0xF7, 0xB5, 0x00, 0x25, 0x84, 0xB0, 0x0C, 0x46, + 0x16, 0x46, 0x61, 0x27, 0x5C, 0xE0, 0x25, 0x28, + 0x54, 0xD1, 0x64, 0x1C, 0x00, 0x22, 0x20, 0x78, + 0x13, 0x46, 0x00, 0x28, 0x57, 0xD0, 0x25, 0x28, + 0x4C, 0xD0, 0x2D, 0x28, 0x01, 0xD1, 0x64, 0x1C, + 0x01, 0x23, 0x02, 0x20, 0x21, 0x78, 0x30, 0x29, + 0x07, 0xD1, 0x64, 0x1C, 0x03, 0x43, 0xF9, 0xE7, + 0x0A, 0x21, 0x4A, 0x43, 0x30, 0x3A, 0x82, 0x18, + 0x64, 0x1C, 0x20, 0x78, 0x01, 0x46, 0x30, 0x39, + 0x09, 0x29, 0xF5, 0xD9, 0xC1, 0xB2, 0x73, 0x29, + 0x0A, 0xD0, 0x64, 0x28, 0x10, 0xD0, 0x78, 0x28, + 0x13, 0xD0, 0x58, 0x28, 0x19, 0xD0, 0x75, 0x28, + 0x1F, 0xD0, 0x63, 0x28, 0x23, 0xD0, 0x2E, 0xE0, + 0x02, 0xCE, 0x00, 0x29, 0x00, 0xD1, 0x1D, 0xA1, + 0x04, 0x98, 0x00, 0xF0, 0x90, 0xF8, 0x0A, 0xE0, + 0x68, 0x46, 0x8C, 0xC0, 0x02, 0xCE, 0x01, 0x23, + 0x13, 0xE0, 0x68, 0x46, 0x8C, 0xC0, 0x08, 0xE0, + 0x04, 0x98, 0x00, 0xF0, 0x3C, 0xF8, 0x45, 0x19, + 0x19, 0xE0, 0x41, 0x20, 0x01, 0x93, 0x00, 0x92, + 0x02, 0x90, 0x02, 0xCE, 0x00, 0x23, 0x10, 0x22, + 0xF2, 0xE7, 0x68, 0x46, 0x8C, 0xC0, 0x02, 0xCE, + 0x00, 0x23, 0x0A, 0x22, 0xEC, 0xE7, 0x02, 0xCE, + 0x68, 0x46, 0x01, 0x73, 0x00, 0x21, 0x41, 0x73, + 0x03, 0xA9, 0xD9, 0xE7, 0xC1, 0xB2, 0x04, 0x98, + 0x00, 0xF0, 0x14, 0xF8, 0x6D, 0x1C, 0x64, 0x1C, + 0x20, 0x78, 0x00, 0x28, 0x9F, 0xD1, 0x04, 0x98, + 0x00, 0x28, 0x03, 0xD0, 0x04, 0x99, 0x00, 0x20, + 0x09, 0x68, 0x08, 0x70, 0x28, 0x46, 0x07, 0xB0, + 0xF0, 0xBD, 0x00, 0x00, 0x28, 0x6E, 0x75, 0x6C, + 0x6C, 0x29, 0x00, 0x00, 0x10, 0xB5, 0x00, 0x28, + 0x05, 0xD0, 0x02, 0x68, 0x11, 0x70, 0x01, 0x68, + 0x49, 0x1C, 0x01, 0x60, 0x10, 0xBD, 0xC8, 0xB2, + 0xFE, 0xF7, 0x64, 0xF9, 0x10, 0xBD, 0xFF, 0xB5, + 0x00, 0x27, 0x83, 0xB0, 0x0C, 0x9D, 0x3E, 0x46, + 0x08, 0x00, 0x3A, 0x46, 0x05, 0xD0, 0x00, 0x2B, + 0x12, 0xD0, 0x05, 0x9B, 0x0A, 0x2B, 0x0B, 0xD0, + 0x0E, 0xE0, 0x30, 0x20, 0x69, 0x46, 0x08, 0x70, + 0x4A, 0x70, 0x2A, 0x46, 0x0D, 0x9B, 0x03, 0x98, + 0x00, 0xF0, 0x31, 0xF8, 0x07, 0xB0, 0xF0, 0xBD, + 0x00, 0x29, 0x01, 0xDA, 0x01, 0x27, 0x40, 0x42, + 0x02, 0xAC, 0x69, 0x46, 0x03, 0x34, 0xCA, 0x72, + 0x0A, 0xE0, 0x05, 0x99, 0xFB, 0xF7, 0x9A, 0xFC, + 0x0A, 0x29, 0x02, 0xDB, 0x0E, 0x9A, 0x89, 0x18, + 0x3A, 0x39, 0x30, 0x31, 0x64, 0x1E, 0x21, 0x70, + 0x00, 0x28, 0xF2, 0xD1, 0x00, 0x2F, 0x0E, 0xD0, + 0x00, 0x2D, 0x09, 0xD0, 0x0D, 0x98, 0x80, 0x07, + 0x06, 0xD5, 0x2D, 0x21, 0x03, 0x98, 0xFF, 0xF7, + 0xB9, 0xFF, 0x76, 0x1C, 0x6D, 0x1E, 0x02, 0xE0, + 0x2D, 0x20, 0x64, 0x1E, 0x20, 0x70, 0x2A, 0x46, + 0x21, 0x46, 0x0D, 0x9B, 0x03, 0x98, 0x00, 0xF0, + 0x02, 0xF8, 0x80, 0x19, 0xCE, 0xE7, 0xFF, 0xB5, + 0x00, 0x25, 0x20, 0x27, 0x81, 0xB0, 0x0E, 0x46, + 0x14, 0x46, 0x00, 0x2A, 0x0E, 0xDD, 0x00, 0x20, + 0x01, 0xE0, 0x40, 0x1C, 0x49, 0x1C, 0x0A, 0x78, + 0x00, 0x2A, 0xFA, 0xD1, 0xA0, 0x42, 0x01, 0xDB, + 0x00, 0x24, 0x00, 0xE0, 0x24, 0x1A, 0x98, 0x07, + 0x00, 0xD5, 0x30, 0x27, 0xD8, 0x07, 0x06, 0xD0, + 0x0D, 0xE0, 0x39, 0x46, 0x01, 0x98, 0xFF, 0xF7, + 0x8D, 0xFF, 0x6D, 0x1C, 0x64, 0x1E, 0x00, 0x2C, + 0xF7, 0xDC, 0x04, 0xE0, 0x01, 0x98, 0xFF, 0xF7, + 0x85, 0xFF, 0x6D, 0x1C, 0x76, 0x1C, 0x31, 0x78, + 0x00, 0x29, 0xF7, 0xD1, 0x05, 0xE0, 0x39, 0x46, + 0x01, 0x98, 0xFF, 0xF7, 0x7B, 0xFF, 0x6D, 0x1C, + 0x64, 0x1E, 0x00, 0x2C, 0xF7, 0xDC, 0x28, 0x46, + 0x05, 0xB0, 0xF0, 0xBD, 0x0F, 0xB4, 0x10, 0xB5, + 0x03, 0xAA, 0x00, 0x20, 0x02, 0x99, 0xFF, 0xF7, + 0xF7, 0xFE, 0x10, 0xBC, 0x08, 0xBC, 0x04, 0xB0, + 0x18, 0x47, 0x00, 0x00, 0xF0, 0xB5, 0xA9, 0xB0, + 0x50, 0x49, 0x00, 0x24, 0x8C, 0x70, 0x9C, 0x21, + 0x02, 0xA8, 0xFB, 0xF7, 0x6E, 0xFC, 0x00, 0x26, + 0x66, 0xE0, 0x34, 0x21, 0x71, 0x43, 0x0D, 0x18, + 0x29, 0x79, 0xFF, 0x29, 0x5E, 0xD0, 0x0A, 0x06, + 0x36, 0xD5, 0x48, 0x4F, 0x00, 0x20, 0x3A, 0x78, + 0x94, 0x46, 0x2E, 0xE0, 0x34, 0x22, 0x42, 0x43, + 0xD2, 0x19, 0x12, 0x79, 0x80, 0x23, 0x1A, 0x43, + 0x8A, 0x42, 0x24, 0xD1, 0x34, 0x21, 0x48, 0x43, + 0x40, 0x49, 0x22, 0x46, 0x47, 0x18, 0x34, 0x20, + 0x42, 0x43, 0x01, 0xA8, 0x10, 0x18, 0x00, 0x90, + 0x39, 0x1D, 0x00, 0x1D, 0x34, 0x22, 0xFB, 0xF7, + 0x2F, 0xFC, 0x28, 0x79, 0x64, 0x1C, 0x41, 0x06, + 0x00, 0x98, 0x49, 0x0E, 0x01, 0x71, 0x00, 0x98, + 0x69, 0x79, 0x41, 0x71, 0x78, 0x79, 0x80, 0x21, + 0x08, 0x43, 0x78, 0x71, 0xA8, 0x79, 0xE4, 0xB2, + 0x00, 0x28, 0x2F, 0xD1, 0x31, 0x48, 0x81, 0x78, + 0x49, 0x1C, 0x81, 0x70, 0x2A, 0xE0, 0x40, 0x1C, + 0xC0, 0xB2, 0x84, 0x45, 0xCE, 0xD8, 0x25, 0xE0, + 0xA9, 0x79, 0x00, 0x29, 0x22, 0xD1, 0xE9, 0x79, + 0x03, 0x29, 0x02, 0xD9, 0x00, 0x20, 0xE8, 0x71, + 0x1C, 0xE0, 0x34, 0x20, 0x22, 0x46, 0x42, 0x43, + 0x01, 0xA8, 0x17, 0x18, 0x29, 0x1D, 0x38, 0x1D, + 0x34, 0x22, 0xFB, 0xF7, 0x01, 0xFC, 0x06, 0x20, + 0x23, 0x49, 0x70, 0x43, 0x40, 0x18, 0xA0, 0x30, + 0x01, 0x88, 0x79, 0x81, 0x40, 0x88, 0xB8, 0x81, + 0x1E, 0x48, 0x64, 0x1C, 0x81, 0x78, 0xE4, 0xB2, + 0x49, 0x1C, 0x81, 0x70, 0x28, 0x79, 0x80, 0x21, + 0x08, 0x43, 0x28, 0x71, 0x76, 0x1C, 0xF6, 0xB2, + 0x19, 0x48, 0x01, 0x78, 0x42, 0x78, 0x89, 0x18, + 0xB1, 0x42, 0x92, 0xD8, 0x00, 0x25, 0x15, 0x4E, + 0x1A, 0xE0, 0x34, 0x20, 0x68, 0x43, 0x81, 0x19, + 0x48, 0x79, 0x02, 0x06, 0x03, 0xD5, 0x40, 0x06, + 0x40, 0x0E, 0x48, 0x71, 0x0E, 0xE0, 0x02, 0x2C, + 0x11, 0xD0, 0x00, 0x20, 0x88, 0x71, 0x34, 0x20, + 0x60, 0x43, 0x01, 0xAA, 0x80, 0x18, 0x09, 0x1D, + 0x00, 0x1D, 0x34, 0x22, 0xFB, 0xF7, 0xCC, 0xFB, + 0x64, 0x1C, 0xE4, 0xB2, 0x6D, 0x1C, 0xED, 0xB2, + 0x30, 0x78, 0xA8, 0x42, 0xE1, 0xD8, 0x34, 0x70, + 0x34, 0x20, 0x44, 0x43, 0x22, 0x46, 0x02, 0xA9, + 0x30, 0x1D, 0xFB, 0xF7, 0xBD, 0xFB, 0x29, 0xB0, + 0xF0, 0xBD, 0x00, 0x00, 0xA0, 0x04, 0x00, 0x20, + 0x40, 0x05, 0x00, 0x20, 0xF8, 0xB5, 0x59, 0x4C, + 0x59, 0x4D, 0x00, 0x26, 0xE6, 0x70, 0x16, 0x21, + 0x28, 0x1D, 0xFB, 0xF7, 0xC6, 0xFB, 0x57, 0x48, + 0x57, 0x49, 0x00, 0x78, 0x09, 0x78, 0xC0, 0x01, + 0x08, 0x43, 0xC1, 0xB2, 0x55, 0x48, 0x01, 0x70, + 0x55, 0x48, 0x02, 0x78, 0x01, 0x20, 0x00, 0x2A, + 0x04, 0xD0, 0xAA, 0x7E, 0x8A, 0x42, 0x01, 0xD0, + 0xA9, 0x76, 0xE0, 0x70, 0x51, 0x49, 0x0A, 0x78, + 0x00, 0x2A, 0x10, 0xD1, 0x50, 0x4A, 0x12, 0x78, + 0x18, 0x2A, 0x0C, 0xD1, 0x4F, 0x4A, 0x12, 0x78, + 0x03, 0x2A, 0x08, 0xD3, 0xA9, 0x78, 0xC9, 0x07, + 0x04, 0xD1, 0xE0, 0x70, 0xA8, 0x70, 0x6E, 0x70, + 0x4B, 0x48, 0x06, 0x70, 0xF8, 0xBD, 0xAA, 0x78, + 0xD3, 0x07, 0x4A, 0x4A, 0x07, 0xD0, 0xE0, 0x70, + 0xAE, 0x70, 0x6E, 0x70, 0x2D, 0x20, 0x08, 0x70, + 0x03, 0x20, 0x10, 0x70, 0xF8, 0xBD, 0x11, 0x78, + 0x00, 0x29, 0xFB, 0xD1, 0x21, 0x78, 0x00, 0x29, + 0x07, 0xD0, 0xE0, 0x70, 0x69, 0x70, 0x38, 0x48, + 0x00, 0x24, 0x40, 0x1C, 0x40, 0x4F, 0x00, 0x90, + 0x63, 0xE0, 0x69, 0x78, 0x00, 0x29, 0x00, 0xD0, + 0xE0, 0x70, 0x6E, 0x70, 0xF8, 0xBD, 0x34, 0x20, + 0x30, 0x49, 0x60, 0x43, 0x45, 0x18, 0x20, 0x46, + 0x0B, 0x26, 0x70, 0x43, 0x2A, 0x79, 0xC6, 0x18, + 0x32, 0x71, 0x68, 0x89, 0x76, 0x1C, 0x30, 0x71, + 0x00, 0x0A, 0x70, 0x71, 0xA8, 0x89, 0xB0, 0x71, + 0x00, 0x0A, 0xF0, 0x71, 0xE8, 0x89, 0x30, 0x72, + 0x00, 0x0A, 0x70, 0x72, 0x20, 0x20, 0x40, 0x5D, + 0xC0, 0x07, 0x07, 0xD0, 0xE8, 0x8C, 0x30, 0x71, + 0x00, 0x0A, 0x70, 0x71, 0x28, 0x8D, 0xB0, 0x71, + 0x00, 0x0A, 0xF0, 0x71, 0x08, 0x78, 0x01, 0x28, + 0x2D, 0xD1, 0xC8, 0x79, 0x00, 0x28, 0x2A, 0xD0, + 0x00, 0x98, 0xC1, 0x79, 0x80, 0x79, 0x09, 0x06, + 0x0B, 0x14, 0x03, 0x43, 0x00, 0x98, 0x41, 0x79, + 0x00, 0x79, 0x09, 0x06, 0x0A, 0x14, 0x02, 0x43, + 0x22, 0x48, 0x00, 0x21, 0x41, 0x5E, 0x00, 0x20, + 0x38, 0x5E, 0x00, 0xF0, 0xCB, 0xFB, 0x20, 0x49, + 0x0A, 0x68, 0xB0, 0x32, 0x51, 0x7B, 0x12, 0x7B, + 0x09, 0x02, 0x11, 0x43, 0x81, 0x42, 0x0E, 0xD2, + 0x00, 0x98, 0x41, 0x79, 0x02, 0x79, 0x08, 0x06, + 0x00, 0x14, 0x10, 0x43, 0x38, 0x80, 0x00, 0x98, + 0xC2, 0x79, 0x81, 0x79, 0x10, 0x06, 0x00, 0x14, + 0x08, 0x43, 0x14, 0x49, 0x08, 0x80, 0xA8, 0x8A, + 0xB0, 0x72, 0x00, 0x0A, 0xF0, 0x72, 0xE8, 0x8A, + 0x30, 0x73, 0x00, 0x0A, 0x70, 0x73, 0x64, 0x1C, + 0xE4, 0xB2, 0x03, 0x4B, 0x58, 0x78, 0xA0, 0x42, + 0x9D, 0xD8, 0xF8, 0xBD, 0xA0, 0x04, 0x00, 0x20, + 0x90, 0x02, 0x00, 0x20, 0x74, 0x04, 0x00, 0x20, + 0x85, 0x02, 0x00, 0x20, 0x34, 0x01, 0x00, 0x20, + 0x2B, 0x00, 0x00, 0x20, 0x72, 0x04, 0x00, 0x20, + 0x8E, 0x02, 0x00, 0x20, 0x73, 0x04, 0x00, 0x20, + 0x83, 0x02, 0x00, 0x20, 0x75, 0x04, 0x00, 0x20, + 0x38, 0x00, 0x00, 0x20, 0x3A, 0x00, 0x00, 0x20, + 0xB8, 0x02, 0x00, 0x20, 0xF7, 0xB5, 0x00, 0x20, + 0x84, 0xB0, 0x03, 0x90, 0x32, 0x48, 0x00, 0x27, + 0x01, 0x90, 0x38, 0x46, 0x56, 0xE0, 0x00, 0x24, + 0x00, 0x29, 0x05, 0xD0, 0x40, 0x1E, 0x88, 0x42, + 0x00, 0xD1, 0x02, 0x24, 0x00, 0x26, 0x45, 0xE0, + 0x01, 0x24, 0xFB, 0xE7, 0x78, 0x00, 0x2B, 0x4A, + 0x02, 0x90, 0x10, 0x5E, 0x06, 0x9B, 0x98, 0x42, + 0x38, 0xDD, 0xC3, 0x07, 0x36, 0xD1, 0x01, 0x9B, + 0x98, 0x42, 0x33, 0xDD, 0xA4, 0x07, 0xA4, 0x0F, + 0x00, 0x2E, 0x0B, 0xD0, 0x49, 0x1E, 0xB1, 0x42, + 0x01, 0xD1, 0x08, 0x20, 0x04, 0x43, 0x00, 0x25, + 0x21, 0x48, 0x41, 0x5D, 0x20, 0x46, 0x08, 0x42, + 0x0F, 0xD1, 0x01, 0xE0, 0x04, 0x20, 0xF5, 0xE7, + 0x38, 0x46, 0xFF, 0xF7, 0x69, 0xFA, 0x1B, 0x4A, + 0x02, 0x99, 0x04, 0x2D, 0x51, 0x5E, 0x02, 0xD2, + 0x81, 0x42, 0x17, 0xDB, 0x01, 0xE0, 0x81, 0x42, + 0x04, 0xDD, 0x6D, 0x1C, 0xED, 0xB2, 0x08, 0x2D, + 0xE6, 0xD3, 0x01, 0xE0, 0x08, 0x2D, 0x0D, 0xD3, + 0x12, 0x49, 0x02, 0x98, 0x08, 0x5E, 0x01, 0x90, + 0x04, 0x99, 0x00, 0x98, 0x08, 0x70, 0x05, 0x98, + 0x10, 0x49, 0x06, 0x70, 0x01, 0x20, 0x03, 0x90, + 0x01, 0x98, 0x08, 0x80, 0x7F, 0x1C, 0x76, 0x1C, + 0xBF, 0xB2, 0xF6, 0xB2, 0x0C, 0x48, 0x00, 0x68, + 0x01, 0x7E, 0xB1, 0x42, 0xB6, 0xD8, 0x00, 0x98, + 0x40, 0x1C, 0xC0, 0xB2, 0x08, 0x4A, 0x00, 0x90, + 0x10, 0x68, 0x00, 0x99, 0x40, 0x7E, 0x88, 0x42, + 0xA1, 0xD8, 0x03, 0x98, 0x07, 0xB0, 0xF0, 0xBD, + 0x00, 0x80, 0xFF, 0xFF, 0x4C, 0x07, 0x00, 0x20, + 0x8E, 0x5A, 0x00, 0x00, 0x28, 0x00, 0x00, 0x20, + 0xB8, 0x02, 0x00, 0x20, 0x05, 0x48, 0x01, 0x78, + 0x82, 0x29, 0x05, 0xD1, 0xC0, 0x79, 0x01, 0x28, + 0x02, 0xD1, 0x03, 0x49, 0xFF, 0x20, 0x08, 0x70, + 0x70, 0x47, 0x00, 0x00, 0x84, 0x04, 0x00, 0x20, + 0xEC, 0x02, 0x00, 0x20, 0x01, 0x20, 0x02, 0x49, + 0x08, 0x70, 0x02, 0x49, 0x08, 0x70, 0x70, 0x47, + 0x0C, 0x00, 0x00, 0x20, 0x81, 0x02, 0x00, 0x20, + 0x70, 0xB5, 0x24, 0x4D, 0x00, 0x21, 0x28, 0x78, + 0x00, 0x28, 0x2C, 0xD1, 0x22, 0x4A, 0x23, 0x4B, + 0x10, 0x80, 0x04, 0x22, 0x1A, 0x70, 0x22, 0x4B, + 0x1A, 0x70, 0x22, 0x4A, 0x10, 0x70, 0x22, 0x4A, + 0x10, 0x70, 0x22, 0x4A, 0x10, 0x70, 0x22, 0x4A, + 0x10, 0x70, 0x22, 0x4A, 0x10, 0x70, 0x1A, 0xE0, + 0x34, 0x20, 0x48, 0x43, 0x40, 0x19, 0x02, 0x46, + 0x20, 0x32, 0x13, 0x78, 0x1C, 0x06, 0x06, 0xD5, + 0x5B, 0x06, 0x5B, 0x0E, 0x13, 0x70, 0x43, 0x89, + 0xC3, 0x84, 0x83, 0x89, 0x03, 0x85, 0x13, 0x78, + 0x9C, 0x07, 0x06, 0xD5, 0xFD, 0x24, 0x23, 0x40, + 0x13, 0x70, 0x42, 0x89, 0x42, 0x84, 0x82, 0x89, + 0x82, 0x84, 0x49, 0x1C, 0xC9, 0xB2, 0x28, 0x78, + 0x88, 0x42, 0xE1, 0xD8, 0x12, 0x4E, 0x00, 0x24, + 0x30, 0x70, 0x68, 0x78, 0x70, 0x70, 0x20, 0x46, + 0x34, 0x21, 0x48, 0x43, 0x41, 0x19, 0x80, 0x19, + 0x09, 0x1D, 0x00, 0x1D, 0x34, 0x22, 0xFB, 0xF7, + 0x13, 0xFA, 0x64, 0x1C, 0xE4, 0xB2, 0x03, 0x2C, + 0xF1, 0xD3, 0x70, 0xBD, 0xA0, 0x04, 0x00, 0x20, + 0x34, 0x00, 0x00, 0x20, 0x4A, 0x00, 0x00, 0x20, + 0x4B, 0x00, 0x00, 0x20, 0x49, 0x00, 0x00, 0x20, + 0x2A, 0x00, 0x00, 0x20, 0x47, 0x00, 0x00, 0x20, + 0x46, 0x00, 0x00, 0x20, 0x48, 0x00, 0x00, 0x20, + 0x40, 0x05, 0x00, 0x20, 0x02, 0x49, 0x00, 0x20, + 0x08, 0x70, 0x02, 0x49, 0x08, 0x80, 0x70, 0x47, + 0x31, 0x00, 0x00, 0x20, 0x3E, 0x00, 0x00, 0x20, + 0xF0, 0xB5, 0x3B, 0x4F, 0x00, 0x20, 0x38, 0x70, + 0x01, 0x21, 0x3A, 0x4C, 0xC9, 0x03, 0x21, 0x80, + 0x39, 0x4B, 0x3B, 0x49, 0x18, 0x80, 0x39, 0x4E, + 0x0A, 0x88, 0x0D, 0xE0, 0x43, 0x00, 0x00, 0x25, + 0xF1, 0x5E, 0x65, 0x5F, 0xA9, 0x42, 0x02, 0xDD, + 0x33, 0x4D, 0x21, 0x80, 0x28, 0x80, 0x49, 0x08, + 0x49, 0x00, 0x40, 0x1C, 0xF1, 0x52, 0x80, 0xB2, + 0x90, 0x42, 0xEF, 0xD3, 0x00, 0x20, 0x20, 0x5E, + 0x41, 0x00, 0x41, 0x18, 0xCB, 0x17, 0x5B, 0x0F, + 0x59, 0x18, 0xCB, 0x10, 0x2D, 0x49, 0x09, 0x68, + 0x90, 0x31, 0x4C, 0x7B, 0x0D, 0x7B, 0x21, 0x02, + 0x29, 0x43, 0x81, 0x42, 0x0C, 0xDA, 0x00, 0x20, + 0x08, 0xE0, 0x41, 0x00, 0x71, 0x5E, 0x99, 0x42, + 0x02, 0xDD, 0x39, 0x78, 0x49, 0x1C, 0x39, 0x70, + 0x40, 0x1C, 0x80, 0xB2, 0x90, 0x42, 0xF4, 0xD3, + 0x23, 0x48, 0x24, 0x4A, 0x01, 0x78, 0x21, 0x48, + 0x00, 0x68, 0x80, 0x30, 0x00, 0x29, 0x81, 0x7B, + 0x02, 0xD0, 0xCB, 0x00, 0xC9, 0x18, 0xC9, 0x08, + 0x11, 0x70, 0xC3, 0x7B, 0x1E, 0x49, 0x00, 0x20, + 0x08, 0x5E, 0x83, 0x42, 0x0C, 0xDA, 0x1D, 0x49, + 0x09, 0x78, 0x02, 0x29, 0x08, 0xD2, 0x1C, 0x49, + 0x0B, 0x78, 0x11, 0x78, 0x49, 0x1E, 0x8B, 0x42, + 0x02, 0xDD, 0x11, 0x78, 0x49, 0x1C, 0x39, 0x70, + 0x18, 0x49, 0x00, 0x23, 0xCB, 0x5E, 0x59, 0x42, + 0x81, 0x42, 0x01, 0xDD, 0x00, 0x20, 0x38, 0x70, + 0x3B, 0x78, 0x12, 0x78, 0x14, 0x49, 0x93, 0x42, + 0x08, 0x78, 0x06, 0xD9, 0x03, 0x28, 0x01, 0xD2, + 0x40, 0x1C, 0x00, 0xE0, 0x0C, 0x20, 0x08, 0x70, + 0xF0, 0xBD, 0x00, 0x28, 0xFC, 0xD0, 0x3A, 0x78, + 0x03, 0x2A, 0xF9, 0xD2, 0x40, 0x1E, 0xF6, 0xE7, + 0x0E, 0x00, 0x00, 0x20, 0x24, 0x00, 0x00, 0x20, + 0x32, 0x00, 0x00, 0x20, 0x4C, 0x07, 0x00, 0x20, + 0x3C, 0x00, 0x00, 0x20, 0xB8, 0x02, 0x00, 0x20, + 0x70, 0x04, 0x00, 0x20, 0x0D, 0x00, 0x00, 0x20, + 0x7C, 0x04, 0x00, 0x20, 0xA0, 0x04, 0x00, 0x20, + 0x8F, 0x02, 0x00, 0x20, 0x7A, 0x04, 0x00, 0x20, + 0x73, 0x04, 0x00, 0x20, 0x10, 0xB5, 0x06, 0x4C, + 0x00, 0x20, 0x20, 0x80, 0x60, 0x80, 0xFF, 0xF7, + 0x5D, 0xFF, 0x04, 0x48, 0xA0, 0x80, 0xE0, 0x80, + 0x03, 0x49, 0x03, 0x20, 0x08, 0x70, 0x10, 0xBD, + 0x34, 0x00, 0x00, 0x20, 0xFF, 0x7F, 0x00, 0x00, + 0x82, 0x02, 0x00, 0x20, 0x10, 0xB5, 0xFF, 0xF7, + 0x4D, 0xFF, 0x08, 0x49, 0x5A, 0x20, 0x08, 0x70, + 0x07, 0x49, 0x00, 0x20, 0x08, 0x70, 0x08, 0x48, + 0x06, 0x49, 0x81, 0x80, 0xC1, 0x80, 0x07, 0x49, + 0x03, 0x20, 0x08, 0x70, 0x06, 0x49, 0x01, 0x20, + 0x08, 0x70, 0x10, 0xBD, 0xB5, 0x02, 0x00, 0x20, + 0x73, 0x04, 0x00, 0x20, 0xFF, 0x7F, 0x00, 0x00, + 0x34, 0x00, 0x00, 0x20, 0x82, 0x02, 0x00, 0x20, + 0x83, 0x02, 0x00, 0x20, 0x70, 0xB5, 0x12, 0x4D, + 0x06, 0x46, 0x2C, 0x78, 0xE0, 0x07, 0x07, 0xD0, + 0x80, 0x22, 0x10, 0x49, 0x10, 0x48, 0xFB, 0xF7, + 0x1F, 0xF9, 0x60, 0x08, 0x40, 0x00, 0x28, 0x70, + 0x28, 0x78, 0x80, 0x07, 0x12, 0xD5, 0x00, 0x2E, + 0x10, 0xD1, 0x0C, 0x49, 0x00, 0x20, 0x09, 0x4D, + 0x09, 0x4C, 0x0E, 0x88, 0x08, 0xE0, 0x41, 0x00, + 0x62, 0x5E, 0x6B, 0x5E, 0xD2, 0x18, 0x52, 0x10, + 0x62, 0x52, 0x40, 0x1C, 0x6A, 0x52, 0x80, 0xB2, + 0xB0, 0x42, 0xF4, 0xD3, 0x70, 0xBD, 0x00, 0x00, + 0x82, 0x02, 0x00, 0x20, 0x4C, 0x07, 0x00, 0x20, + 0xF4, 0x05, 0x00, 0x20, 0x3C, 0x00, 0x00, 0x20, + 0x10, 0xB5, 0xFB, 0xF7, 0x11, 0xFF, 0x10, 0xBD, + 0x10, 0xB5, 0x04, 0x48, 0x40, 0x78, 0x80, 0x06, + 0x01, 0xD5, 0xFE, 0xF7, 0x53, 0xF9, 0xFB, 0xF7, + 0x81, 0xFD, 0x10, 0xBD, 0x88, 0x02, 0x00, 0x20, + 0xF8, 0xB5, 0x22, 0x49, 0x00, 0x20, 0x03, 0x46, + 0x1F, 0x4A, 0x0C, 0x88, 0x06, 0xE0, 0x41, 0x00, + 0x55, 0x5E, 0x00, 0x2D, 0x00, 0xDA, 0x53, 0x52, + 0x40, 0x1C, 0xC0, 0xB2, 0xA0, 0x42, 0xF6, 0xD3, + 0x1C, 0x48, 0x1B, 0x4D, 0x01, 0x78, 0x1D, 0x48, + 0x1B, 0x4A, 0x04, 0x68, 0x2E, 0x78, 0x20, 0x46, + 0xB0, 0x30, 0x12, 0x88, 0x02, 0x2E, 0x0F, 0xD0, + 0xC3, 0x79, 0x84, 0x79, 0x18, 0x02, 0x20, 0x43, + 0x90, 0x42, 0x01, 0xD8, 0x01, 0x29, 0x01, 0xD9, + 0x03, 0x20, 0x28, 0x70, 0x00, 0x20, 0xFF, 0xF7, + 0x99, 0xFF, 0xFF, 0xF7, 0xD1, 0xFE, 0xF8, 0xBD, + 0x90, 0x34, 0x66, 0x7B, 0x27, 0x7B, 0x34, 0x02, + 0x3C, 0x43, 0x0F, 0x4E, 0x00, 0x27, 0xF7, 0x5F, + 0xBC, 0x42, 0x05, 0xDA, 0x00, 0x29, 0x03, 0xD1, + 0x0C, 0x49, 0x09, 0x78, 0x01, 0x29, 0xE7, 0xD0, + 0xC1, 0x79, 0x84, 0x79, 0x08, 0x02, 0x20, 0x43, + 0x90, 0x42, 0xE3, 0xD2, 0x2B, 0x70, 0xE1, 0xE7, + 0x4C, 0x07, 0x00, 0x20, 0x3C, 0x00, 0x00, 0x20, + 0x82, 0x02, 0x00, 0x20, 0x40, 0x05, 0x00, 0x20, + 0x34, 0x00, 0x00, 0x20, 0xB8, 0x02, 0x00, 0x20, + 0x78, 0x04, 0x00, 0x20, 0x84, 0x04, 0x00, 0x20, + 0x10, 0xB5, 0xFE, 0xF7, 0xAD, 0xFD, 0xFF, 0xF7, + 0x49, 0xFF, 0x10, 0xBD, 0xF8, 0xB5, 0x00, 0x26, + 0xFB, 0xF7, 0xE8, 0xFB, 0x83, 0x4C, 0x20, 0x78, + 0x01, 0x28, 0x2A, 0xD1, 0x60, 0x7A, 0x0A, 0x28, + 0x27, 0xD2, 0x81, 0x4F, 0x81, 0x49, 0xB8, 0x5F, + 0x88, 0x42, 0x22, 0xD0, 0x80, 0x4D, 0x0C, 0x23, + 0x0A, 0x22, 0xE3, 0x5E, 0xA2, 0x5E, 0xA9, 0x5F, + 0x00, 0xF0, 0x1C, 0xF9, 0x7D, 0x49, 0x0A, 0x68, + 0xB0, 0x32, 0x51, 0x7B, 0x12, 0x7B, 0x09, 0x02, + 0x11, 0x43, 0x81, 0x42, 0x11, 0xD3, 0xB8, 0x5F, + 0x41, 0x00, 0x40, 0x18, 0x0A, 0x21, 0x61, 0x5E, + 0x40, 0x18, 0x80, 0x10, 0x60, 0x81, 0xA9, 0x5F, + 0x4A, 0x00, 0x89, 0x18, 0x0C, 0x22, 0xA2, 0x5E, + 0x89, 0x18, 0x89, 0x10, 0xA1, 0x81, 0x60, 0x84, + 0xA1, 0x84, 0x00, 0x25, 0xCC, 0xE0, 0x70, 0x4F, + 0x34, 0x23, 0x00, 0x20, 0x6B, 0x43, 0x39, 0x78, + 0x9C, 0x18, 0x0A, 0xE0, 0x22, 0x79, 0x80, 0x23, + 0x1A, 0x43, 0x34, 0x23, 0x43, 0x43, 0xDB, 0x19, + 0x1B, 0x79, 0x9A, 0x42, 0x03, 0xD0, 0x40, 0x1C, + 0xC0, 0xB2, 0x81, 0x42, 0xF2, 0xD8, 0x81, 0x42, + 0x7E, 0xD9, 0x34, 0x21, 0x48, 0x43, 0xC1, 0x19, + 0x14, 0x20, 0x08, 0x5E, 0x14, 0x22, 0x43, 0x00, + 0xC0, 0x18, 0xA2, 0x5E, 0x01, 0x23, 0x10, 0x18, + 0x80, 0x03, 0xDB, 0x03, 0xC0, 0x18, 0x00, 0x14, + 0xA0, 0x82, 0x16, 0x20, 0x08, 0x5E, 0x16, 0x22, + 0x47, 0x00, 0xC0, 0x19, 0xA2, 0x5E, 0x27, 0x46, + 0x10, 0x18, 0x80, 0x03, 0xC0, 0x18, 0x00, 0x14, + 0xE0, 0x82, 0x20, 0x37, 0x20, 0x31, 0x0A, 0x22, + 0x38, 0x46, 0xFB, 0xF7, 0x25, 0xF8, 0x38, 0x78, + 0x40, 0x07, 0x07, 0xD5, 0x00, 0x20, 0x53, 0x49, + 0x38, 0x70, 0x08, 0x70, 0x52, 0x49, 0x08, 0x70, + 0x52, 0x49, 0x08, 0x70, 0x24, 0x23, 0x22, 0x22, + 0x0C, 0x21, 0x0A, 0x20, 0xE3, 0x5E, 0xA2, 0x5E, + 0x61, 0x5E, 0x20, 0x5E, 0x00, 0xF0, 0xB2, 0xF8, + 0x39, 0x78, 0xCA, 0x07, 0x67, 0xD0, 0x62, 0x7A, + 0x2D, 0x2A, 0x09, 0xD2, 0x48, 0x4A, 0x12, 0x78, + 0x00, 0x2A, 0x05, 0xD0, 0x22, 0x7E, 0x00, 0x2A, + 0x02, 0xD1, 0x02, 0x22, 0x11, 0x43, 0x39, 0x70, + 0x40, 0x49, 0x0A, 0x68, 0x92, 0x21, 0x89, 0x5C, + 0x81, 0x42, 0x01, 0xD2, 0x01, 0x26, 0x16, 0xE0, + 0x10, 0x46, 0xB0, 0x30, 0x41, 0x7A, 0x03, 0x7A, + 0x08, 0x02, 0x18, 0x43, 0x81, 0x08, 0x3E, 0x48, + 0x00, 0x88, 0x81, 0x42, 0x07, 0xD2, 0x39, 0x48, + 0x00, 0x78, 0x00, 0x28, 0x03, 0xD1, 0x3B, 0x48, + 0x00, 0x78, 0x00, 0x28, 0x36, 0xD0, 0x00, 0x20, + 0xB8, 0x72, 0x00, 0x2E, 0x4E, 0xD0, 0x38, 0x78, + 0x04, 0x21, 0x08, 0x43, 0x38, 0x70, 0xB0, 0x32, + 0x50, 0x7A, 0x12, 0x7A, 0x00, 0x02, 0x10, 0x43, + 0x31, 0x4A, 0x79, 0x7D, 0x12, 0x88, 0x90, 0x42, + 0x40, 0xD9, 0x26, 0x23, 0xE3, 0x5E, 0x30, 0x20, + 0x2F, 0x4A, 0x9F, 0x01, 0x41, 0x43, 0x88, 0x18, + 0x87, 0x60, 0xC7, 0x60, 0x2D, 0x4F, 0x00, 0xE0, + 0x34, 0xE0, 0xBF, 0x88, 0x0A, 0x2F, 0x01, 0xD8, + 0x0A, 0x23, 0xE3, 0x5E, 0x53, 0x50, 0x0A, 0x21, + 0x61, 0x5E, 0x41, 0x60, 0x28, 0x21, 0x61, 0x5E, + 0x8A, 0x01, 0x82, 0x61, 0xC2, 0x61, 0x25, 0x4A, + 0x92, 0x88, 0x0A, 0x2A, 0x01, 0xD8, 0x0C, 0x21, + 0x61, 0x5E, 0x01, 0x61, 0x0C, 0x21, 0x61, 0x5E, + 0x41, 0x61, 0x1B, 0xE0, 0xB8, 0x7A, 0x40, 0x1C, + 0xC0, 0xB2, 0xB8, 0x72, 0x02, 0x28, 0xC4, 0xD9, + 0x00, 0x20, 0xB8, 0x72, 0xAA, 0xE7, 0x00, 0x29, + 0x10, 0xD1, 0x12, 0x49, 0x09, 0x68, 0x80, 0x31, + 0x89, 0x7C, 0x49, 0x08, 0x81, 0x42, 0x11, 0xD9, + 0x78, 0x78, 0x40, 0x1C, 0xC0, 0xB2, 0x78, 0x70, + 0x0A, 0x28, 0x03, 0xD9, 0x00, 0x20, 0x78, 0x70, + 0x83, 0x20, 0x38, 0x70, 0x6D, 0x1C, 0xED, 0xB2, + 0x04, 0x4A, 0x10, 0x78, 0xA8, 0x42, 0x00, 0xD9, + 0x2D, 0xE7, 0xF8, 0xBD, 0x00, 0x20, 0x78, 0x70, + 0x02, 0x20, 0xF2, 0xE7, 0xA0, 0x04, 0x00, 0x20, + 0x38, 0x00, 0x00, 0x20, 0xFF, 0x7F, 0x00, 0x00, + 0x3A, 0x00, 0x00, 0x20, 0xB8, 0x02, 0x00, 0x20, + 0x40, 0x05, 0x00, 0x20, 0x75, 0x04, 0x00, 0x20, + 0x47, 0x00, 0x00, 0x20, 0x46, 0x00, 0x00, 0x20, + 0x34, 0x00, 0x00, 0x20, 0x48, 0x00, 0x00, 0x20, + 0x38, 0x01, 0x00, 0x20, 0x84, 0x04, 0x00, 0x20, + 0x10, 0xB5, 0x03, 0x48, 0x00, 0x78, 0x00, 0x06, + 0x01, 0xD4, 0xFF, 0xF7, 0xAF, 0xFB, 0x10, 0xBD, + 0x86, 0x02, 0x00, 0x20, 0x10, 0xB5, 0xFF, 0xF7, + 0x07, 0xFD, 0x10, 0xBD, 0x10, 0x1A, 0x59, 0x1A, + 0x40, 0x43, 0x49, 0x43, 0xC2, 0x17, 0xCB, 0x17, + 0x08, 0x18, 0x53, 0x41, 0x00, 0x22, 0xD2, 0x43, + 0x00, 0x21, 0x12, 0x1A, 0x99, 0x41, 0x01, 0xD2, + 0x00, 0x20, 0xC0, 0x43, 0x70, 0x47, 0x05, 0x01, + 0x09, 0x04, 0x08, 0x02, 0x06, 0x0A, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x40, 0x10, 0x03, 0x42, 0x88, + 0x0C, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x09, 0x00, 0x50, 0x1F, 0x00, 0x00, 0x00, + 0x04, 0x03, 0x00, 0x50, 0x64, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x50, 0x77, 0x00, 0x01, 0x48, + 0x44, 0x09, 0x00, 0x50, 0x39, 0x5A, 0x5B, 0x00, + 0x10, 0x06, 0x00, 0x50, 0x00, 0x00, 0x06, 0x07, + 0x00, 0x06, 0x00, 0x50, 0x00, 0x00, 0x00, 0x78, + 0x08, 0x06, 0x00, 0x50, 0x0C, 0x30, 0x00, 0x00, + 0x28, 0x06, 0x00, 0x50, 0x06, 0x00, 0x00, 0x00, + 0x2C, 0x06, 0x00, 0x50, 0x0A, 0x66, 0x00, 0x00, + 0x30, 0x06, 0x00, 0x50, 0xCC, 0x02, 0x00, 0x20, + 0x34, 0x06, 0x00, 0x50, 0x00, 0x20, 0x00, 0x00, + 0x44, 0x00, 0x00, 0x50, 0x01, 0x00, 0x00, 0x00, + 0x1C, 0x00, 0x00, 0x50, 0x50, 0x71, 0x02, 0x00, + 0x20, 0x00, 0x00, 0x50, 0x24, 0x29, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x40, 0x04, 0x04, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x40, 0x14, 0x33, 0x43, 0xC8, + 0x0C, 0x00, 0x00, 0x40, 0x29, 0x0A, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x40, 0x10, 0x32, 0x00, 0x00, + 0x1C, 0x0E, 0x00, 0x50, 0x03, 0x00, 0x00, 0x00, + 0x54, 0x00, 0x00, 0x50, 0x14, 0x07, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x2C, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, + 0x58, 0x10, 0x00, 0x50, 0x01, 0x00, 0x00, 0x00, + 0x04, 0x11, 0x00, 0x50, 0x78, 0x11, 0x00, 0x00, + 0x0C, 0x11, 0x00, 0x50, 0x01, 0x00, 0x00, 0x00, + 0x10, 0x11, 0x00, 0x50, 0x78, 0x01, 0x00, 0x00, + 0x14, 0x11, 0x00, 0x50, 0xC8, 0x03, 0x60, 0x00, + 0x4C, 0x00, 0x00, 0x50, 0x31, 0x00, 0x00, 0x00, + 0x3C, 0x10, 0x00, 0x50, 0x00, 0x00, 0x10, 0x00, + 0xB4, 0x10, 0x00, 0x50, 0x00, 0x26, 0x31, 0x00, + 0xC0, 0x10, 0x00, 0x50, 0x33, 0x03, 0x33, 0x03, + 0xC4, 0x10, 0x00, 0x50, 0x33, 0x03, 0x33, 0x03, + 0xC8, 0x10, 0x00, 0x50, 0x0C, 0x0A, 0x00, 0x00, + 0xCC, 0x10, 0x00, 0x50, 0x1A, 0x00, 0x00, 0x00, + 0xD0, 0x10, 0x00, 0x50, 0x03, 0x19, 0x19, 0x00, + 0xF0, 0x11, 0x00, 0x50, 0x12, 0x00, 0x00, 0x00, + 0xEC, 0x11, 0x00, 0x50, 0x5C, 0x00, 0x00, 0x00, + 0xF4, 0x11, 0x00, 0x50, 0x01, 0x00, 0x01, 0x00, + 0x2C, 0x10, 0x00, 0x50, 0x10, 0x00, 0x90, 0x00, + 0x30, 0x10, 0x00, 0x50, 0x20, 0x0C, 0x90, 0x00, + 0x34, 0x10, 0x00, 0x50, 0x30, 0x0C, 0x30, 0x0C, + 0x38, 0x10, 0x00, 0x50, 0xFF, 0x0F, 0x00, 0x00, + 0x7C, 0x10, 0x00, 0x50, 0x88, 0x88, 0xFE, 0x88, + 0x80, 0x10, 0x00, 0x50, 0x88, 0xFF, 0x00, 0x00, + 0x84, 0x10, 0x00, 0x50, 0x55, 0x55, 0x55, 0x55, + 0x88, 0x10, 0x00, 0x50, 0x55, 0x55, 0x55, 0x55, + 0x8C, 0x10, 0x00, 0x50, 0x55, 0x55, 0x55, 0x55, + 0xE8, 0x10, 0x00, 0x50, 0x3F, 0x16, 0x3F, 0x15, + 0x04, 0x00, 0x00, 0x40, 0x04, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x78, 0x5C, 0x00, 0x00, 0x04, 0x00, 0x00, 0x20, + 0x08, 0x00, 0x00, 0x00, 0x5C, 0x0A, 0x00, 0x00, + 0x80, 0x5C, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x20, + 0xC4, 0x0C, 0x00, 0x00, 0x6A, 0x0A, 0x00, 0x00, + 0xFF, 0x00, 0x00, 0x00, 0x78, 0x7F, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x6E, 0x53, 0x58, 0x91, +}; +const unsigned char u8_rad_para_30[] = { + 0xA1, 0x00, 0x03, 0xF3, 0x02, 0x02, 0x00, 0x01, + 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x40, 0x01, 0x68, 0x01, 0x44, 0x01, 0x44, 0x01, + 0x05, 0x05, 0x00, 0x00, 0x01, 0x05, 0x41, 0x06, + 0x0A, 0x0B, 0x41, 0x0F, 0x10, 0x14, 0x41, 0x15, + 0x02, 0x07, 0x41, 0x16, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41, 0x41, 0x04, 0x03, 0x09, 0x41, 0x08, + 0x0E, 0x0D, 0x41, 0x13, 0x12, 0x18, 0x41, 0x17, + 0x0C, 0x11, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0C, 0x3E, 0x0C, 0x3E, 0x08, 0x08, 0x02, 0x02, + 0x14, 0x03, 0x08, 0x08, 0x01, 0x01, 0xC8, 0x00, + 0x1A, 0x01, 0x08, 0x14, 0x82, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5E, 0x01, + 0xA0, 0x00, 0x64, 0x00, 0x4A, 0x00, 0x0A, 0x78, + 0x0F, 0x08, 0x1A, 0x4A, 0x4A, 0x3C, 0x3C, 0x00, + 0x00, 0x00, 0x6A, 0x00, 0x38, 0x00, 0x1C, 0x00, + 0x06, 0x32, 0x69, 0xE3, 0x0D, 0x00, 0x00, 0x72, + 0x45, 0x00, 0x00, 0x06, 0x00, 0xA0, 0x00, 0x78, + 0x00, 0x64, 0xDB, 0x13, 0xB5, 0x0C, 0x2D, 0x03, + 0xCB, 0x00, 0x14, 0x0A, 0xB1, 0x01, 0xF1, 0x05, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x55, 0xAA, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xD6, 0x9D, 0x10, 0x3D, 0xCB, 0x25, 0xBF, 0xD8, +}; +const unsigned char u8_rad_testfw_30[] = { + 0x60, 0x0E, 0x00, 0x20, 0xB1, 0x08, 0x00, 0x00, + 0xB9, 0x08, 0x00, 0x00, 0x95, 0x08, 0x00, 0x00, + 0xBD, 0x08, 0x00, 0x00, 0xBF, 0x08, 0x00, 0x00, + 0xC1, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xC3, 0x08, 0x00, 0x00, + 0xC5, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xC7, 0x08, 0x00, 0x00, 0x81, 0x0F, 0x00, 0x00, + 0xE1, 0x0C, 0x00, 0x00, 0x01, 0x0A, 0x00, 0x00, + 0xCB, 0x08, 0x00, 0x00, 0x61, 0x0E, 0x00, 0x00, + 0xA1, 0x0D, 0x00, 0x00, 0x41, 0x0A, 0x00, 0x00, + 0xCB, 0x08, 0x00, 0x00, 0x01, 0x0B, 0x00, 0x00, + 0xCB, 0x08, 0x00, 0x00, 0x41, 0x0B, 0x00, 0x00, + 0xE1, 0x0A, 0x00, 0x00, 0xA1, 0x0A, 0x00, 0x00, + 0xC1, 0x0A, 0x00, 0x00, 0x81, 0x0A, 0x00, 0x00, + 0xCB, 0x08, 0x00, 0x00, 0xCB, 0x08, 0x00, 0x00, + 0x03, 0x48, 0x85, 0x46, 0x00, 0xF0, 0x80, 0xF8, + 0x00, 0x48, 0x00, 0x47, 0xC1, 0x35, 0x00, 0x00, + 0x60, 0x0E, 0x00, 0x20, 0x04, 0x20, 0x71, 0x46, + 0x08, 0x42, 0x02, 0xD0, 0xEF, 0xF3, 0x09, 0x80, + 0x01, 0xE0, 0xEF, 0xF3, 0x08, 0x80, 0x71, 0x46, + 0x00, 0x4A, 0x10, 0x47, 0xD9, 0x34, 0x00, 0x00, + 0x06, 0x48, 0x80, 0x47, 0x06, 0x48, 0x00, 0x47, + 0xFE, 0xE7, 0xFE, 0xE7, 0xFE, 0xE7, 0xFE, 0xE7, + 0xFE, 0xE7, 0xFE, 0xE7, 0xFE, 0xE7, 0xFE, 0xE7, + 0xFE, 0xE7, 0xFE, 0xE7, 0x15, 0x0A, 0x00, 0x00, + 0x81, 0x08, 0x00, 0x00, 0x30, 0xB5, 0x0B, 0x46, + 0x01, 0x46, 0x00, 0x20, 0x20, 0x22, 0x01, 0x24, + 0x09, 0xE0, 0x0D, 0x46, 0xD5, 0x40, 0x9D, 0x42, + 0x05, 0xD3, 0x1D, 0x46, 0x95, 0x40, 0x49, 0x1B, + 0x25, 0x46, 0x95, 0x40, 0x40, 0x19, 0x15, 0x46, + 0x52, 0x1E, 0x00, 0x2D, 0xF1, 0xDC, 0x30, 0xBD, + 0x70, 0xB5, 0x00, 0x24, 0x25, 0x46, 0x00, 0x28, + 0x01, 0xDA, 0x01, 0x24, 0x40, 0x42, 0x00, 0x29, + 0x01, 0xDA, 0x01, 0x25, 0x49, 0x42, 0xFF, 0xF7, + 0xDD, 0xFF, 0xAC, 0x42, 0x00, 0xD0, 0x40, 0x42, + 0x00, 0x2C, 0x00, 0xD0, 0x49, 0x42, 0x70, 0xBD, + 0x03, 0x46, 0x0B, 0x43, 0x9B, 0x07, 0x03, 0xD0, + 0x09, 0xE0, 0x08, 0xC9, 0x12, 0x1F, 0x08, 0xC0, + 0x04, 0x2A, 0xFA, 0xD2, 0x03, 0xE0, 0x0B, 0x78, + 0x49, 0x1C, 0x03, 0x70, 0x40, 0x1C, 0x52, 0x1E, + 0xF9, 0xD2, 0x70, 0x47, 0xD2, 0xB2, 0x01, 0xE0, + 0x02, 0x70, 0x40, 0x1C, 0x49, 0x1E, 0xFB, 0xD2, + 0x70, 0x47, 0x00, 0x22, 0xF6, 0xE7, 0x10, 0xB5, + 0x04, 0x46, 0x08, 0x46, 0x11, 0x46, 0x02, 0x46, + 0x20, 0x46, 0xFF, 0xF7, 0xEF, 0xFF, 0x20, 0x46, + 0x10, 0xBD, 0x00, 0x1D, 0x03, 0x21, 0x40, 0x1E, + 0x03, 0x78, 0x12, 0x02, 0x1A, 0x43, 0x49, 0x1E, + 0xF9, 0xD5, 0x10, 0x46, 0x70, 0x47, 0x00, 0x00, + 0x06, 0x4C, 0x01, 0x25, 0x06, 0x4E, 0x05, 0xE0, + 0x20, 0x46, 0xE3, 0x68, 0x07, 0xC8, 0x2B, 0x43, + 0x98, 0x47, 0x10, 0x34, 0xB4, 0x42, 0xF7, 0xD3, + 0xFF, 0xF7, 0x72, 0xFF, 0x10, 0x3B, 0x00, 0x00, + 0x30, 0x3B, 0x00, 0x00, 0xC1, 0x06, 0xC9, 0x0E, + 0x01, 0x20, 0x88, 0x40, 0x01, 0x49, 0x08, 0x60, + 0x70, 0x47, 0x00, 0x00, 0x00, 0xE1, 0x00, 0xE0, + 0x0B, 0x49, 0x10, 0xB5, 0x88, 0x42, 0x01, 0xD9, + 0x01, 0x20, 0x10, 0xBD, 0x01, 0x02, 0x09, 0x0A, + 0x08, 0x48, 0x49, 0x1E, 0x41, 0x61, 0x08, 0x49, + 0x07, 0x23, 0xCA, 0x69, 0x12, 0x02, 0x12, 0x0A, + 0x04, 0x04, 0x22, 0x43, 0xCA, 0x61, 0x00, 0x21, + 0x81, 0x61, 0x03, 0x61, 0x08, 0x46, 0x10, 0xBD, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xE0, 0x00, 0xE0, + 0x04, 0xED, 0x00, 0xE0, 0x70, 0x47, 0x00, 0x00, + 0x03, 0x49, 0x02, 0x20, 0x08, 0x60, 0x02, 0x49, + 0x80, 0x39, 0x08, 0x60, 0x70, 0x47, 0x00, 0x00, + 0x80, 0xE1, 0x00, 0xE0, 0x62, 0xB6, 0x02, 0x48, + 0x00, 0x21, 0x01, 0x60, 0x70, 0x47, 0x00, 0x00, + 0x0C, 0x00, 0x00, 0x40, 0x30, 0xB4, 0x74, 0x46, + 0x64, 0x1E, 0x25, 0x78, 0x64, 0x1C, 0xAB, 0x42, + 0x00, 0xD2, 0x1D, 0x46, 0x63, 0x5D, 0x5B, 0x00, + 0xE3, 0x18, 0x30, 0xBC, 0x18, 0x47, 0x00, 0x00, + 0x05, 0x48, 0x01, 0x68, 0x49, 0x1C, 0x01, 0x60, + 0x01, 0x20, 0x80, 0x07, 0x01, 0x6A, 0x03, 0x22, + 0xD2, 0x03, 0x11, 0x43, 0x01, 0x62, 0x70, 0x47, + 0x34, 0x00, 0x00, 0x20, 0x02, 0xE0, 0x08, 0xC8, + 0x12, 0x1F, 0x08, 0xC1, 0x00, 0x2A, 0xFA, 0xD1, + 0x70, 0x47, 0x00, 0x20, 0x01, 0xE0, 0x01, 0xC1, + 0x12, 0x1F, 0x00, 0x2A, 0xFB, 0xD1, 0x70, 0x47, + 0x10, 0xB5, 0x00, 0xF0, 0x55, 0xF8, 0x10, 0xBD, + 0x01, 0x20, 0x80, 0x07, 0x41, 0x6A, 0x82, 0x13, + 0x11, 0x43, 0x41, 0x62, 0x41, 0x6A, 0xC2, 0x13, + 0x11, 0x43, 0x41, 0x62, 0x70, 0x47, 0x10, 0xB5, + 0x02, 0xF0, 0xD4, 0xF8, 0x10, 0xBD, 0x00, 0x00, + 0x03, 0x48, 0x01, 0x68, 0x49, 0x1C, 0x01, 0x60, + 0x02, 0x49, 0x01, 0x20, 0xC8, 0x60, 0x70, 0x47, + 0x5C, 0x01, 0x00, 0x20, 0x00, 0x03, 0x00, 0x50, + 0x10, 0xB5, 0x00, 0xF0, 0xE7, 0xFA, 0x10, 0xBD, + 0x70, 0x47, 0x00, 0x00, 0x04, 0x49, 0x06, 0x22, + 0x00, 0x28, 0x08, 0x68, 0x01, 0xD0, 0x10, 0x43, + 0x00, 0xE0, 0x90, 0x43, 0x08, 0x60, 0x70, 0x47, + 0x00, 0x09, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, + 0x70, 0x47, 0x00, 0x00, 0x03, 0x48, 0x02, 0x49, + 0x41, 0x60, 0x03, 0x49, 0x81, 0x60, 0x70, 0x47, + 0x1F, 0x1F, 0x5F, 0x1F, 0x00, 0x10, 0x00, 0x50, + 0x1F, 0x1F, 0x1F, 0x1F, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xB5, 0x07, 0x48, 0x01, 0x69, 0x02, 0x29, + 0x06, 0xD1, 0x01, 0x61, 0x05, 0x49, 0x8A, 0x78, + 0x00, 0x2A, 0x02, 0xD0, 0x02, 0xF0, 0x92, 0xFA, + 0x00, 0xBD, 0x01, 0x20, 0xC8, 0x70, 0x00, 0xBD, + 0x00, 0x02, 0x00, 0x50, 0xE0, 0x05, 0x00, 0x20, + 0x01, 0x21, 0x89, 0x07, 0x0A, 0x15, 0x00, 0x28, + 0x48, 0x69, 0x02, 0xD0, 0x10, 0x43, 0x48, 0x61, + 0x70, 0x47, 0x90, 0x43, 0xFB, 0xE7, 0x00, 0x00, + 0x30, 0xB5, 0x1E, 0x4B, 0x58, 0x68, 0x99, 0x68, + 0x00, 0x28, 0x01, 0xDA, 0xDA, 0x04, 0x5A, 0x60, + 0x1B, 0x4A, 0x15, 0x68, 0x01, 0x24, 0x05, 0x40, + 0x00, 0x2D, 0x02, 0xD1, 0x50, 0x68, 0x08, 0x42, + 0x0F, 0xD0, 0x18, 0x48, 0x04, 0x70, 0xFF, 0xF7, + 0xBD, 0xFF, 0x14, 0x72, 0x16, 0x48, 0x00, 0x78, + 0x00, 0x28, 0x06, 0xD0, 0x18, 0x68, 0x01, 0x21, + 0x00, 0x09, 0x00, 0x01, 0x89, 0x02, 0x08, 0x43, + 0x18, 0x60, 0x58, 0x68, 0x80, 0x02, 0x02, 0xD5, + 0x01, 0x20, 0x40, 0x05, 0x58, 0x60, 0x58, 0x68, + 0x00, 0x04, 0x03, 0xD5, 0x01, 0x20, 0xC0, 0x03, + 0x58, 0x60, 0x94, 0x72, 0x58, 0x68, 0x80, 0x00, + 0x03, 0xD5, 0x01, 0x20, 0x40, 0x07, 0x58, 0x60, + 0xD4, 0x72, 0x58, 0x68, 0x40, 0x00, 0x03, 0xD5, + 0x01, 0x20, 0x80, 0x07, 0x58, 0x60, 0x94, 0x72, + 0x30, 0xBD, 0x00, 0x00, 0x00, 0x10, 0x00, 0x50, + 0x20, 0x00, 0x00, 0x20, 0x39, 0x00, 0x00, 0x20, + 0x31, 0x00, 0x00, 0x20, 0x01, 0x28, 0x05, 0xD0, + 0x02, 0x28, 0x05, 0xD0, 0x04, 0x28, 0x06, 0xD0, + 0x00, 0x20, 0x70, 0x47, 0x03, 0x48, 0x70, 0x47, + 0x02, 0x48, 0xC0, 0x30, 0x70, 0x47, 0x02, 0x48, + 0x70, 0x47, 0x00, 0x00, 0x78, 0x7C, 0x00, 0x00, + 0xF8, 0x7D, 0x00, 0x00, 0x10, 0xB5, 0x00, 0xF0, + 0x67, 0xF8, 0x10, 0xBD, 0x00, 0x00, 0x00, 0x00, + 0x70, 0x47, 0x00, 0x00, 0xF8, 0xB5, 0x06, 0x46, + 0x0D, 0x46, 0x30, 0x23, 0xFF, 0xF7, 0xDE, 0xFF, + 0x04, 0x00, 0x28, 0xD0, 0x19, 0x4F, 0x00, 0x2A, + 0x21, 0xD0, 0x00, 0x20, 0xFF, 0xF7, 0x52, 0xFF, + 0x02, 0xE0, 0x98, 0x00, 0x29, 0x58, 0x21, 0x50, + 0x18, 0x46, 0x5B, 0x1E, 0xDB, 0xB2, 0x00, 0x28, + 0xF7, 0xD1, 0x38, 0x68, 0x01, 0x88, 0x31, 0x43, + 0x01, 0x80, 0x11, 0x49, 0x09, 0x68, 0xCA, 0x79, + 0x8B, 0x79, 0x11, 0x02, 0x19, 0x43, 0x41, 0x80, + 0xC1, 0x21, 0x89, 0x00, 0x0D, 0x48, 0x00, 0xF0, + 0x1D, 0xF8, 0x0D, 0x49, 0xC8, 0x63, 0x01, 0x20, + 0xFF, 0xF7, 0x34, 0xFF, 0x0C, 0xE0, 0x38, 0x68, + 0x00, 0x88, 0x30, 0x40, 0x03, 0xD1, 0xF8, 0xBD, + 0x98, 0x00, 0x21, 0x58, 0x29, 0x50, 0x18, 0x46, + 0x5B, 0x1E, 0xDB, 0xB2, 0x00, 0x28, 0xF7, 0xD1, + 0x01, 0x20, 0xF8, 0xBD, 0x08, 0x00, 0x00, 0x20, + 0x98, 0x01, 0x00, 0x20, 0x78, 0x7C, 0x00, 0x00, + 0x40, 0x7F, 0x00, 0x00, 0x70, 0xB5, 0x41, 0x18, + 0x49, 0x1E, 0x64, 0x24, 0x09, 0x04, 0x0B, 0x4D, + 0x01, 0x43, 0x69, 0x63, 0xE8, 0x68, 0x81, 0x21, + 0x09, 0x06, 0x08, 0x43, 0xE8, 0x60, 0x02, 0xE0, + 0x01, 0x20, 0xFF, 0xF7, 0xA3, 0xFF, 0xE8, 0x68, + 0xC0, 0x01, 0x04, 0xD5, 0x20, 0x46, 0x64, 0x1E, + 0xA4, 0xB2, 0x00, 0x28, 0xF4, 0xD1, 0xA8, 0x6B, + 0x70, 0xBD, 0x00, 0x00, 0x40, 0x09, 0x00, 0x50, + 0x03, 0x49, 0x0A, 0x68, 0x10, 0x18, 0x0A, 0x68, + 0x90, 0x42, 0xFC, 0xD1, 0x70, 0x47, 0x00, 0x00, + 0x5C, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x70, 0xB5, 0x01, 0x25, 0xAD, 0x07, 0xE8, 0x68, + 0x40, 0x08, 0x40, 0x00, 0xE8, 0x60, 0x19, 0x48, + 0x41, 0x68, 0x01, 0x26, 0x49, 0x07, 0x00, 0x29, + 0x25, 0xDA, 0x42, 0x68, 0x04, 0x21, 0x0A, 0x43, + 0x42, 0x60, 0x15, 0x48, 0x00, 0x68, 0x15, 0x4C, + 0x40, 0x05, 0x40, 0x0F, 0x20, 0x73, 0x20, 0x7B, + 0x06, 0x28, 0x00, 0xD1, 0x21, 0x73, 0x20, 0x7B, + 0x02, 0x28, 0x18, 0xD0, 0x20, 0x7B, 0x06, 0x28, + 0x05, 0xD8, 0x20, 0x7B, 0x04, 0x28, 0x02, 0xD3, + 0x01, 0x20, 0x01, 0xF0, 0x31, 0xF9, 0x20, 0x7B, + 0x0B, 0x49, 0x09, 0x78, 0x88, 0x42, 0x06, 0xD0, + 0x0A, 0x48, 0x01, 0x78, 0x31, 0x43, 0x01, 0x70, + 0x09, 0x49, 0x81, 0x20, 0x08, 0x70, 0xE8, 0x68, + 0x30, 0x43, 0xE8, 0x60, 0x70, 0xBD, 0x00, 0x20, + 0xEB, 0xE7, 0x00, 0x00, 0x40, 0x00, 0x00, 0x50, + 0x00, 0x11, 0x00, 0x50, 0x20, 0x00, 0x00, 0x20, + 0x39, 0x01, 0x00, 0x20, 0x30, 0x00, 0x00, 0x20, + 0x20, 0x08, 0x00, 0x20, 0x70, 0xB5, 0x05, 0x46, + 0x06, 0x4C, 0x01, 0xF0, 0x7B, 0xFC, 0x00, 0x21, + 0x8A, 0x00, 0x49, 0x1C, 0xA0, 0x58, 0xC9, 0xB2, + 0xA8, 0x50, 0x18, 0x29, 0xF8, 0xD3, 0x01, 0x20, + 0x70, 0xBD, 0x00, 0x00, 0x00, 0x20, 0x00, 0x50, + 0x01, 0x20, 0x80, 0x07, 0x41, 0x68, 0x82, 0x14, + 0x11, 0x43, 0x41, 0x60, 0x70, 0x47, 0x00, 0x00, + 0x10, 0xB5, 0x14, 0x48, 0x80, 0x69, 0x40, 0x04, + 0x23, 0xD5, 0x13, 0x4C, 0xE0, 0x68, 0xA2, 0x68, + 0x02, 0x40, 0xE0, 0x68, 0xA1, 0x68, 0x88, 0x43, + 0xE0, 0x60, 0x10, 0x07, 0x03, 0xD5, 0x08, 0x20, + 0xE0, 0x60, 0x02, 0xF0, 0x85, 0xFD, 0x50, 0x07, + 0x0E, 0xD5, 0x04, 0x20, 0xE0, 0x60, 0x02, 0xF0, + 0x7F, 0xFD, 0x00, 0xF0, 0xCB, 0xF8, 0x00, 0x28, + 0x06, 0xD1, 0x08, 0x48, 0x00, 0x78, 0x00, 0x28, + 0x02, 0xD0, 0x01, 0x20, 0xFF, 0xF7, 0x48, 0xFE, + 0xFF, 0x20, 0xF3, 0x30, 0xE0, 0x60, 0x01, 0x20, + 0xE0, 0x60, 0x10, 0xBD, 0x00, 0x09, 0x00, 0x50, + 0x00, 0x05, 0x00, 0x50, 0x90, 0x02, 0x00, 0x20, + 0x70, 0xB5, 0x0C, 0x49, 0x00, 0x20, 0x0C, 0x4B, + 0x89, 0x25, 0x0C, 0x68, 0xAD, 0x00, 0x21, 0x18, + 0xC9, 0x7E, 0x41, 0x29, 0x08, 0xD0, 0x42, 0x00, + 0x99, 0x5A, 0x49, 0x05, 0x49, 0x0D, 0x99, 0x52, + 0x4E, 0x05, 0x01, 0xD5, 0x49, 0x1B, 0x99, 0x52, + 0x40, 0x1C, 0xC0, 0xB2, 0x30, 0x28, 0xEE, 0xD3, + 0x01, 0x20, 0x70, 0xBD, 0x98, 0x01, 0x00, 0x20, + 0xAC, 0x03, 0x00, 0x20, 0x01, 0x21, 0x89, 0x07, + 0x00, 0xB5, 0x8A, 0x14, 0x00, 0x28, 0x08, 0x68, + 0x04, 0xD0, 0x10, 0x43, 0x08, 0x60, 0xFF, 0xF7, + 0x9F, 0xFF, 0x00, 0xBD, 0x90, 0x43, 0x08, 0x60, + 0x00, 0xBD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xF8, 0xB5, 0x33, 0x48, 0x80, 0x69, 0x40, 0x04, + 0x61, 0xD5, 0x32, 0x4D, 0xA8, 0x6A, 0xE9, 0x68, + 0xAC, 0x68, 0x0C, 0x40, 0xE9, 0x68, 0xAA, 0x68, + 0x91, 0x43, 0xE9, 0x60, 0x2E, 0x4E, 0xC0, 0x07, + 0x15, 0xD0, 0x68, 0x69, 0x89, 0x27, 0xC0, 0xB2, + 0xEF, 0x60, 0xAA, 0x6A, 0x01, 0x21, 0x0A, 0x43, + 0xAA, 0x62, 0x0B, 0x28, 0x03, 0xD2, 0x31, 0x70, + 0x00, 0xF0, 0x92, 0xFD, 0x03, 0xE0, 0x00, 0x21, + 0x31, 0x70, 0x00, 0xF0, 0x69, 0xFD, 0xBC, 0x43, + 0x01, 0x20, 0xFF, 0xF7, 0xE5, 0xFD, 0x23, 0x48, + 0xA1, 0x04, 0x04, 0xD5, 0x29, 0x6A, 0x81, 0x43, + 0x29, 0x62, 0x41, 0x14, 0xE9, 0x60, 0xE1, 0x04, + 0x05, 0xD5, 0x29, 0x6A, 0x81, 0x43, 0x29, 0x62, + 0x01, 0x20, 0x00, 0x03, 0xE8, 0x60, 0xA0, 0x05, + 0x02, 0xD5, 0x01, 0x20, 0x40, 0x02, 0xE8, 0x60, + 0x20, 0x07, 0x09, 0xD5, 0x30, 0x78, 0x01, 0x28, + 0x02, 0xD1, 0x0A, 0x20, 0x00, 0xF0, 0x48, 0xFD, + 0x64, 0x08, 0x64, 0x00, 0x09, 0x20, 0xE8, 0x60, + 0x60, 0x07, 0x17, 0xD5, 0x30, 0x78, 0x01, 0x28, + 0x02, 0xD1, 0x0A, 0x20, 0x00, 0xF0, 0x3C, 0xFD, + 0x05, 0x20, 0xE8, 0x60, 0xA8, 0x69, 0x0E, 0x49, + 0x88, 0x42, 0x08, 0xD0, 0xA9, 0x69, 0x0C, 0x48, + 0x40, 0x1C, 0x81, 0x42, 0x03, 0xD0, 0xA9, 0x69, + 0xC0, 0x1C, 0x81, 0x42, 0x02, 0xD1, 0x01, 0x20, + 0xFF, 0xF7, 0xAA, 0xFD, 0xFF, 0x20, 0xF3, 0x30, + 0xE8, 0x60, 0x01, 0x20, 0xE8, 0x60, 0xF8, 0xBD, + 0x00, 0x09, 0x00, 0x50, 0x00, 0x06, 0x00, 0x50, + 0xCC, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x40, + 0x88, 0x02, 0x00, 0x20, 0x10, 0xB5, 0x02, 0x46, + 0x07, 0x49, 0x60, 0x32, 0x11, 0x80, 0x00, 0x21, + 0x51, 0x80, 0x4C, 0x00, 0x53, 0x88, 0x04, 0x5B, + 0x49, 0x1C, 0x1B, 0x19, 0xC9, 0xB2, 0x53, 0x80, + 0x31, 0x29, 0xF6, 0xD3, 0x10, 0xBD, 0x00, 0x00, + 0xAA, 0x55, 0x00, 0x00, 0x01, 0x20, 0x80, 0x07, + 0x40, 0x69, 0x40, 0x05, 0x01, 0xD5, 0x01, 0x20, + 0x70, 0x47, 0x00, 0x20, 0x70, 0x47, 0x00, 0x00, + 0x02, 0x48, 0x01, 0x68, 0x49, 0x1C, 0x01, 0x60, + 0x70, 0x47, 0x00, 0x00, 0x1C, 0x08, 0x00, 0x20, + 0x01, 0x20, 0x01, 0x49, 0x08, 0x70, 0x70, 0x47, + 0xD3, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x20, 0x00, 0x07, 0x82, 0x69, 0x0C, 0x49, + 0x00, 0x2A, 0x07, 0xDA, 0x82, 0x69, 0xC3, 0x00, + 0x92, 0x00, 0x92, 0x08, 0xD2, 0x18, 0x82, 0x61, + 0x01, 0x22, 0x4A, 0x72, 0x82, 0x69, 0x52, 0x00, + 0x08, 0xD5, 0x82, 0x69, 0x01, 0x23, 0x92, 0x00, + 0x92, 0x08, 0x9B, 0x07, 0xD2, 0x18, 0x82, 0x61, + 0x00, 0x20, 0x48, 0x72, 0x70, 0x47, 0x00, 0x00, + 0x20, 0x08, 0x00, 0x20, 0x70, 0xB5, 0x1D, 0x4D, + 0x68, 0x78, 0x00, 0x06, 0x2A, 0xD5, 0x05, 0x20, + 0xFF, 0xF7, 0x04, 0xFE, 0x01, 0x20, 0xFF, 0xF7, + 0x43, 0xFD, 0x68, 0x78, 0x40, 0x06, 0x40, 0x0E, + 0x68, 0x70, 0x68, 0x78, 0x16, 0x4C, 0x10, 0x38, + 0x03, 0x00, 0xFF, 0xF7, 0x0F, 0xFD, 0x05, 0x04, + 0x06, 0x0C, 0x1C, 0x1E, 0x0E, 0x00, 0x06, 0x20, + 0x06, 0xE0, 0x01, 0x20, 0x20, 0x70, 0x11, 0xA0, + 0x02, 0xF0, 0xDF, 0xFB, 0x01, 0xE0, 0x02, 0x20, + 0x20, 0x70, 0x68, 0x78, 0x80, 0x21, 0x08, 0x43, + 0x28, 0x71, 0x00, 0x0A, 0x68, 0x71, 0x00, 0x20, + 0xFF, 0xF7, 0x22, 0xFD, 0xA9, 0x88, 0x0C, 0xA0, + 0x02, 0xF0, 0xCF, 0xFB, 0x70, 0xBD, 0x03, 0x20, + 0xEE, 0xE7, 0x0D, 0x48, 0x69, 0x78, 0x82, 0x88, + 0x0C, 0xA0, 0x02, 0xF0, 0xC6, 0xFB, 0x05, 0x20, + 0xE6, 0xE7, 0x00, 0x00, 0x88, 0x02, 0x00, 0x20, + 0xCE, 0x00, 0x00, 0x20, 0x43, 0x4D, 0x44, 0x5F, + 0x49, 0x4E, 0x49, 0x54, 0x0D, 0x0A, 0x00, 0x00, + 0x43, 0x4D, 0x44, 0x20, 0x45, 0x78, 0x69, 0x74, + 0x5B, 0x25, 0x78, 0x5D, 0x3D, 0x0D, 0x0A, 0x00, + 0x74, 0x06, 0x00, 0x20, 0x43, 0x4D, 0x44, 0x3D, + 0x30, 0x78, 0x25, 0x78, 0x3A, 0x25, 0x78, 0x0D, + 0x0A, 0x00, 0x00, 0x00, 0xF8, 0xB5, 0x1A, 0x4C, + 0x01, 0x20, 0x20, 0x70, 0x19, 0x4E, 0x00, 0x20, + 0x30, 0x71, 0x70, 0x71, 0x18, 0x48, 0xB0, 0x70, + 0x00, 0x0A, 0xF0, 0x70, 0x00, 0x20, 0xFF, 0xF7, + 0xC9, 0xFE, 0x16, 0x4F, 0x04, 0x25, 0x20, 0x78, + 0x03, 0x00, 0xFF, 0xF7, 0xB7, 0xFC, 0x07, 0x14, + 0x05, 0x12, 0x12, 0x16, 0x19, 0x1F, 0x14, 0x00, + 0x37, 0x71, 0x38, 0x0A, 0x70, 0x71, 0x25, 0x70, + 0x70, 0x20, 0x30, 0x70, 0x0E, 0x48, 0x30, 0x71, + 0x00, 0x0A, 0x70, 0x71, 0x00, 0xF0, 0x92, 0xFB, + 0xE9, 0xE7, 0x00, 0xF0, 0x8F, 0xFB, 0x25, 0x70, + 0xE5, 0xE7, 0xFF, 0xF7, 0x7B, 0xFF, 0xE2, 0xE7, + 0x08, 0xA0, 0x02, 0xF0, 0x76, 0xFB, 0x00, 0xF0, + 0x11, 0xF8, 0xF2, 0xE7, 0xF8, 0xBD, 0x00, 0x00, + 0xCE, 0x00, 0x00, 0x20, 0x88, 0x02, 0x00, 0x20, + 0xCC, 0x55, 0x00, 0x00, 0x55, 0xAA, 0x00, 0x00, + 0x11, 0x5A, 0x00, 0x00, 0x5B, 0x53, 0x54, 0x5D, + 0x0D, 0x0A, 0x00, 0x00, 0x70, 0xB5, 0x1F, 0x48, + 0x00, 0x25, 0x84, 0x88, 0x01, 0x21, 0x00, 0x2C, + 0x1A, 0xD0, 0x0B, 0x00, 0xFF, 0xF7, 0x7E, 0xFC, + 0x07, 0x16, 0x05, 0x13, 0x1A, 0x16, 0x0F, 0x1F, + 0x16, 0x00, 0x00, 0xF0, 0x55, 0xF8, 0x00, 0xF0, + 0xC1, 0xF9, 0x01, 0x28, 0x01, 0xD1, 0x05, 0x21, + 0x08, 0xE0, 0x06, 0x21, 0x06, 0xE0, 0x02, 0xF0, + 0x99, 0xF8, 0x02, 0x21, 0x02, 0xE0, 0x00, 0xF0, + 0x9F, 0xFE, 0x03, 0x21, 0x00, 0x2C, 0xE4, 0xD1, + 0x28, 0x46, 0x70, 0xBD, 0x01, 0xF0, 0xF6, 0xFE, + 0x00, 0xF0, 0x5E, 0xFE, 0xED, 0xE7, 0x0C, 0xA0, + 0x02, 0xF0, 0x37, 0xFB, 0x0E, 0x48, 0xFF, 0xF7, + 0xE9, 0xFE, 0x0E, 0x48, 0xFF, 0xF7, 0xE6, 0xFE, + 0x0D, 0x48, 0xFF, 0xF7, 0xE3, 0xFE, 0x0D, 0x48, + 0xFF, 0xF7, 0xE0, 0xFE, 0x00, 0xF0, 0x8A, 0xFA, + 0x0B, 0x48, 0x01, 0x68, 0x0B, 0xA0, 0x02, 0xF0, + 0x24, 0xFB, 0xE1, 0xE7, 0x74, 0x06, 0x00, 0x20, + 0x54, 0x45, 0x53, 0x54, 0x5F, 0x43, 0x4D, 0x44, + 0x5F, 0x45, 0x58, 0x49, 0x54, 0x0D, 0x0A, 0x00, + 0xE4, 0x02, 0x00, 0x20, 0x48, 0x03, 0x00, 0x20, + 0xAC, 0x03, 0x00, 0x20, 0x10, 0x04, 0x00, 0x20, + 0x0C, 0x05, 0x00, 0x20, 0x67, 0x5F, 0x75, 0x33, + 0x32, 0x5F, 0x77, 0x65, 0x61, 0x72, 0x61, 0x62, + 0x6C, 0x65, 0x5F, 0x74, 0x65, 0x73, 0x74, 0x5F, + 0x72, 0x65, 0x73, 0x75, 0x6C, 0x74, 0x3A, 0x20, + 0x30, 0x78, 0x25, 0x78, 0x0D, 0x0A, 0x00, 0x00, + 0x10, 0xB5, 0xC8, 0x21, 0x0E, 0x48, 0xFF, 0xF7, + 0xB8, 0xFB, 0xC8, 0x21, 0x0D, 0x48, 0xFF, 0xF7, + 0xB4, 0xFB, 0xC8, 0x21, 0x0C, 0x48, 0xFF, 0xF7, + 0xB0, 0xFB, 0xC8, 0x21, 0x0B, 0x48, 0xFF, 0xF7, + 0xAC, 0xFB, 0x64, 0x21, 0x0A, 0x48, 0xFF, 0xF7, + 0xA8, 0xFB, 0x32, 0x21, 0x09, 0x48, 0xFF, 0xF7, + 0xA4, 0xFB, 0x09, 0x49, 0x00, 0x20, 0x08, 0x60, + 0x08, 0xA0, 0x02, 0xF0, 0xDE, 0xFA, 0x10, 0xBD, + 0xE4, 0x02, 0x00, 0x20, 0x48, 0x03, 0x00, 0x20, + 0xAC, 0x03, 0x00, 0x20, 0x10, 0x04, 0x00, 0x20, + 0x74, 0x04, 0x00, 0x20, 0xD8, 0x04, 0x00, 0x20, + 0x0C, 0x05, 0x00, 0x20, 0x46, 0x54, 0x20, 0x52, + 0x65, 0x73, 0x75, 0x6C, 0x74, 0x20, 0x49, 0x6E, + 0x69, 0x74, 0x0D, 0x0A, 0x00, 0x00, 0x00, 0x00, + 0xF3, 0xB5, 0xC1, 0xB0, 0x42, 0x98, 0x02, 0x28, + 0x01, 0xD2, 0x02, 0x20, 0x42, 0x90, 0xC0, 0x21, + 0x68, 0x46, 0xFF, 0xF7, 0x7A, 0xFB, 0x01, 0xF0, + 0xA3, 0xFB, 0x01, 0xF0, 0xD3, 0xFD, 0x01, 0xF0, + 0x7B, 0xF8, 0x00, 0x24, 0x6E, 0x46, 0x1A, 0xE0, + 0x01, 0xF0, 0xE4, 0xFC, 0x01, 0xF0, 0xCA, 0xFD, + 0x01, 0xF0, 0x72, 0xF8, 0x18, 0x49, 0x00, 0x20, + 0x0A, 0x68, 0x18, 0x49, 0x0F, 0x68, 0x39, 0x18, + 0xC9, 0x7E, 0x41, 0x29, 0x05, 0xD0, 0x81, 0x00, + 0x45, 0x00, 0x73, 0x58, 0x55, 0x5F, 0x5B, 0x19, + 0x73, 0x50, 0x40, 0x1C, 0xC0, 0xB2, 0x30, 0x28, + 0xF1, 0xD3, 0x64, 0x1C, 0xE4, 0xB2, 0x42, 0x98, + 0x84, 0x42, 0xE1, 0xD3, 0x01, 0xF0, 0xAE, 0xFD, + 0x01, 0xF0, 0xDC, 0xFC, 0x0B, 0x4D, 0x00, 0x24, + 0x28, 0x68, 0x00, 0x19, 0xC0, 0x7E, 0x41, 0x28, + 0x07, 0xD0, 0xA0, 0x00, 0x30, 0x58, 0x42, 0x99, + 0xFF, 0xF7, 0x16, 0xFB, 0x41, 0x99, 0x62, 0x00, + 0x88, 0x52, 0x64, 0x1C, 0xE4, 0xB2, 0x30, 0x2C, + 0xEE, 0xD3, 0x01, 0x20, 0x43, 0xB0, 0xF0, 0xBD, + 0x4C, 0x01, 0x00, 0x20, 0x98, 0x01, 0x00, 0x20, + 0x70, 0xB5, 0x24, 0x48, 0xFF, 0x24, 0x41, 0x6B, + 0x2D, 0x34, 0x03, 0x22, 0x12, 0x05, 0x91, 0x43, + 0x01, 0x22, 0x12, 0x05, 0x89, 0x18, 0x41, 0x63, + 0x1F, 0x4D, 0x20, 0x48, 0xA9, 0x7E, 0xFF, 0xF7, + 0x9F, 0xFF, 0xE8, 0x7E, 0x29, 0x7E, 0x41, 0x43, + 0x89, 0x04, 0x28, 0x7F, 0x09, 0x0E, 0x02, 0x04, + 0x09, 0x02, 0x0A, 0x43, 0x17, 0x49, 0x0C, 0x32, + 0x40, 0x39, 0x4A, 0x60, 0x0C, 0x38, 0xC0, 0xB2, + 0xC9, 0x14, 0x40, 0x18, 0x13, 0x49, 0x40, 0x31, + 0x88, 0x60, 0xA9, 0x7E, 0x14, 0x48, 0xFF, 0xF7, + 0x87, 0xFF, 0x14, 0x49, 0x12, 0x4B, 0x0A, 0x68, + 0x00, 0x20, 0x11, 0x18, 0xC9, 0x7E, 0x41, 0x29, + 0x03, 0xD0, 0x41, 0x00, 0x5D, 0x5A, 0x2D, 0x1B, + 0x5D, 0x52, 0x40, 0x1C, 0xC0, 0xB2, 0x30, 0x28, + 0xF3, 0xD3, 0x0D, 0xA0, 0x02, 0xF0, 0x39, 0xFA, + 0x01, 0x21, 0x08, 0x48, 0x01, 0xF0, 0x1C, 0xFE, + 0x0C, 0xA0, 0x02, 0xF0, 0x32, 0xFA, 0x01, 0x21, + 0x05, 0x48, 0x01, 0xF0, 0x15, 0xFE, 0x01, 0x20, + 0x70, 0xBD, 0x00, 0x00, 0x80, 0x10, 0x00, 0x50, + 0xA8, 0x06, 0x00, 0x20, 0x74, 0x04, 0x00, 0x20, + 0x48, 0x03, 0x00, 0x20, 0x98, 0x01, 0x00, 0x20, + 0x42, 0x61, 0x73, 0x65, 0x6C, 0x69, 0x6E, 0x65, + 0x0D, 0x0A, 0x00, 0x00, 0x51, 0x75, 0x69, 0x63, + 0x6B, 0x28, 0x30, 0x78, 0x31, 0x32, 0x43, 0x29, + 0x0D, 0x0A, 0x00, 0x00, 0xF8, 0xB5, 0x3C, 0x48, + 0x00, 0x24, 0x60, 0x21, 0x00, 0x68, 0xFF, 0xF7, + 0xCC, 0xFA, 0x3A, 0x4E, 0x3A, 0x48, 0x00, 0x68, + 0x00, 0x19, 0xC0, 0x7E, 0x41, 0x28, 0x5A, 0xD0, + 0xA5, 0x07, 0xAD, 0x0F, 0xA7, 0x08, 0x0C, 0x21, + 0x20, 0x46, 0xFF, 0xF7, 0x7B, 0xFA, 0x88, 0x08, + 0x00, 0x21, 0xC9, 0x43, 0x71, 0x62, 0x09, 0x0C, + 0xB1, 0x62, 0x20, 0x2C, 0x05, 0xD2, 0x72, 0x6A, + 0x01, 0x21, 0xA1, 0x40, 0x8A, 0x43, 0x72, 0x62, + 0x06, 0xE0, 0xB3, 0x6A, 0x21, 0x46, 0x20, 0x39, + 0x01, 0x22, 0x8A, 0x40, 0x93, 0x43, 0xB3, 0x62, + 0x03, 0x2D, 0x02, 0xD2, 0x69, 0x1C, 0xC9, 0xB2, + 0x00, 0xE0, 0x08, 0x21, 0xF2, 0x6A, 0x27, 0x4B, + 0x1A, 0x40, 0x01, 0x23, 0xBB, 0x40, 0x1B, 0x05, + 0x1B, 0x09, 0x1A, 0x43, 0xF2, 0x62, 0x72, 0x6B, + 0x0E, 0x23, 0x9A, 0x43, 0x01, 0x23, 0x83, 0x40, + 0x5B, 0x07, 0x1B, 0x0F, 0x1A, 0x43, 0x72, 0x63, + 0x40, 0x1C, 0x82, 0x07, 0x92, 0x0F, 0x90, 0x05, + 0x13, 0x05, 0x18, 0x43, 0x93, 0x04, 0x18, 0x43, + 0x12, 0x04, 0x18, 0x4F, 0x10, 0x43, 0x40, 0x37, + 0xFA, 0x6A, 0xFF, 0x23, 0x1B, 0x04, 0x9A, 0x43, + 0x10, 0x43, 0xF8, 0x62, 0xF0, 0x6B, 0x09, 0x07, + 0xF4, 0x22, 0x09, 0x0E, 0x90, 0x43, 0x09, 0x1D, + 0x08, 0x43, 0xF0, 0x63, 0xB0, 0x6B, 0x74, 0x21, + 0x88, 0x43, 0x6D, 0x1C, 0x69, 0x07, 0x49, 0x0E, + 0x09, 0x1D, 0x08, 0x43, 0xB0, 0x63, 0x01, 0xF0, + 0xE1, 0xFB, 0x01, 0xF0, 0xC7, 0xFC, 0x64, 0x1C, + 0xE4, 0xB2, 0x30, 0x2C, 0x9A, 0xD3, 0x06, 0x48, + 0x60, 0x22, 0x01, 0x68, 0x08, 0x48, 0xFF, 0xF7, + 0x47, 0xFA, 0x01, 0x21, 0x06, 0x48, 0x01, 0xF0, + 0x83, 0xFD, 0x01, 0x20, 0xF8, 0xBD, 0x00, 0x00, + 0x4C, 0x01, 0x00, 0x20, 0x80, 0x10, 0x00, 0x50, + 0x98, 0x01, 0x00, 0x20, 0xFF, 0xFF, 0x00, 0xF0, + 0xE4, 0x02, 0x00, 0x20, 0x10, 0xB5, 0x00, 0xF0, + 0xC1, 0xF8, 0x0E, 0x48, 0x00, 0x68, 0x0E, 0x4C, + 0x40, 0x05, 0x40, 0x0F, 0x20, 0x70, 0x00, 0x20, + 0x01, 0xF0, 0xA2, 0xFF, 0x0B, 0x49, 0x01, 0x20, + 0x08, 0x70, 0x08, 0x20, 0x20, 0x70, 0x0A, 0x49, + 0x06, 0x20, 0x08, 0x70, 0x01, 0x20, 0x09, 0x49, + 0x80, 0x02, 0x08, 0x80, 0x01, 0xF0, 0xF2, 0xFE, + 0x01, 0xF0, 0x90, 0xFC, 0x01, 0xF0, 0xBE, 0xFB, + 0x01, 0x20, 0x10, 0xBD, 0x00, 0x11, 0x00, 0x50, + 0x39, 0x01, 0x00, 0x20, 0x3B, 0x01, 0x00, 0x20, + 0x0D, 0x08, 0x00, 0x20, 0x46, 0x01, 0x00, 0x20, + 0xF8, 0xB5, 0x36, 0x48, 0x00, 0x24, 0x36, 0x4D, + 0x44, 0x80, 0x2C, 0x60, 0x6C, 0x60, 0x35, 0xA0, + 0x02, 0xF0, 0x5B, 0xF9, 0x32, 0x48, 0x61, 0x1E, + 0x80, 0x38, 0xC1, 0x61, 0x09, 0x0C, 0x01, 0x62, + 0x35, 0x49, 0x41, 0x62, 0x81, 0x62, 0xC1, 0x62, + 0x01, 0x06, 0x0A, 0x68, 0x0B, 0x13, 0x9A, 0x43, + 0x0A, 0x60, 0x32, 0x49, 0x01, 0x26, 0x8E, 0x61, + 0xCE, 0x61, 0x30, 0x4A, 0x40, 0x3A, 0xD4, 0x63, + 0x0C, 0x60, 0x8C, 0x60, 0x2E, 0x4B, 0x1F, 0x7F, + 0xDB, 0x7E, 0x3A, 0x04, 0x1B, 0x02, 0x1A, 0x43, + 0x0C, 0x32, 0xBC, 0x46, 0x42, 0x60, 0x82, 0x60, + 0x1F, 0x22, 0x02, 0x61, 0x44, 0x61, 0x0C, 0x62, + 0x1F, 0x48, 0x28, 0x4A, 0x40, 0x30, 0x82, 0x61, + 0x03, 0x22, 0xC2, 0x61, 0x1C, 0x4A, 0xC0, 0x3A, + 0xD4, 0x60, 0x03, 0x23, 0x1B, 0x02, 0x13, 0x61, + 0x14, 0x23, 0x83, 0x62, 0x34, 0x23, 0xC3, 0x63, + 0x17, 0x4B, 0x54, 0x27, 0x80, 0x33, 0x1F, 0x61, + 0x5C, 0x62, 0xD0, 0x27, 0xC7, 0x62, 0xF0, 0x27, + 0x1F, 0x60, 0xFF, 0x27, 0x11, 0x37, 0x5F, 0x61, + 0x9C, 0x62, 0x04, 0x62, 0x1A, 0x4B, 0x43, 0x62, + 0x1A, 0x48, 0x88, 0x62, 0x0E, 0x49, 0x1A, 0x48, + 0x40, 0x39, 0x48, 0x63, 0x67, 0x46, 0x0C, 0x3F, + 0xF8, 0xB2, 0xC9, 0x14, 0x40, 0x18, 0xA8, 0x60, + 0x17, 0x48, 0x16, 0x49, 0x01, 0x60, 0x44, 0x60, + 0xD1, 0x68, 0x16, 0x48, 0x01, 0x60, 0x11, 0x69, + 0x41, 0x60, 0x15, 0x48, 0x06, 0x70, 0x10, 0x15, + 0x10, 0x60, 0x14, 0xA0, 0x02, 0xF0, 0xF9, 0xF8, + 0xF8, 0xBD, 0x00, 0x00, 0xCE, 0x00, 0x00, 0x20, + 0xC0, 0x10, 0x00, 0x50, 0x4F, 0x70, 0x65, 0x6E, + 0x20, 0x49, 0x6E, 0x69, 0x74, 0x20, 0x53, 0x74, + 0x61, 0x72, 0x74, 0x0D, 0x0A, 0x00, 0x00, 0x00, + 0x55, 0x55, 0x55, 0x55, 0xC0, 0x11, 0x00, 0x50, + 0xA8, 0x06, 0x00, 0x20, 0x03, 0x00, 0x03, 0x00, + 0x00, 0x3F, 0x3F, 0x3F, 0x03, 0x30, 0xBC, 0x00, + 0x00, 0x26, 0x30, 0x00, 0x21, 0x20, 0x00, 0x00, + 0x00, 0x19, 0x00, 0x50, 0x20, 0x00, 0x00, 0x20, + 0x31, 0x00, 0x00, 0x20, 0x4F, 0x70, 0x65, 0x6E, + 0x20, 0x49, 0x6E, 0x69, 0x74, 0x20, 0x45, 0x6E, + 0x64, 0x0D, 0x0A, 0x00, 0x10, 0xB5, 0x90, 0xB0, + 0x40, 0x21, 0x68, 0x46, 0xFF, 0xF7, 0x85, 0xF9, + 0x41, 0x22, 0x40, 0x21, 0x68, 0x46, 0xFF, 0xF7, + 0x79, 0xF9, 0x0E, 0x49, 0x00, 0x20, 0x0B, 0x68, + 0x6A, 0x46, 0x19, 0x18, 0xC9, 0x7E, 0x41, 0x29, + 0x00, 0xD0, 0x50, 0x54, 0x40, 0x1C, 0xC0, 0xB2, + 0x30, 0x28, 0xF6, 0xD3, 0x08, 0x49, 0x00, 0x20, + 0x13, 0x5C, 0x44, 0x00, 0x40, 0x1C, 0xC0, 0xB2, + 0x0B, 0x53, 0x40, 0x28, 0xF8, 0xD3, 0x00, 0x21, + 0x03, 0x48, 0x01, 0xF0, 0x8D, 0xFC, 0x01, 0x20, + 0x10, 0xB0, 0x10, 0xBD, 0x98, 0x01, 0x00, 0x20, + 0x9C, 0x01, 0x00, 0x20, 0x0E, 0x49, 0x10, 0xB5, + 0x5A, 0x20, 0x08, 0x74, 0x00, 0x20, 0x0A, 0x46, + 0x48, 0x74, 0x20, 0x3A, 0x14, 0x5C, 0x4B, 0x7C, + 0x40, 0x1C, 0x1B, 0x19, 0xC0, 0xB2, 0x4B, 0x74, + 0x31, 0x28, 0xF7, 0xD3, 0x07, 0x49, 0x00, 0x20, + 0x13, 0x5C, 0x44, 0x00, 0x40, 0x1C, 0xC0, 0xB2, + 0x0B, 0x53, 0x40, 0x28, 0xF8, 0xD3, 0x01, 0x21, + 0x02, 0x48, 0x01, 0xF0, 0x69, 0xFC, 0x10, 0xBD, + 0xF8, 0x04, 0x00, 0x20, 0x9C, 0x01, 0x00, 0x20, + 0x70, 0xB5, 0x2E, 0xA0, 0x02, 0xF0, 0x79, 0xF8, + 0x32, 0x49, 0x31, 0x48, 0x08, 0x60, 0x33, 0x4B, + 0x31, 0x48, 0x58, 0x60, 0x31, 0x4C, 0x32, 0x48, + 0x80, 0x34, 0xA0, 0x60, 0x31, 0x48, 0x40, 0x78, + 0x41, 0x1E, 0xC9, 0x05, 0xC9, 0x0D, 0x19, 0x61, + 0x2F, 0x4A, 0x00, 0x21, 0x11, 0x62, 0x2B, 0x4A, + 0x2E, 0x4D, 0x40, 0x32, 0x55, 0x63, 0x05, 0x02, + 0x2D, 0x48, 0x6D, 0x1C, 0x05, 0x60, 0x41, 0x60, + 0x09, 0x25, 0x26, 0x48, 0x2D, 0x05, 0x40, 0x38, + 0x05, 0x63, 0x41, 0x63, 0x81, 0x63, 0xD9, 0x63, + 0x11, 0x60, 0xA1, 0x62, 0x27, 0x4B, 0x19, 0x70, + 0x04, 0x23, 0x03, 0x60, 0x83, 0x02, 0xC3, 0x60, + 0x01, 0x61, 0xC3, 0x68, 0x24, 0x49, 0x0B, 0x60, + 0x03, 0x69, 0x4B, 0x60, 0x03, 0x21, 0x23, 0x4B, + 0x09, 0x06, 0x19, 0x60, 0x01, 0x21, 0x49, 0x02, + 0x11, 0x62, 0x81, 0x04, 0xD1, 0x62, 0x81, 0x69, + 0x03, 0x23, 0x9B, 0x03, 0x19, 0x43, 0x81, 0x61, + 0xC1, 0x69, 0x1D, 0x4B, 0x19, 0x43, 0xC1, 0x61, + 0xD0, 0x21, 0x11, 0x63, 0xC1, 0x6A, 0x10, 0x22, + 0x11, 0x43, 0xC1, 0x62, 0x55, 0x20, 0xE0, 0x62, + 0x60, 0x21, 0x18, 0x48, 0xFF, 0xF7, 0xE9, 0xF8, + 0x16, 0x48, 0x60, 0x21, 0x60, 0x30, 0xFF, 0xF7, + 0xE4, 0xF8, 0x15, 0xA0, 0x02, 0xF0, 0x21, 0xF8, + 0x70, 0xBD, 0x00, 0x00, 0x53, 0x68, 0x6F, 0x72, + 0x74, 0x20, 0x49, 0x6E, 0x69, 0x74, 0x20, 0x53, + 0x74, 0x61, 0x72, 0x74, 0x0D, 0x0A, 0x00, 0x00, + 0x00, 0x00, 0x04, 0x20, 0x4C, 0x01, 0x00, 0x20, + 0x0C, 0x0D, 0xF2, 0x00, 0x40, 0x10, 0x00, 0x50, + 0xE6, 0x0A, 0x00, 0x00, 0xC8, 0x06, 0x00, 0x20, + 0xC0, 0x11, 0x00, 0x50, 0x00, 0x36, 0x30, 0x04, + 0x00, 0x19, 0x00, 0x50, 0x31, 0x00, 0x00, 0x20, + 0x20, 0x00, 0x00, 0x20, 0x00, 0x07, 0x00, 0x50, + 0x00, 0xC0, 0x00, 0xC0, 0x00, 0x20, 0x00, 0x50, + 0x53, 0x68, 0x6F, 0x72, 0x74, 0x20, 0x49, 0x6E, + 0x69, 0x74, 0x20, 0x45, 0x6E, 0x64, 0x0D, 0x0A, + 0x00, 0x00, 0x00, 0x00, 0x10, 0xB5, 0x1C, 0x48, + 0x64, 0x24, 0x41, 0x79, 0x02, 0x79, 0x09, 0x02, + 0x11, 0x43, 0x80, 0x22, 0x91, 0x43, 0x01, 0x71, + 0x09, 0x0A, 0x41, 0x71, 0x41, 0x79, 0x02, 0x79, + 0x09, 0x02, 0x11, 0x43, 0x2D, 0x22, 0x52, 0x02, + 0x11, 0x43, 0x01, 0x71, 0x09, 0x0A, 0x41, 0x71, + 0x81, 0x88, 0x12, 0xA0, 0x01, 0xF0, 0xD5, 0xFF, + 0x06, 0xE0, 0x21, 0x46, 0x16, 0xA0, 0x01, 0xF0, + 0xD0, 0xFF, 0x01, 0x20, 0xFF, 0xF7, 0xDA, 0xF9, + 0xFF, 0xF7, 0x94, 0xFB, 0x00, 0x28, 0x04, 0xD1, + 0x20, 0x46, 0x64, 0x1E, 0x24, 0xB2, 0x00, 0x28, + 0xEF, 0xD1, 0x64, 0x1C, 0x08, 0xD1, 0x01, 0x20, + 0xFF, 0xF7, 0x0E, 0xF9, 0x10, 0xA0, 0x01, 0xF0, + 0xBC, 0xFF, 0x0A, 0x20, 0xFF, 0xF7, 0xC6, 0xF9, + 0x00, 0x20, 0xFF, 0xF7, 0x05, 0xF9, 0x10, 0xBD, + 0x88, 0x02, 0x00, 0x20, 0x46, 0x54, 0x20, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x20, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x20, 0x28, 0x30, 0x78, + 0x25, 0x34, 0x78, 0x29, 0x0D, 0x0A, 0x00, 0x00, + 0x49, 0x4E, 0x54, 0x20, 0x4C, 0x6F, 0x77, 0x21, + 0x21, 0x28, 0x25, 0x64, 0x29, 0x0D, 0x0A, 0x00, + 0x49, 0x4E, 0x54, 0x20, 0x70, 0x61, 0x64, 0x20, + 0x69, 0x73, 0x20, 0x6C, 0x6F, 0x77, 0x20, 0x6C, + 0x65, 0x76, 0x65, 0x6C, 0x20, 0x21, 0x21, 0x00, + 0x70, 0xB5, 0x17, 0x48, 0x5A, 0x25, 0x01, 0x78, + 0x16, 0x48, 0x00, 0x29, 0x25, 0xD0, 0x09, 0x21, + 0x01, 0x70, 0x15, 0x4C, 0x01, 0x21, 0xE0, 0x89, + 0x89, 0x02, 0x88, 0x42, 0x01, 0xD0, 0x03, 0x20, + 0x60, 0x71, 0x11, 0x48, 0x30, 0x21, 0x28, 0x30, + 0xFF, 0xF7, 0x3B, 0xF8, 0x00, 0x20, 0x60, 0x72, + 0xA0, 0x71, 0x60, 0x62, 0x16, 0x21, 0x21, 0x72, + 0x0C, 0x49, 0x20, 0x71, 0x08, 0x70, 0xA5, 0x81, + 0x60, 0x81, 0x60, 0x8A, 0x09, 0x21, 0x09, 0x03, + 0x08, 0x43, 0x60, 0x82, 0x03, 0x20, 0x40, 0x02, + 0xE0, 0x61, 0x0D, 0x20, 0xC0, 0x01, 0x20, 0x62, + 0x70, 0xBD, 0x05, 0x70, 0xD9, 0xE7, 0x00, 0x00, + 0x92, 0x01, 0x00, 0x20, 0x94, 0x01, 0x00, 0x20, + 0x38, 0x01, 0x00, 0x20, 0x93, 0x01, 0x00, 0x20, + 0x0E, 0x48, 0x03, 0x21, 0x41, 0x71, 0x0E, 0x49, + 0x41, 0x61, 0x0D, 0x49, 0x60, 0x31, 0x81, 0x61, + 0x01, 0x21, 0x01, 0x70, 0x07, 0x22, 0x42, 0x70, + 0x0A, 0x4B, 0x05, 0x22, 0x1A, 0x70, 0x0A, 0x4B, + 0x1A, 0x70, 0x0A, 0x4B, 0x55, 0x22, 0xDA, 0x70, + 0x04, 0x22, 0x02, 0x82, 0x00, 0x22, 0xC2, 0x70, + 0x09, 0x22, 0x12, 0x03, 0x42, 0x82, 0x81, 0x70, + 0x70, 0x47, 0x00, 0x00, 0x38, 0x01, 0x00, 0x20, + 0x00, 0x00, 0x04, 0x20, 0x92, 0x01, 0x00, 0x20, + 0x0D, 0x08, 0x00, 0x20, 0x90, 0x02, 0x00, 0x20, + 0x04, 0x22, 0x0F, 0x49, 0x0C, 0x28, 0x10, 0xD0, + 0x8B, 0x05, 0x0D, 0x28, 0x08, 0x6A, 0x10, 0xD0, + 0x18, 0x43, 0x08, 0x62, 0x88, 0x6A, 0x10, 0x43, + 0x88, 0x62, 0x0A, 0x4A, 0x01, 0x20, 0x10, 0x70, + 0xC8, 0x68, 0xC8, 0x60, 0x88, 0x6A, 0x88, 0x62, + 0x70, 0x47, 0x08, 0x6A, 0x40, 0x00, 0x40, 0x08, + 0x00, 0xE0, 0x18, 0x43, 0x08, 0x62, 0x88, 0x6A, + 0x90, 0x43, 0x88, 0x62, 0xF0, 0xE7, 0x00, 0x00, + 0x00, 0x06, 0x00, 0x50, 0xCC, 0x00, 0x00, 0x20, + 0x00, 0xB5, 0x08, 0x49, 0x0A, 0x28, 0x05, 0xD0, + 0x07, 0x48, 0x00, 0x0C, 0x48, 0x63, 0x07, 0x48, + 0x08, 0x63, 0x00, 0xBD, 0x06, 0x48, 0x00, 0x68, + 0x08, 0x62, 0x0D, 0x20, 0xFF, 0xF7, 0xCC, 0xFF, + 0x00, 0xBD, 0x00, 0x00, 0x00, 0x06, 0x00, 0x50, + 0xBC, 0x02, 0x00, 0x20, 0xCC, 0x02, 0x00, 0x20, + 0xC0, 0x02, 0x00, 0x20, 0x10, 0xB5, 0x1B, 0x49, + 0x00, 0x20, 0x03, 0x00, 0xFF, 0xF7, 0x12, 0xF8, + 0x0C, 0x07, 0x0A, 0x0E, 0x26, 0x26, 0x11, 0x14, + 0x17, 0x1A, 0x1D, 0x20, 0x23, 0x26, 0x16, 0x4A, + 0x0A, 0x80, 0x1E, 0xE0, 0x14, 0x4A, 0x12, 0x1D, + 0x4A, 0x80, 0x1A, 0xE0, 0x13, 0x4A, 0x8A, 0x80, + 0x17, 0xE0, 0x13, 0x4A, 0x4A, 0x81, 0x14, 0xE0, + 0x12, 0x4A, 0x8A, 0x81, 0x11, 0xE0, 0x12, 0x4A, + 0xCA, 0x81, 0x0E, 0xE0, 0x11, 0x4A, 0x0A, 0x82, + 0x0B, 0xE0, 0x11, 0x4A, 0x4A, 0x82, 0x08, 0xE0, + 0x10, 0x4A, 0x8A, 0x82, 0x05, 0xE0, 0x10, 0x4A, + 0xCA, 0x82, 0x02, 0xE0, 0x0F, 0x4A, 0x43, 0x00, + 0xCA, 0x52, 0x40, 0x1C, 0x80, 0xB2, 0x0C, 0x28, + 0xCF, 0xD3, 0x0D, 0xA0, 0x01, 0xF0, 0xBD, 0xFE, + 0x10, 0xBD, 0x00, 0x00, 0xCC, 0x02, 0x00, 0x20, + 0x90, 0x02, 0x00, 0x20, 0x88, 0x02, 0x00, 0x20, + 0x34, 0x01, 0x00, 0x20, 0xE0, 0x06, 0x00, 0x20, + 0xE4, 0x06, 0x00, 0x20, 0xD8, 0x06, 0x00, 0x20, + 0xC0, 0x02, 0x00, 0x20, 0xBC, 0x02, 0x00, 0x20, + 0x9C, 0x01, 0x00, 0x20, 0xC4, 0x02, 0x00, 0x20, + 0x49, 0x32, 0x43, 0x20, 0x4F, 0x4B, 0x0D, 0x0A, + 0x00, 0x00, 0x00, 0x00, 0x70, 0xB5, 0x01, 0x25, + 0xCA, 0x07, 0x0A, 0xD0, 0x00, 0x20, 0x70, 0xBD, + 0x93, 0x00, 0xC4, 0x58, 0x66, 0x1C, 0x02, 0xD0, + 0x1B, 0x18, 0x5B, 0x68, 0x23, 0x60, 0x92, 0x1C, + 0x92, 0xB2, 0x8A, 0x42, 0xF4, 0xD3, 0x28, 0x46, + 0x70, 0xBD, 0x00, 0x00, 0x10, 0xB5, 0x1D, 0x49, + 0x0A, 0x68, 0x1C, 0x48, 0x40, 0x30, 0x02, 0x61, + 0x4A, 0x68, 0x42, 0x61, 0x8A, 0x68, 0x82, 0x61, + 0xC9, 0x68, 0xC1, 0x61, 0x82, 0x69, 0xF0, 0x21, + 0x8A, 0x43, 0x82, 0x61, 0x82, 0x69, 0x0A, 0x43, + 0x82, 0x61, 0x41, 0x69, 0x49, 0x04, 0x04, 0xD5, + 0x41, 0x69, 0x01, 0x22, 0x52, 0x03, 0x89, 0x1A, + 0x41, 0x61, 0x10, 0x48, 0x40, 0x38, 0x01, 0x68, + 0x01, 0x22, 0x12, 0x06, 0x11, 0x43, 0x01, 0x60, + 0x30, 0x21, 0x0D, 0x48, 0xFF, 0xF7, 0xC6, 0xFF, + 0x0B, 0x48, 0x40, 0x21, 0xC0, 0x30, 0xFF, 0xF7, + 0xC1, 0xFF, 0x0B, 0x20, 0xFE, 0xF7, 0x46, 0xFF, + 0x03, 0x20, 0xFE, 0xF7, 0x43, 0xFF, 0x00, 0x20, + 0xFE, 0xF7, 0x40, 0xFF, 0x05, 0x20, 0xFE, 0xF7, + 0x3D, 0xFF, 0x09, 0x20, 0xFE, 0xF7, 0x3A, 0xFF, + 0x01, 0x20, 0x10, 0xBD, 0x40, 0x14, 0x00, 0x50, + 0x50, 0x39, 0x00, 0x00, 0xFE, 0xB5, 0x27, 0x48, + 0x0A, 0x25, 0x45, 0x5F, 0x01, 0x26, 0x29, 0x46, + 0x25, 0xA0, 0x01, 0xF0, 0x42, 0xFE, 0x2A, 0x48, + 0x72, 0x04, 0x01, 0x68, 0x29, 0x4F, 0x91, 0x43, + 0x00, 0x24, 0x01, 0x60, 0x28, 0x48, 0x00, 0x68, + 0x00, 0x19, 0xC0, 0x7E, 0x41, 0x28, 0x18, 0xD0, + 0x26, 0x49, 0x60, 0x00, 0x0A, 0x5E, 0x26, 0x49, + 0x0B, 0x5A, 0xD0, 0x1A, 0x00, 0xB2, 0xA8, 0x42, + 0x0B, 0xDA, 0x00, 0x90, 0x21, 0x46, 0x01, 0x95, + 0x22, 0xA0, 0x01, 0xF0, 0x26, 0xFE, 0x38, 0x5D, + 0x20, 0x21, 0x08, 0x43, 0x38, 0x55, 0x00, 0x26, + 0x03, 0xE0, 0x38, 0x5D, 0xDF, 0x21, 0x08, 0x40, + 0x38, 0x55, 0x64, 0x1C, 0xE4, 0xB2, 0x30, 0x2C, + 0xDC, 0xD3, 0x01, 0x2E, 0x18, 0xD0, 0x14, 0x4A, + 0x01, 0x21, 0x10, 0x68, 0x49, 0x04, 0x08, 0x43, + 0x10, 0x60, 0x23, 0xA0, 0x01, 0xF0, 0x0D, 0xFE, + 0x27, 0xA0, 0x01, 0xF0, 0x0A, 0xFE, 0x01, 0x21, + 0x10, 0x48, 0x01, 0xF0, 0xED, 0xF9, 0x27, 0xA0, + 0x01, 0xF0, 0x03, 0xFE, 0x01, 0x21, 0x0E, 0x48, + 0x01, 0xF0, 0xE6, 0xF9, 0x30, 0x46, 0xFE, 0xBD, + 0x27, 0xA0, 0xEB, 0xE7, 0x84, 0x06, 0x00, 0x20, + 0x4F, 0x70, 0x65, 0x6E, 0x20, 0x43, 0x43, 0x20, + 0x43, 0x68, 0x65, 0x63, 0x6B, 0x20, 0x54, 0x48, + 0x44, 0x3D, 0x25, 0x64, 0x20, 0x0D, 0x0A, 0x00, + 0x0C, 0x05, 0x00, 0x20, 0xD8, 0x04, 0x00, 0x20, + 0x98, 0x01, 0x00, 0x20, 0xAC, 0x03, 0x00, 0x20, + 0x4C, 0x07, 0x00, 0x20, 0x4F, 0x70, 0x65, 0x6E, + 0x20, 0x43, 0x43, 0x20, 0x4E, 0x47, 0x20, 0x50, + 0x69, 0x6E, 0x5B, 0x25, 0x64, 0x5D, 0x2C, 0x20, + 0x44, 0x69, 0x66, 0x66, 0x3D, 0x25, 0x64, 0x2D, + 0x25, 0x64, 0x3D, 0x25, 0x64, 0x2C, 0x20, 0x4C, + 0x6F, 0x77, 0x54, 0x48, 0x44, 0x20, 0x3D, 0x20, + 0x25, 0x64, 0x0D, 0x0A, 0x00, 0x00, 0x00, 0x00, + 0x53, 0x69, 0x6E, 0x67, 0x6C, 0x65, 0x20, 0x43, + 0x43, 0x20, 0x54, 0x65, 0x73, 0x74, 0x20, 0x5B, + 0x4E, 0x47, 0x5D, 0x0D, 0x0A, 0x00, 0x00, 0x00, + 0x4F, 0x70, 0x65, 0x6E, 0x20, 0x43, 0x43, 0x0D, + 0x0A, 0x00, 0x00, 0x00, 0x47, 0x6F, 0x6C, 0x64, + 0x65, 0x6E, 0x20, 0x4F, 0x70, 0x65, 0x6E, 0x20, + 0x43, 0x43, 0x0D, 0x0A, 0x00, 0x00, 0x00, 0x00, + 0x53, 0x69, 0x6E, 0x67, 0x6C, 0x65, 0x20, 0x43, + 0x43, 0x20, 0x54, 0x65, 0x73, 0x74, 0x20, 0x5B, + 0x50, 0x61, 0x73, 0x73, 0x5D, 0x0D, 0x0A, 0x00, + 0xFE, 0xB5, 0x32, 0x49, 0x02, 0x20, 0x08, 0x5E, + 0x00, 0x90, 0x0A, 0x20, 0x08, 0x5E, 0x03, 0x21, + 0x89, 0x03, 0x48, 0x43, 0x00, 0x14, 0x01, 0x90, + 0x2D, 0x48, 0x01, 0x27, 0x01, 0x68, 0xB9, 0x43, + 0x00, 0x24, 0x01, 0x60, 0x2B, 0x48, 0x00, 0x68, + 0x00, 0x19, 0xC0, 0x7E, 0x41, 0x28, 0x30, 0xD0, + 0x65, 0x00, 0x29, 0x4A, 0x29, 0x48, 0x56, 0x5F, + 0x41, 0x5B, 0x00, 0x20, 0x71, 0x1A, 0x0B, 0xB2, + 0x53, 0x53, 0x00, 0x99, 0x8B, 0x42, 0x05, 0xDA, + 0x32, 0x46, 0x21, 0x46, 0x24, 0xA0, 0x01, 0xF0, + 0x78, 0xFD, 0x01, 0x20, 0x00, 0x2E, 0x0E, 0xD1, + 0x2B, 0x49, 0x2C, 0x4A, 0x49, 0x5B, 0x52, 0x5B, + 0x89, 0x1A, 0x0A, 0xB2, 0x01, 0x99, 0x8A, 0x42, + 0x05, 0xDA, 0x0B, 0x46, 0x21, 0x46, 0x28, 0xA0, + 0x01, 0xF0, 0x67, 0xFD, 0x01, 0xE0, 0x00, 0x28, + 0x06, 0xD0, 0x2F, 0x48, 0x01, 0x22, 0x01, 0x5D, + 0x11, 0x43, 0x01, 0x55, 0x00, 0x27, 0x04, 0xE0, + 0x2B, 0x48, 0x01, 0x5D, 0x49, 0x08, 0x49, 0x00, + 0x01, 0x55, 0x64, 0x1C, 0xE4, 0xB2, 0x30, 0x2C, + 0xC4, 0xD3, 0x00, 0x99, 0x01, 0x2F, 0x10, 0xD0, + 0x26, 0xA0, 0x01, 0xF0, 0x4E, 0xFD, 0x0A, 0x49, + 0x01, 0x22, 0x08, 0x68, 0x10, 0x43, 0x08, 0x60, + 0x29, 0xA0, 0x01, 0xF0, 0x46, 0xFD, 0x01, 0x21, + 0x07, 0x48, 0x01, 0xF0, 0x29, 0xF9, 0x38, 0x46, + 0xFE, 0xBD, 0x2A, 0xA0, 0x01, 0xF0, 0x3D, 0xFD, + 0xF2, 0xE7, 0x00, 0x00, 0x84, 0x06, 0x00, 0x20, + 0x0C, 0x05, 0x00, 0x20, 0x98, 0x01, 0x00, 0x20, + 0x48, 0x03, 0x00, 0x20, 0x74, 0x04, 0x00, 0x20, + 0x47, 0x6C, 0x6F, 0x62, 0x61, 0x6C, 0x20, 0x54, + 0x48, 0x44, 0x20, 0x4F, 0x70, 0x65, 0x6E, 0x20, + 0x4E, 0x47, 0x20, 0x44, 0x61, 0x74, 0x61, 0x5B, + 0x25, 0x64, 0x5D, 0x5B, 0x25, 0x64, 0x3A, 0x25, + 0x64, 0x5D, 0x0D, 0x0A, 0x00, 0x00, 0x00, 0x00, + 0xAC, 0x03, 0x00, 0x20, 0x4C, 0x07, 0x00, 0x20, + 0x4F, 0x70, 0x65, 0x6E, 0x20, 0x4E, 0x47, 0x20, + 0x50, 0x69, 0x6E, 0x5B, 0x25, 0x64, 0x5D, 0x2C, + 0x20, 0x43, 0x43, 0x5F, 0x44, 0x69, 0x66, 0x66, + 0x3D, 0x25, 0x64, 0x2C, 0x20, 0x54, 0x48, 0x44, + 0x3D, 0x25, 0x64, 0x0D, 0x0A, 0x00, 0x00, 0x00, + 0xD8, 0x04, 0x00, 0x20, 0x4F, 0x70, 0x65, 0x6E, + 0x20, 0x54, 0x65, 0x73, 0x74, 0x28, 0x54, 0x48, + 0x44, 0x3A, 0x25, 0x64, 0x29, 0x20, 0x5B, 0x4E, + 0x47, 0x5D, 0x0D, 0x0A, 0x00, 0x00, 0x00, 0x00, + 0x51, 0x75, 0x69, 0x63, 0x6B, 0x20, 0x2D, 0x20, + 0x42, 0x61, 0x73, 0x65, 0x6C, 0x69, 0x6E, 0x65, + 0x0D, 0x0A, 0x00, 0x00, 0x4F, 0x70, 0x65, 0x6E, + 0x20, 0x54, 0x65, 0x73, 0x74, 0x28, 0x54, 0x48, + 0x44, 0x3A, 0x25, 0x64, 0x29, 0x20, 0x5B, 0x50, + 0x61, 0x73, 0x73, 0x5D, 0x0D, 0x0A, 0x00, 0x00, + 0xF0, 0xB5, 0x17, 0x49, 0x00, 0x20, 0x7D, 0x27, + 0x09, 0x68, 0xFF, 0x00, 0x02, 0x46, 0x89, 0x07, + 0x24, 0xD5, 0x14, 0x49, 0x14, 0x4D, 0x09, 0x68, + 0xCE, 0x26, 0x0B, 0x18, 0xDB, 0x7E, 0x41, 0x2B, + 0x0A, 0xD0, 0x12, 0x4B, 0x44, 0x00, 0x1B, 0x5F, + 0xBB, 0x42, 0x00, 0xDA, 0x01, 0x22, 0x2B, 0x5C, + 0x9C, 0x07, 0x01, 0xD5, 0x33, 0x40, 0x2B, 0x54, + 0x40, 0x1C, 0xC0, 0xB2, 0x30, 0x28, 0xEC, 0xD3, + 0x00, 0x2A, 0x0B, 0xD0, 0x00, 0x20, 0x0A, 0x18, + 0xD2, 0x7E, 0x41, 0x2A, 0x02, 0xD0, 0x2A, 0x5C, + 0x32, 0x40, 0x2A, 0x54, 0x40, 0x1C, 0xC0, 0xB2, + 0x30, 0x28, 0xF4, 0xD3, 0x01, 0x20, 0xF0, 0xBD, + 0x0C, 0x05, 0x00, 0x20, 0x98, 0x01, 0x00, 0x20, + 0xD8, 0x04, 0x00, 0x20, 0xE4, 0x02, 0x00, 0x20, + 0x70, 0xB5, 0x20, 0x48, 0x01, 0x25, 0xC4, 0x7D, + 0x1F, 0x48, 0x80, 0x88, 0x40, 0x07, 0x01, 0xD4, + 0x01, 0x20, 0x70, 0xBD, 0xFF, 0xF7, 0x34, 0xFB, + 0x1C, 0x48, 0xFE, 0xF7, 0x5F, 0xFF, 0x00, 0x28, + 0x0F, 0xD1, 0x1B, 0xA0, 0x01, 0xF0, 0x91, 0xFC, + 0x01, 0x20, 0x20, 0x49, 0x00, 0x25, 0x08, 0x70, + 0x00, 0x2C, 0x06, 0xD0, 0x64, 0x1E, 0xE4, 0xB2, + 0x21, 0x46, 0x1D, 0xA0, 0x01, 0xF0, 0x85, 0xFC, + 0xE8, 0xE7, 0xFE, 0xF7, 0x95, 0xFF, 0xFF, 0xF7, + 0x0B, 0xFA, 0xFF, 0xF7, 0xDD, 0xFE, 0x00, 0x28, + 0x07, 0xD1, 0x00, 0x25, 0x00, 0x2C, 0x04, 0xD0, + 0x64, 0x1E, 0xE4, 0xB2, 0x21, 0x46, 0x1C, 0xA0, + 0x0A, 0xE0, 0xFF, 0xF7, 0x27, 0xFE, 0x00, 0x28, + 0x0A, 0xD1, 0x00, 0x25, 0x00, 0x2C, 0x07, 0xD0, + 0x64, 0x1E, 0xE4, 0xB2, 0x21, 0x46, 0x1C, 0xA0, + 0x01, 0xF0, 0x67, 0xFC, 0x01, 0x25, 0xC9, 0xE7, + 0x28, 0x46, 0x70, 0xBD, 0xA8, 0x06, 0x00, 0x20, + 0x74, 0x06, 0x00, 0x20, 0xAC, 0x03, 0x00, 0x20, + 0x4F, 0x70, 0x65, 0x6E, 0x20, 0x54, 0x65, 0x73, + 0x74, 0x20, 0x43, 0x61, 0x6C, 0x69, 0x62, 0x72, + 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x4E, 0x47, + 0x21, 0x0D, 0x0A, 0x00, 0xD8, 0x04, 0x00, 0x20, + 0x4F, 0x70, 0x65, 0x6E, 0x20, 0x43, 0x61, 0x6C, + 0x69, 0x62, 0x72, 0x61, 0x74, 0x69, 0x6F, 0x6E, + 0x20, 0x52, 0x65, 0x74, 0x72, 0x79, 0x20, 0x28, + 0x25, 0x64, 0x29, 0x0D, 0x0A, 0x00, 0x00, 0x00, + 0x4F, 0x70, 0x65, 0x6E, 0x20, 0x54, 0x65, 0x73, + 0x74, 0x20, 0x52, 0x65, 0x74, 0x72, 0x79, 0x20, + 0x28, 0x25, 0x64, 0x29, 0x0D, 0x0A, 0x00, 0x00, + 0x4F, 0x70, 0x65, 0x6E, 0x20, 0x43, 0x43, 0x20, + 0x52, 0x65, 0x74, 0x72, 0x79, 0x20, 0x28, 0x25, + 0x64, 0x29, 0x0D, 0x0A, 0x00, 0x00, 0x00, 0x00, + 0x10, 0xB5, 0x17, 0x4A, 0x11, 0x78, 0x81, 0x42, + 0x29, 0xD0, 0x16, 0x49, 0x0B, 0x68, 0x01, 0x24, + 0xA4, 0x02, 0x23, 0x43, 0x0B, 0x60, 0x10, 0x70, + 0x05, 0x22, 0x40, 0x24, 0x12, 0x49, 0x12, 0x07, + 0x80, 0x23, 0x00, 0x28, 0x09, 0xD0, 0x08, 0x68, + 0x40, 0x06, 0x0C, 0xD4, 0x08, 0x68, 0x20, 0x43, + 0x08, 0x60, 0xD0, 0x68, 0x98, 0x43, 0xD0, 0x60, + 0x05, 0xE0, 0xD0, 0x68, 0x18, 0x43, 0xD0, 0x60, + 0x08, 0x68, 0xA0, 0x43, 0x08, 0x60, 0x08, 0x68, + 0x18, 0x43, 0x08, 0x60, 0xD0, 0x68, 0x01, 0x21, + 0xC9, 0x03, 0x08, 0x43, 0xD0, 0x60, 0xE1, 0x20, + 0x00, 0x02, 0x01, 0xF0, 0x3B, 0xF8, 0x10, 0xBD, + 0x04, 0x00, 0x00, 0x20, 0x00, 0x10, 0x00, 0x50, + 0x00, 0x11, 0x00, 0x50, 0x06, 0x49, 0x83, 0x20, + 0x08, 0x70, 0x06, 0x49, 0x00, 0x20, 0x08, 0x70, + 0x05, 0x48, 0x00, 0x68, 0x05, 0x49, 0x40, 0x05, + 0x40, 0x0F, 0x08, 0x73, 0x70, 0x47, 0x00, 0x00, + 0x20, 0x08, 0x00, 0x20, 0x30, 0x00, 0x00, 0x20, + 0x00, 0x11, 0x00, 0x50, 0x20, 0x00, 0x00, 0x20, + 0xF0, 0xB5, 0x04, 0x46, 0x8F, 0xB0, 0xA6, 0x48, + 0x0A, 0x90, 0xA5, 0x48, 0xA6, 0x4D, 0x60, 0x30, + 0x0B, 0x90, 0xA4, 0x48, 0x03, 0x90, 0xA5, 0x48, + 0x05, 0x95, 0x01, 0x68, 0x22, 0x46, 0xA4, 0xA0, + 0x01, 0xF0, 0xC7, 0xFB, 0x03, 0x98, 0x08, 0x90, + 0xA5, 0x48, 0xA6, 0x4A, 0x00, 0x21, 0x09, 0x95, + 0x8B, 0x00, 0x08, 0x9D, 0x49, 0x1C, 0xE8, 0x50, + 0x09, 0x9D, 0x89, 0xB2, 0xEA, 0x50, 0x18, 0x29, + 0xF6, 0xD3, 0x01, 0x20, 0x80, 0x02, 0x06, 0x90, + 0x20, 0x46, 0x07, 0x94, 0x50, 0x30, 0x00, 0xB2, + 0x50, 0x3C, 0x0C, 0x90, 0x20, 0xB2, 0x0D, 0x90, + 0x00, 0xF0, 0x96, 0xFC, 0x00, 0xF0, 0xC6, 0xFE, + 0x00, 0xF0, 0x6E, 0xF9, 0x98, 0x48, 0x00, 0x78, + 0xC0, 0x07, 0x18, 0xD1, 0x01, 0x90, 0x00, 0x20, + 0x08, 0x9A, 0x81, 0x00, 0x53, 0x58, 0x0A, 0x9A, + 0x40, 0x1C, 0x53, 0x50, 0x09, 0x9A, 0x80, 0xB2, + 0x53, 0x58, 0x0B, 0x9A, 0x18, 0x28, 0x53, 0x50, + 0xF2, 0xD3, 0x00, 0xF0, 0xC7, 0xFD, 0x00, 0xF0, + 0xAD, 0xFE, 0x00, 0xF0, 0x55, 0xF9, 0x8C, 0x48, + 0x00, 0x78, 0xC0, 0x07, 0x02, 0xD0, 0x00, 0x20, + 0x0F, 0xB0, 0xF0, 0xBD, 0x3B, 0x46, 0x89, 0xA0, + 0x00, 0x9A, 0x01, 0x99, 0x01, 0xF0, 0x81, 0xFB, + 0x00, 0x25, 0x2D, 0xE0, 0x00, 0x2D, 0x2B, 0xD0, + 0x01, 0x98, 0x05, 0x28, 0x73, 0xD9, 0x30, 0x2F, + 0x71, 0xD2, 0x7A, 0x4C, 0x05, 0x98, 0x40, 0x3C, + 0x04, 0x90, 0x60, 0x6B, 0x03, 0x26, 0x36, 0x05, + 0x01, 0x21, 0xB0, 0x43, 0x49, 0x05, 0x40, 0x18, + 0x60, 0x63, 0x00, 0xF0, 0x9F, 0xFD, 0x00, 0xF0, + 0x85, 0xFE, 0x00, 0xF0, 0x2D, 0xF9, 0x60, 0x6B, + 0x01, 0x21, 0xB0, 0x43, 0x09, 0x05, 0x40, 0x18, + 0x60, 0x63, 0x00, 0x27, 0x00, 0x26, 0x79, 0x48, + 0x00, 0x68, 0x80, 0x19, 0xC0, 0x7E, 0x41, 0x28, + 0x0E, 0xD0, 0x70, 0x00, 0x04, 0x99, 0x02, 0x90, + 0x0C, 0x5A, 0x16, 0x2E, 0x12, 0xD0, 0x1A, 0xE0, + 0x00, 0x98, 0x30, 0x28, 0x77, 0xD0, 0x03, 0x98, + 0x04, 0x90, 0x00, 0x20, 0x00, 0x90, 0xE9, 0xE7, + 0x00, 0x2D, 0x02, 0xD0, 0x7F, 0x1C, 0xBF, 0xB2, + 0x68, 0xE0, 0x00, 0x98, 0x40, 0x1C, 0x80, 0xB2, + 0x00, 0x90, 0x63, 0xE0, 0x6A, 0x48, 0x2C, 0x23, + 0x00, 0x68, 0x22, 0x46, 0xC3, 0x5E, 0x31, 0x46, + 0x68, 0xA0, 0x01, 0xF0, 0x36, 0xFB, 0x01, 0x98, + 0x0A, 0x28, 0x0C, 0xD2, 0x64, 0x49, 0x02, 0x98, + 0x09, 0x68, 0x09, 0x5E, 0x07, 0x98, 0x81, 0x42, + 0x01, 0xDA, 0x06, 0x98, 0x84, 0x43, 0x06, 0x98, + 0x40, 0x08, 0x04, 0x43, 0x32, 0xE0, 0x5E, 0x48, + 0x02, 0x99, 0x00, 0x68, 0x41, 0x5E, 0x00, 0x29, + 0x01, 0xDB, 0x08, 0x46, 0x00, 0xE0, 0x48, 0x42, + 0x00, 0xB2, 0x40, 0x30, 0xC2, 0x17, 0x52, 0x0E, + 0x10, 0x18, 0x0D, 0x9A, 0xC0, 0x11, 0x91, 0x42, + 0x10, 0xDA, 0x21, 0x05, 0x06, 0xD5, 0x20, 0x1A, + 0x84, 0xB2, 0x01, 0x20, 0xC0, 0x02, 0x84, 0x42, + 0x11, 0xD3, 0x17, 0xE0, 0x84, 0x42, 0x03, 0xDD, + 0x20, 0x1A, 0x84, 0xB2, 0x12, 0xE0, 0x2E, 0xE0, + 0x00, 0x24, 0x0F, 0xE0, 0x0C, 0x9A, 0x91, 0x42, + 0x15, 0xDD, 0x21, 0x05, 0x05, 0xD5, 0x20, 0x18, + 0x84, 0xB2, 0x4D, 0x48, 0x04, 0xE0, 0x04, 0x46, + 0x04, 0xE0, 0x20, 0x18, 0x84, 0xB2, 0x4B, 0x48, + 0x84, 0x42, 0xF8, 0xD8, 0x00, 0x2D, 0x0F, 0xD0, + 0x01, 0x20, 0xC0, 0x02, 0x04, 0x43, 0x05, 0x99, + 0x02, 0x98, 0x0C, 0x52, 0x0E, 0xE0, 0x00, 0x2D, + 0x02, 0xD0, 0x7F, 0x1C, 0xBF, 0xB2, 0xF3, 0xE7, + 0x00, 0x98, 0x40, 0x1C, 0x80, 0xB2, 0x00, 0x90, + 0x03, 0x99, 0x02, 0x98, 0x0C, 0x52, 0x01, 0x98, + 0x05, 0x28, 0xE9, 0xD9, 0x76, 0x1C, 0xB6, 0xB2, + 0x30, 0x2E, 0x00, 0xD2, 0x77, 0xE7, 0x6D, 0x1C, + 0xED, 0xB2, 0x02, 0x2D, 0x00, 0xD2, 0x51, 0xE7, + 0x06, 0x98, 0x40, 0x08, 0x06, 0x90, 0x38, 0xA0, + 0x01, 0xF0, 0xCB, 0xFA, 0x00, 0x98, 0x30, 0x28, + 0x03, 0xD1, 0x30, 0x2F, 0x01, 0xD1, 0x40, 0x20, + 0x01, 0x90, 0x01, 0x98, 0x40, 0x1C, 0x80, 0xB2, + 0x01, 0x90, 0x40, 0x28, 0x00, 0xD8, 0x1A, 0xE7, + 0x1C, 0x4C, 0x40, 0x3C, 0x60, 0x6B, 0x03, 0x25, + 0x2D, 0x05, 0x01, 0x21, 0xA8, 0x43, 0x09, 0x05, + 0x40, 0x18, 0x60, 0x63, 0x00, 0xF0, 0xE6, 0xFC, + 0x00, 0xF0, 0xCC, 0xFD, 0x00, 0xF0, 0x74, 0xF8, + 0x21, 0x4E, 0x01, 0x21, 0x30, 0x68, 0x00, 0xF0, + 0x8F, 0xFE, 0x60, 0x6B, 0x01, 0x21, 0xA8, 0x43, + 0x49, 0x05, 0x40, 0x18, 0x60, 0x63, 0x00, 0xF0, + 0xD5, 0xFC, 0x00, 0xF0, 0xBB, 0xFD, 0x00, 0xF0, + 0x63, 0xF8, 0x01, 0x21, 0x30, 0x68, 0x00, 0xF0, + 0x7F, 0xFE, 0x60, 0x6B, 0x28, 0x43, 0x60, 0x63, + 0x00, 0xF0, 0xC8, 0xFC, 0x00, 0xF0, 0xAE, 0xFD, + 0x00, 0xF0, 0x56, 0xF8, 0x01, 0x21, 0x30, 0x68, + 0x00, 0xF0, 0x72, 0xFE, 0x01, 0x20, 0xFF, 0xE6, + 0x00, 0x20, 0x00, 0x50, 0xF4, 0x05, 0x00, 0x20, + 0x00, 0x00, 0x01, 0x20, 0xC0, 0x10, 0x00, 0x50, + 0x5B, 0x43, 0x46, 0x42, 0x3A, 0x25, 0x78, 0x3A, + 0x25, 0x64, 0x5D, 0x0D, 0x0A, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x04, 0x00, 0x0C, 0x00, 0x0C, + 0x30, 0x00, 0x00, 0x20, 0x5B, 0x25, 0x64, 0x3A, + 0x25, 0x64, 0x3A, 0x25, 0x64, 0x5D, 0x20, 0x0D, + 0x0A, 0x00, 0x00, 0x00, 0x98, 0x01, 0x00, 0x20, + 0x4C, 0x01, 0x00, 0x20, 0x25, 0x64, 0x2C, 0x30, + 0x78, 0x25, 0x78, 0x2C, 0x25, 0x64, 0x2C, 0x00, + 0xFF, 0x0F, 0x00, 0x00, 0xFF, 0x07, 0x00, 0x00, + 0x0D, 0x0A, 0x00, 0x00, 0x10, 0xB5, 0x0B, 0x49, + 0x30, 0x24, 0x00, 0x28, 0x0A, 0x4A, 0x0B, 0x4B, + 0x08, 0x68, 0x06, 0xD0, 0x20, 0x43, 0x08, 0x60, + 0x09, 0x48, 0x10, 0x60, 0x08, 0x48, 0x60, 0x30, + 0x05, 0xE0, 0xA0, 0x43, 0x08, 0x60, 0x07, 0x48, + 0x10, 0x60, 0x06, 0x48, 0x60, 0x30, 0x18, 0x60, + 0x10, 0xBD, 0x00, 0x00, 0x00, 0x10, 0x00, 0x50, + 0x4C, 0x01, 0x00, 0x20, 0x50, 0x01, 0x00, 0x20, + 0x00, 0x00, 0x04, 0x20, 0x00, 0x10, 0x04, 0x20, + 0xF0, 0xB5, 0x11, 0x48, 0x01, 0x68, 0x11, 0x4D, + 0x89, 0x06, 0x30, 0x22, 0x60, 0x35, 0x00, 0x29, + 0x0E, 0x4B, 0x0F, 0x4C, 0x0F, 0x4E, 0x10, 0x4F, + 0x01, 0x68, 0x0A, 0xDA, 0x91, 0x43, 0x01, 0x60, + 0x23, 0x60, 0x0E, 0x48, 0x35, 0x60, 0x07, 0x60, + 0x0B, 0x48, 0x0D, 0x49, 0x60, 0x30, 0x08, 0x60, + 0xF0, 0xBD, 0x11, 0x43, 0x01, 0x60, 0x08, 0x48, + 0x27, 0x60, 0x60, 0x30, 0x30, 0x60, 0x07, 0x48, + 0x03, 0x60, 0x07, 0x48, 0x05, 0x60, 0xF0, 0xBD, + 0x00, 0x10, 0x00, 0x50, 0x00, 0x00, 0x04, 0x20, + 0x40, 0x00, 0x00, 0x20, 0x44, 0x00, 0x00, 0x20, + 0x00, 0x10, 0x04, 0x20, 0x4C, 0x01, 0x00, 0x20, + 0x50, 0x01, 0x00, 0x20, 0x00, 0xB5, 0x09, 0x48, + 0x00, 0x78, 0x00, 0x28, 0x08, 0xD0, 0x01, 0x28, + 0x09, 0xD0, 0x02, 0x28, 0x03, 0xD1, 0x06, 0x49, + 0x04, 0x20, 0x00, 0xF0, 0x65, 0xF8, 0x00, 0xBD, + 0x04, 0x49, 0x01, 0x20, 0xF9, 0xE7, 0x04, 0x49, + 0x02, 0x20, 0xF6, 0xE7, 0x0D, 0x08, 0x00, 0x20, + 0x70, 0x05, 0x00, 0x20, 0x1C, 0x02, 0x00, 0x20, + 0x10, 0x05, 0x00, 0x20, 0xF8, 0xB5, 0x07, 0x46, + 0x00, 0xF0, 0xD2, 0xFA, 0x21, 0x4D, 0x60, 0x21, + 0x28, 0x46, 0xFE, 0xF7, 0xA2, 0xFA, 0x00, 0x24, + 0x00, 0xF0, 0xFC, 0xFC, 0xFF, 0xF7, 0xA4, 0xFF, + 0xFE, 0xF7, 0x39, 0xFB, 0x00, 0x2C, 0x0C, 0xD0, + 0x1B, 0x49, 0x00, 0x20, 0x0E, 0x68, 0x42, 0x00, + 0x51, 0x19, 0x0B, 0x88, 0xB2, 0x5A, 0x40, 0x1C, + 0x9A, 0x18, 0xC0, 0xB2, 0x0A, 0x80, 0x30, 0x28, + 0xF5, 0xD3, 0x64, 0x1C, 0xE4, 0xB2, 0x05, 0x2C, + 0xE6, 0xD3, 0x00, 0x20, 0x41, 0x00, 0x49, 0x19, + 0x00, 0x22, 0x8A, 0x5E, 0x40, 0x1C, 0x92, 0x10, + 0xC0, 0xB2, 0x0A, 0x80, 0x30, 0x28, 0xF5, 0xD3, + 0x39, 0x46, 0x0E, 0xA0, 0x01, 0xF0, 0xB9, 0xF9, + 0x01, 0x21, 0x28, 0x46, 0x00, 0xF0, 0x9C, 0xFD, + 0x01, 0x20, 0xFE, 0xF7, 0xBF, 0xFB, 0x0C, 0x4C, + 0x20, 0x78, 0x38, 0x42, 0x08, 0xD1, 0x0B, 0x48, + 0x60, 0x22, 0x29, 0x46, 0x00, 0x68, 0xFE, 0xF7, + 0x4F, 0xFA, 0x20, 0x78, 0x38, 0x43, 0x20, 0x70, + 0x01, 0x20, 0xF8, 0xBD, 0x00, 0x00, 0x01, 0x20, + 0x4C, 0x01, 0x00, 0x20, 0x42, 0x43, 0x5F, 0x4F, + 0x4B, 0x5F, 0x25, 0x64, 0x0D, 0x0A, 0x00, 0x00, + 0x38, 0x00, 0x00, 0x20, 0x48, 0x00, 0x00, 0x20, + 0xF0, 0xB5, 0x21, 0x48, 0x00, 0x22, 0x00, 0x68, + 0x20, 0x4C, 0xA0, 0x42, 0x28, 0xD8, 0x20, 0x48, + 0x00, 0x78, 0x04, 0x28, 0x27, 0xD1, 0x1F, 0x4D, + 0x1F, 0x4C, 0x2D, 0x68, 0x00, 0x20, 0x2E, 0x18, + 0xF6, 0x7E, 0x41, 0x2E, 0x08, 0xD0, 0x43, 0x00, + 0x1F, 0x19, 0x00, 0x26, 0xBE, 0x5F, 0xCB, 0x5E, + 0xF3, 0x1A, 0x00, 0xD5, 0x5B, 0x42, 0x1B, 0xB2, + 0x9A, 0x42, 0x00, 0xDA, 0x1A, 0x46, 0x40, 0x1C, + 0xC0, 0xB2, 0x30, 0x28, 0xEB, 0xD3, 0x60, 0x20, + 0x40, 0x5D, 0x90, 0x35, 0x40, 0x1C, 0x50, 0x43, + 0x6A, 0x7B, 0x2B, 0x7B, 0x00, 0x11, 0x12, 0x02, + 0x00, 0xB2, 0x1A, 0x43, 0x82, 0x42, 0x03, 0xDA, + 0x0E, 0x49, 0x00, 0x20, 0x08, 0x70, 0xF0, 0xBD, + 0x00, 0x20, 0x42, 0x00, 0x8B, 0x5E, 0x00, 0x26, + 0xDD, 0x00, 0xEB, 0x1A, 0x15, 0x19, 0xAE, 0x5F, + 0x40, 0x1C, 0x9B, 0x19, 0xDB, 0x10, 0xC0, 0xB2, + 0x8B, 0x52, 0x30, 0x28, 0xF1, 0xD3, 0xF0, 0xBD, + 0x5C, 0x01, 0x00, 0x20, 0x40, 0x77, 0x1B, 0x00, + 0x93, 0x01, 0x00, 0x20, 0x98, 0x01, 0x00, 0x20, + 0x00, 0x00, 0x01, 0x20, 0x38, 0x00, 0x00, 0x20, + 0x70, 0xB5, 0x21, 0x48, 0x21, 0x49, 0x00, 0x78, + 0x00, 0x28, 0x06, 0xD0, 0x01, 0x28, 0x08, 0xD0, + 0x02, 0x28, 0x16, 0xD0, 0x05, 0x28, 0x2F, 0xD1, + 0x34, 0xE0, 0x01, 0x25, 0x2C, 0x46, 0x1C, 0x48, + 0x02, 0xE0, 0x02, 0x25, 0x1B, 0x48, 0x2C, 0x46, + 0x08, 0x60, 0x00, 0x22, 0x1A, 0x49, 0x28, 0x46, + 0xFE, 0xF7, 0x40, 0xFB, 0x00, 0x28, 0x18, 0xD0, + 0x18, 0x48, 0x00, 0x78, 0x00, 0x28, 0x04, 0xD0, + 0x13, 0xE0, 0x04, 0x25, 0x2C, 0x46, 0x16, 0x48, + 0xEE, 0xE7, 0x16, 0x48, 0x41, 0x6B, 0x03, 0x22, + 0x12, 0x05, 0x11, 0x43, 0x41, 0x63, 0x14, 0x48, + 0x00, 0xF0, 0x4C, 0xFA, 0x00, 0x28, 0x0B, 0xD1, + 0x20, 0x46, 0xFF, 0xF7, 0x27, 0xFF, 0x00, 0x28, + 0x06, 0xD1, 0x01, 0x22, 0x21, 0x46, 0x28, 0x46, + 0x00, 0xF0, 0x1E, 0xF8, 0x00, 0x28, 0x04, 0xD0, + 0x0C, 0x48, 0x00, 0x78, 0xC0, 0x07, 0x01, 0xD0, + 0x00, 0x20, 0x70, 0xBD, 0x01, 0x20, 0x70, 0xBD, + 0x92, 0x01, 0x00, 0x20, 0x48, 0x00, 0x00, 0x20, + 0x1C, 0x02, 0x00, 0x20, 0x10, 0x05, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x50, 0x3B, 0x01, 0x00, 0x20, + 0x70, 0x05, 0x00, 0x20, 0x80, 0x10, 0x00, 0x50, + 0x00, 0x00, 0x01, 0x20, 0x30, 0x00, 0x00, 0x20, + 0xF7, 0xB5, 0x07, 0x46, 0x14, 0x48, 0x00, 0x25, + 0x05, 0x70, 0x78, 0x07, 0x1D, 0xD0, 0x01, 0x21, + 0x02, 0x20, 0x00, 0xF0, 0x45, 0xFB, 0x11, 0x4C, + 0x60, 0x6B, 0x03, 0x26, 0x36, 0x05, 0x01, 0x21, + 0xB0, 0x43, 0x09, 0x05, 0x40, 0x18, 0x60, 0x63, + 0x00, 0x20, 0xFF, 0xF7, 0xFD, 0xFC, 0x00, 0x28, + 0x0D, 0xD0, 0x01, 0x98, 0xFF, 0xF7, 0xE6, 0xFE, + 0x01, 0x22, 0x09, 0x49, 0x38, 0x46, 0xFE, 0xF7, + 0xE1, 0xFA, 0x08, 0x48, 0x05, 0x70, 0x08, 0x48, + 0x05, 0x70, 0x01, 0x20, 0xFE, 0xBD, 0x60, 0x6B, + 0x30, 0x43, 0x60, 0x63, 0x00, 0x20, 0xFE, 0xBD, + 0x90, 0x01, 0x00, 0x20, 0x80, 0x10, 0x00, 0x50, + 0x00, 0x20, 0x00, 0x50, 0x3B, 0x01, 0x00, 0x20, + 0x3C, 0x01, 0x00, 0x20, 0x70, 0xB5, 0x10, 0x4C, + 0x60, 0x6B, 0x03, 0x25, 0x2D, 0x05, 0x01, 0x26, + 0xA8, 0x43, 0x36, 0x05, 0x80, 0x19, 0x60, 0x63, + 0x0C, 0x49, 0x02, 0x20, 0x08, 0x5E, 0xFF, 0xF7, + 0xCF, 0xFC, 0x00, 0x28, 0x60, 0x6B, 0x09, 0xD0, + 0xA8, 0x43, 0x80, 0x19, 0x60, 0x63, 0x08, 0x49, + 0x00, 0x20, 0x08, 0x70, 0x07, 0x49, 0x08, 0x70, + 0x01, 0x20, 0x70, 0xBD, 0xA8, 0x43, 0x80, 0x19, + 0x60, 0x63, 0x00, 0x20, 0x70, 0xBD, 0x00, 0x00, + 0x80, 0x10, 0x00, 0x50, 0xCE, 0x00, 0x00, 0x20, + 0x3B, 0x01, 0x00, 0x20, 0x3C, 0x01, 0x00, 0x20, + 0xF0, 0xB5, 0xB4, 0x48, 0x01, 0x22, 0x23, 0x23, + 0x13, 0x24, 0x92, 0x02, 0x5B, 0x01, 0xA4, 0x01, + 0x05, 0x46, 0x1C, 0xC5, 0x29, 0x21, 0x49, 0x01, + 0xC1, 0x60, 0xAF, 0x48, 0x07, 0x68, 0x38, 0x46, + 0x40, 0x30, 0x45, 0x7C, 0x06, 0x7C, 0x2D, 0x04, + 0x36, 0x02, 0x35, 0x43, 0x0C, 0x35, 0x15, 0x60, + 0x45, 0x7C, 0x06, 0x7C, 0x2D, 0x04, 0x36, 0x02, + 0x35, 0x43, 0x0C, 0x35, 0x55, 0x60, 0x01, 0x25, + 0x95, 0x60, 0xD5, 0x60, 0x05, 0x7D, 0xBC, 0x46, + 0x6E, 0x1E, 0x6D, 0x08, 0x6D, 0x1E, 0xF6, 0x05, + 0xED, 0x05, 0xF6, 0x09, 0xED, 0x0D, 0x2E, 0x43, + 0x16, 0x61, 0xA0, 0x4D, 0x55, 0x61, 0x03, 0x25, + 0x95, 0x61, 0x03, 0x26, 0x36, 0x02, 0x00, 0x25, + 0x16, 0x62, 0xD5, 0x61, 0x06, 0x7E, 0xD6, 0x62, + 0x06, 0x7E, 0x87, 0x7E, 0xF6, 0x19, 0x16, 0x63, + 0x87, 0x7E, 0x06, 0x7E, 0x7F, 0x00, 0xF6, 0x19, + 0x56, 0x63, 0x95, 0x63, 0x06, 0x7E, 0xBC, 0x36, + 0xD6, 0x63, 0x06, 0x7E, 0x87, 0x7E, 0xF6, 0x19, + 0xBC, 0x36, 0x16, 0x64, 0x07, 0x7E, 0x86, 0x7E, + 0x95, 0x64, 0x76, 0x00, 0xBC, 0x36, 0xBE, 0x19, + 0x56, 0x64, 0x67, 0x46, 0x55, 0x62, 0x8E, 0x4E, + 0x50, 0x37, 0x96, 0x62, 0xBC, 0x46, 0xFE, 0x7B, + 0xBF, 0x7B, 0x36, 0x02, 0x3E, 0x43, 0xF6, 0x1C, + 0xB7, 0x05, 0x8A, 0x4E, 0xBF, 0x0D, 0xBE, 0x19, + 0xD6, 0x64, 0x89, 0x4E, 0x16, 0x65, 0x46, 0x7C, + 0x0C, 0x3E, 0xF7, 0xB2, 0x05, 0x26, 0x76, 0x02, + 0xBE, 0x19, 0x56, 0x65, 0x06, 0x7D, 0x36, 0x02, + 0x21, 0x36, 0x96, 0x65, 0xD5, 0x65, 0x42, 0x7C, + 0x06, 0x7C, 0x12, 0x04, 0x36, 0x02, 0x32, 0x43, + 0x0C, 0x32, 0x1A, 0x60, 0x42, 0x7C, 0x06, 0x7C, + 0x12, 0x04, 0x36, 0x02, 0x32, 0x43, 0x0C, 0x32, + 0x5A, 0x60, 0x01, 0x22, 0x9A, 0x60, 0xDA, 0x60, + 0x42, 0x7D, 0x56, 0x1E, 0x52, 0x08, 0x52, 0x1E, + 0xF6, 0x05, 0xD2, 0x05, 0xF7, 0x09, 0xD2, 0x0D, + 0x17, 0x43, 0x72, 0x4A, 0x1F, 0x61, 0x5A, 0x61, + 0x03, 0x22, 0xDD, 0x61, 0x9A, 0x61, 0x12, 0x02, + 0x1A, 0x62, 0x02, 0x7E, 0xDA, 0x62, 0x02, 0x7E, + 0xC6, 0x7E, 0x92, 0x19, 0x1A, 0x63, 0xC6, 0x7E, + 0x02, 0x7E, 0x76, 0x00, 0x92, 0x19, 0x9D, 0x63, + 0x5A, 0x63, 0x02, 0x7E, 0x67, 0x46, 0xBC, 0x32, + 0xDA, 0x63, 0x02, 0x7E, 0xC6, 0x7E, 0x92, 0x19, + 0xBC, 0x32, 0x1A, 0x64, 0xC6, 0x7E, 0x02, 0x7E, + 0x76, 0x00, 0xBC, 0x36, 0x9D, 0x64, 0x92, 0x19, + 0x5D, 0x62, 0x5A, 0x64, 0x60, 0x4A, 0x9A, 0x62, + 0xFA, 0x7B, 0xBE, 0x7B, 0x12, 0x02, 0x32, 0x43, + 0xD2, 0x1C, 0x96, 0x05, 0x5D, 0x4A, 0xB6, 0x0D, + 0xB2, 0x18, 0xDA, 0x64, 0x5C, 0x4A, 0x1A, 0x65, + 0x42, 0x7C, 0x0C, 0x3A, 0xD6, 0xB2, 0x05, 0x22, + 0x52, 0x02, 0xB2, 0x18, 0x5A, 0x65, 0x42, 0x7D, + 0xDD, 0x65, 0x12, 0x02, 0x21, 0x32, 0x9A, 0x65, + 0xC2, 0x7C, 0x83, 0x7C, 0x12, 0x04, 0x1B, 0x02, + 0x1A, 0x43, 0x0C, 0x32, 0x23, 0x46, 0x1A, 0x60, + 0xC2, 0x7C, 0x83, 0x7C, 0x12, 0x04, 0x1B, 0x02, + 0x1A, 0x43, 0x0C, 0x32, 0x23, 0x46, 0x5A, 0x60, + 0x82, 0x7D, 0x52, 0x1E, 0xD3, 0x05, 0xDB, 0x0D, + 0x22, 0x46, 0x93, 0x60, 0x82, 0x7D, 0x52, 0x08, + 0x52, 0x1E, 0xD2, 0x05, 0x23, 0x46, 0xD2, 0x0D, + 0x1D, 0x61, 0xDA, 0x60, 0x47, 0x4A, 0x5A, 0x61, + 0x26, 0x46, 0x01, 0x23, 0xF5, 0x61, 0xB3, 0x61, + 0x13, 0x12, 0x33, 0x62, 0x43, 0x7E, 0xF3, 0x62, + 0x43, 0x7E, 0x06, 0x7F, 0x9B, 0x19, 0x26, 0x46, + 0x33, 0x63, 0x06, 0x7F, 0x43, 0x7E, 0x76, 0x00, + 0x9B, 0x19, 0x26, 0x46, 0xB5, 0x63, 0xF5, 0x63, + 0x35, 0x64, 0x73, 0x63, 0x65, 0x64, 0x38, 0x4B, + 0xA5, 0x64, 0x3F, 0x33, 0x65, 0x62, 0xA3, 0x62, + 0x36, 0x4B, 0xDB, 0x1C, 0xE3, 0x64, 0x36, 0x4B, + 0x23, 0x65, 0xC6, 0x7C, 0x0C, 0x3E, 0xF7, 0xB2, + 0x05, 0x26, 0x76, 0x02, 0xBF, 0x19, 0x67, 0x65, + 0x87, 0x7D, 0x7F, 0x08, 0x3F, 0x02, 0x21, 0x37, + 0xA7, 0x65, 0xE5, 0x65, 0xC4, 0x7C, 0x80, 0x7C, + 0x24, 0x04, 0x00, 0x02, 0x04, 0x43, 0x0C, 0x34, + 0x27, 0x48, 0x0C, 0x60, 0x00, 0x68, 0x40, 0x30, + 0xC4, 0x7C, 0x87, 0x7C, 0x24, 0x04, 0x3F, 0x02, + 0x3C, 0x43, 0x0C, 0x34, 0x4C, 0x60, 0xC4, 0x7D, + 0x64, 0x1E, 0xE4, 0x05, 0xE4, 0x0D, 0x8C, 0x60, + 0xC4, 0x7D, 0x0D, 0x61, 0x64, 0x08, 0x64, 0x1E, + 0xE4, 0x05, 0xE4, 0x0D, 0xCC, 0x60, 0x4A, 0x61, + 0x01, 0x22, 0xCD, 0x61, 0x8A, 0x61, 0x12, 0x02, + 0x0A, 0x62, 0x42, 0x7E, 0xCA, 0x62, 0x42, 0x7E, + 0x44, 0x7F, 0x12, 0x19, 0x0A, 0x63, 0x44, 0x7F, + 0x42, 0x7E, 0x64, 0x00, 0x12, 0x19, 0x8D, 0x63, + 0x4A, 0x63, 0x42, 0x7E, 0xBC, 0x32, 0xCA, 0x63, + 0x42, 0x7E, 0x44, 0x7F, 0x12, 0x19, 0xBC, 0x32, + 0x0A, 0x64, 0x42, 0x7F, 0x44, 0x7E, 0x52, 0x00, + 0xBC, 0x32, 0xA2, 0x18, 0x8D, 0x64, 0x4A, 0x64, + 0x01, 0x22, 0x92, 0x04, 0x4A, 0x62, 0x0C, 0x4A, + 0x3F, 0x32, 0x8A, 0x62, 0x0B, 0x4A, 0x0B, 0x65, + 0xD2, 0x1C, 0xCA, 0x64, 0xC2, 0x7C, 0x0C, 0x3A, + 0xD2, 0xB2, 0x92, 0x19, 0x4A, 0x65, 0xC0, 0x7D, + 0xCD, 0x65, 0x40, 0x08, 0x00, 0x02, 0x21, 0x30, + 0x88, 0x65, 0xF0, 0xBD, 0xD0, 0x05, 0x00, 0x20, + 0x98, 0x01, 0x00, 0x20, 0x03, 0x00, 0x03, 0x00, + 0x00, 0x3F, 0x3F, 0x3F, 0x00, 0x30, 0xBC, 0x00, + 0x00, 0x26, 0x31, 0x00, 0x01, 0x00, 0x01, 0x00, + 0x10, 0xB5, 0x00, 0xF0, 0x2F, 0xFA, 0x00, 0xF0, + 0x5D, 0xF9, 0x01, 0x20, 0x80, 0x07, 0x41, 0x68, + 0x04, 0x22, 0x11, 0x43, 0x41, 0x60, 0x00, 0x20, + 0xFF, 0xF7, 0xAC, 0xFC, 0x09, 0x48, 0x01, 0x78, + 0x09, 0x48, 0x00, 0x29, 0x04, 0xD0, 0x01, 0x68, + 0xFF, 0x22, 0x01, 0x32, 0x11, 0x43, 0x01, 0x60, + 0x01, 0x68, 0x01, 0x22, 0x92, 0x02, 0x91, 0x43, + 0x01, 0x60, 0x04, 0x49, 0x02, 0x20, 0x08, 0x72, + 0x10, 0xBD, 0x00, 0x00, 0x31, 0x00, 0x00, 0x20, + 0x00, 0x10, 0x00, 0x50, 0x20, 0x00, 0x00, 0x20, + 0x10, 0xB5, 0x01, 0x20, 0x80, 0x07, 0x41, 0x68, + 0x04, 0x22, 0x11, 0x43, 0x41, 0x60, 0x09, 0x48, + 0x07, 0x49, 0x41, 0x60, 0x08, 0x49, 0x81, 0x60, + 0xFF, 0xF7, 0x52, 0xFE, 0x80, 0x21, 0x07, 0x48, + 0xFD, 0xF7, 0x9B, 0xFF, 0x80, 0x21, 0x06, 0x48, + 0xFD, 0xF7, 0x97, 0xFF, 0x10, 0xBD, 0x00, 0x00, + 0x1F, 0x1F, 0x5F, 0x1F, 0x00, 0x10, 0x00, 0x50, + 0x1F, 0x1F, 0x1F, 0x1F, 0xDC, 0x08, 0x00, 0x20, + 0x9C, 0x01, 0x00, 0x20, 0x10, 0xB5, 0x0F, 0x49, + 0x0A, 0x78, 0x0F, 0x49, 0x00, 0x2A, 0x09, 0x78, + 0x04, 0xD0, 0x01, 0x2A, 0x07, 0xD0, 0x02, 0x2A, + 0x12, 0xD1, 0x09, 0xE0, 0xC9, 0x07, 0x0F, 0xD0, + 0x60, 0x22, 0x0A, 0x49, 0x08, 0xE0, 0x89, 0x07, + 0x0A, 0xD5, 0x60, 0x22, 0x08, 0x49, 0x03, 0xE0, + 0x49, 0x07, 0x05, 0xD5, 0x60, 0x22, 0x07, 0x49, + 0xFD, 0xF7, 0x56, 0xFF, 0x01, 0x20, 0x10, 0xBD, + 0x00, 0x20, 0x10, 0xBD, 0x92, 0x01, 0x00, 0x20, + 0x38, 0x00, 0x00, 0x20, 0x1C, 0x02, 0x00, 0x20, + 0x10, 0x05, 0x00, 0x20, 0x70, 0x05, 0x00, 0x20, + 0x10, 0xB5, 0x13, 0x49, 0x13, 0x4B, 0x09, 0x68, + 0x13, 0x4A, 0x40, 0x31, 0x00, 0x28, 0x0F, 0xD0, + 0x01, 0x28, 0x12, 0xD0, 0x02, 0x28, 0x15, 0xD0, + 0x03, 0x28, 0x08, 0xD1, 0x48, 0x7E, 0x84, 0x1E, + 0x1C, 0x80, 0x49, 0x7F, 0x4B, 0x00, 0xC9, 0x18, + 0x89, 0x1C, 0x40, 0x18, 0x10, 0x80, 0x10, 0xBD, + 0x08, 0x7E, 0x84, 0x1E, 0x1C, 0x80, 0x89, 0x7E, + 0xF4, 0xE7, 0x08, 0x7E, 0x84, 0x1E, 0x1C, 0x80, + 0xC9, 0x7E, 0xEF, 0xE7, 0x48, 0x7E, 0x84, 0x1E, + 0x1C, 0x80, 0x09, 0x7F, 0xEA, 0xE7, 0x00, 0x00, + 0x98, 0x01, 0x00, 0x20, 0x3A, 0x00, 0x00, 0x20, + 0x3C, 0x00, 0x00, 0x20, 0xF8, 0xB5, 0x05, 0x46, + 0x20, 0x48, 0x00, 0x21, 0x01, 0x60, 0x41, 0x60, + 0x1F, 0x49, 0x01, 0x20, 0x08, 0x70, 0x1F, 0x48, + 0x1F, 0x4C, 0x00, 0x78, 0x00, 0x28, 0x06, 0xD0, + 0x2D, 0x26, 0x09, 0x27, 0x01, 0x28, 0x0D, 0xD0, + 0x02, 0x28, 0x29, 0xD1, 0x17, 0xE0, 0x1B, 0xA0, + 0x00, 0xF0, 0x5B, 0xFE, 0x5A, 0x20, 0x00, 0x2D, + 0x20, 0x70, 0x01, 0xD0, 0x00, 0x20, 0x0B, 0xE0, + 0x01, 0x20, 0x09, 0xE0, 0x18, 0xA0, 0x00, 0xF0, + 0x50, 0xFE, 0x00, 0x2D, 0x02, 0xD0, 0x26, 0x70, + 0x02, 0x20, 0x01, 0xE0, 0x27, 0x70, 0x03, 0x20, + 0x00, 0xF0, 0x0C, 0xF9, 0x10, 0xE0, 0x14, 0xA0, + 0x00, 0xF0, 0x43, 0xFE, 0x00, 0x2D, 0x02, 0xD0, + 0x26, 0x70, 0x02, 0x20, 0x01, 0xE0, 0x27, 0x70, + 0x03, 0x20, 0x00, 0xF0, 0xFF, 0xF8, 0x11, 0x48, + 0x81, 0x6A, 0x11, 0x4A, 0x11, 0x40, 0x81, 0x62, + 0x05, 0x20, 0x10, 0x49, 0x00, 0x02, 0x08, 0x60, + 0xF8, 0xBD, 0x00, 0x00, 0x20, 0x00, 0x00, 0x20, + 0x31, 0x00, 0x00, 0x20, 0x92, 0x01, 0x00, 0x20, + 0x94, 0x01, 0x00, 0x20, 0x41, 0x63, 0x74, 0x69, + 0x76, 0x65, 0x0D, 0x0A, 0x00, 0x00, 0x00, 0x00, + 0x49, 0x64, 0x6C, 0x65, 0x0D, 0x0A, 0x00, 0x00, + 0x47, 0x65, 0x73, 0x74, 0x75, 0x72, 0x65, 0x0D, + 0x0A, 0x00, 0x00, 0x00, 0xC0, 0x11, 0x00, 0x50, + 0xFF, 0xFF, 0x00, 0xF8, 0x00, 0x10, 0x00, 0x50, + 0x70, 0xB5, 0x19, 0x4D, 0x19, 0x4C, 0x28, 0x70, + 0x02, 0x46, 0x21, 0x78, 0x18, 0xA0, 0x00, 0xF0, + 0x08, 0xFE, 0x1B, 0x49, 0x01, 0x20, 0x08, 0x70, + 0x00, 0xF0, 0x20, 0xF9, 0x00, 0xF0, 0x4E, 0xF8, + 0x01, 0x20, 0xFD, 0xF7, 0x4D, 0xFF, 0xFF, 0xF7, + 0xF5, 0xFB, 0x01, 0x20, 0xFF, 0xF7, 0x86, 0xFF, + 0x00, 0xF0, 0x42, 0xFC, 0xFE, 0xF7, 0x64, 0xFE, + 0x28, 0x78, 0x20, 0x70, 0x05, 0x28, 0x0F, 0xD0, + 0xFF, 0xF7, 0xAA, 0xFC, 0x00, 0x28, 0x0C, 0xD0, + 0x0E, 0x48, 0x00, 0x7A, 0x00, 0x28, 0x03, 0xD1, + 0x00, 0x21, 0x02, 0x20, 0x00, 0xF0, 0x48, 0xF8, + 0x0B, 0x48, 0x00, 0x78, 0xC0, 0x07, 0x01, 0xD0, + 0x00, 0x20, 0x70, 0xBD, 0x01, 0x20, 0x70, 0xBD, + 0x92, 0x01, 0x00, 0x20, 0x0D, 0x08, 0x00, 0x20, + 0x53, 0x4D, 0x3D, 0x5B, 0x25, 0x64, 0x3A, 0x25, + 0x64, 0x5D, 0x0D, 0x0A, 0x00, 0x00, 0x00, 0x00, + 0x91, 0x01, 0x00, 0x20, 0x20, 0x00, 0x00, 0x20, + 0x30, 0x00, 0x00, 0x20, 0x08, 0x49, 0x02, 0x20, + 0x08, 0x72, 0x08, 0x48, 0x01, 0x78, 0x08, 0x48, + 0x00, 0x29, 0x01, 0x68, 0x04, 0xD0, 0x01, 0x22, + 0x92, 0x02, 0x91, 0x43, 0x01, 0x60, 0x70, 0x47, + 0x01, 0x22, 0x11, 0x43, 0xFA, 0xE7, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x20, 0x31, 0x00, 0x00, 0x20, + 0x00, 0x10, 0x00, 0x50, 0x10, 0xB5, 0x08, 0x48, + 0x01, 0x68, 0x01, 0x22, 0x92, 0x02, 0x11, 0x43, + 0x01, 0x60, 0x00, 0x68, 0x05, 0x4C, 0xC0, 0x07, + 0x03, 0xD0, 0x02, 0x20, 0x20, 0x72, 0x00, 0xF0, + 0xC1, 0xF8, 0x00, 0x20, 0x20, 0x72, 0x10, 0xBD, + 0x00, 0x10, 0x00, 0x50, 0x20, 0x00, 0x00, 0x20, + 0x70, 0xB5, 0x05, 0x00, 0x0E, 0x46, 0x16, 0xD0, + 0xFF, 0xF7, 0x82, 0xFE, 0x00, 0x24, 0x6D, 0x1E, + 0x07, 0xE0, 0x00, 0xF0, 0xAF, 0xF8, 0xFF, 0xF7, + 0x57, 0xFB, 0xFF, 0xF7, 0xC3, 0xFF, 0x64, 0x1C, + 0xE4, 0xB2, 0xAC, 0x42, 0xF5, 0xDB, 0x00, 0xF0, + 0xA5, 0xF8, 0xFF, 0xF7, 0x4D, 0xFB, 0x00, 0x2E, + 0x02, 0xD0, 0xFF, 0xF7, 0xCF, 0xFF, 0x70, 0xBD, + 0xFF, 0xF7, 0xB4, 0xFF, 0x70, 0xBD, 0x00, 0x00, + 0x30, 0xB5, 0x01, 0x24, 0x1C, 0x4A, 0xA4, 0x07, + 0x23, 0x13, 0x1B, 0x49, 0x40, 0x32, 0x00, 0x28, + 0x21, 0xD0, 0x01, 0x28, 0x1F, 0xD0, 0x02, 0x28, + 0x01, 0xD0, 0x03, 0x28, 0x1A, 0xD1, 0x25, 0x68, + 0x1D, 0x43, 0x25, 0x60, 0x62, 0x23, 0x93, 0x61, + 0xD3, 0x61, 0x14, 0x4B, 0xCB, 0x63, 0x14, 0x49, + 0x09, 0x68, 0x40, 0x31, 0x02, 0x28, 0x48, 0x7E, + 0x17, 0xD0, 0x40, 0x1E, 0x40, 0x05, 0x40, 0x0D, + 0x10, 0x60, 0x4B, 0x7E, 0x48, 0x7F, 0x41, 0x00, + 0x40, 0x18, 0x80, 0x1C, 0x18, 0x18, 0x40, 0x05, + 0x40, 0x0D, 0x90, 0x60, 0x30, 0xBD, 0x20, 0x68, + 0x98, 0x43, 0x20, 0x60, 0x01, 0x20, 0x90, 0x61, + 0xD0, 0x61, 0x00, 0x20, 0xC8, 0x63, 0x10, 0x60, + 0xF3, 0xE7, 0x40, 0x1E, 0x40, 0x05, 0x40, 0x0D, + 0x10, 0x60, 0x4B, 0x7E, 0x08, 0x7F, 0xE6, 0xE7, + 0x80, 0x11, 0x00, 0x50, 0x01, 0x00, 0x01, 0x00, + 0x98, 0x01, 0x00, 0x20, 0x70, 0xB5, 0x04, 0x46, + 0x81, 0x00, 0x25, 0x48, 0x41, 0x58, 0x25, 0x48, + 0x0A, 0x68, 0x42, 0x60, 0x4A, 0x68, 0x82, 0x60, + 0x8A, 0x68, 0x02, 0x61, 0xCA, 0x68, 0x42, 0x61, + 0x21, 0x4D, 0x08, 0x69, 0x28, 0x62, 0x1F, 0x4A, + 0x48, 0x69, 0xC0, 0x32, 0x90, 0x61, 0x88, 0x69, + 0xD0, 0x61, 0x48, 0x6A, 0x10, 0x62, 0x88, 0x6A, + 0x50, 0x62, 0x1A, 0x48, 0xCB, 0x69, 0x40, 0x38, + 0xC3, 0x60, 0x0B, 0x6A, 0x03, 0x61, 0xCB, 0x6A, + 0x93, 0x62, 0x0B, 0x6B, 0xD3, 0x63, 0x16, 0x4B, + 0x4E, 0x6B, 0x80, 0x3B, 0x1E, 0x61, 0x8E, 0x6B, + 0x5E, 0x62, 0xCE, 0x6B, 0xD6, 0x62, 0x0A, 0x6C, + 0x1A, 0x60, 0x4A, 0x6C, 0x5A, 0x61, 0x8A, 0x6C, + 0x9A, 0x62, 0xCA, 0x6C, 0xAA, 0x62, 0x0D, 0x4B, + 0x0A, 0x6D, 0x40, 0x33, 0x5A, 0x63, 0x0B, 0x4B, + 0x4A, 0x6D, 0x80, 0x33, 0x9A, 0x60, 0x0B, 0x4A, + 0x8B, 0x6D, 0x13, 0x60, 0xC9, 0x6D, 0x51, 0x60, + 0xC2, 0x68, 0x09, 0x49, 0x0A, 0x60, 0x00, 0x69, + 0x48, 0x60, 0x20, 0x46, 0xFF, 0xF7, 0x50, 0xFE, + 0x20, 0x46, 0xFF, 0xF7, 0x71, 0xFF, 0x70, 0xBD, + 0xD0, 0x05, 0x00, 0x20, 0x40, 0x10, 0x00, 0x50, + 0xC0, 0x11, 0x00, 0x50, 0x00, 0x19, 0x00, 0x50, + 0x20, 0x00, 0x00, 0x20, 0x70, 0xB5, 0x05, 0x20, + 0x40, 0x04, 0x00, 0x23, 0x0C, 0x4A, 0x0D, 0x49, + 0x0D, 0x4C, 0x05, 0xE0, 0x0D, 0x78, 0xED, 0x07, + 0x01, 0xD0, 0x13, 0x72, 0x09, 0xE0, 0x40, 0x1E, + 0x15, 0x7A, 0x02, 0x2D, 0x02, 0xD0, 0x25, 0x68, + 0xED, 0x07, 0x02, 0xD0, 0x00, 0x28, 0xF1, 0xD1, + 0x01, 0xE0, 0x00, 0x28, 0x03, 0xD1, 0x13, 0x72, + 0x04, 0xA0, 0x00, 0xF0, 0xC6, 0xFC, 0x70, 0xBD, + 0x20, 0x00, 0x00, 0x20, 0x30, 0x00, 0x00, 0x20, + 0x00, 0x10, 0x00, 0x50, 0x57, 0x53, 0x46, 0x20, + 0x54, 0x4F, 0x0D, 0x0A, 0x00, 0x00, 0x00, 0x00, + 0xF8, 0xB5, 0x1D, 0x48, 0x01, 0x25, 0x01, 0x68, + 0x00, 0x24, 0x02, 0x22, 0x91, 0x43, 0x1B, 0x4F, + 0x1B, 0x4E, 0x01, 0x60, 0x1B, 0x48, 0x00, 0x68, + 0x00, 0x19, 0xC0, 0x7E, 0x41, 0x28, 0x14, 0xD0, + 0x19, 0x48, 0x61, 0x00, 0x42, 0x5E, 0x04, 0x20, + 0x38, 0x5E, 0x82, 0x42, 0x09, 0xDA, 0x21, 0x46, + 0x16, 0xA0, 0x00, 0xF0, 0x9E, 0xFC, 0x30, 0x5D, + 0x02, 0x21, 0x08, 0x43, 0x30, 0x55, 0x00, 0x25, + 0x03, 0xE0, 0x30, 0x5D, 0xFD, 0x21, 0x08, 0x40, + 0x30, 0x55, 0x64, 0x1C, 0xE4, 0xB2, 0x30, 0x2C, + 0xE0, 0xD3, 0x15, 0x49, 0x01, 0x2D, 0x79, 0x5E, + 0x09, 0xD0, 0x14, 0xA0, 0x00, 0xF0, 0x89, 0xFC, + 0x05, 0x49, 0x02, 0x22, 0x08, 0x68, 0x10, 0x43, + 0x08, 0x60, 0x28, 0x46, 0xF8, 0xBD, 0x16, 0xA0, + 0x00, 0xF0, 0x7F, 0xFC, 0xF9, 0xE7, 0x00, 0x00, + 0x0C, 0x05, 0x00, 0x20, 0x84, 0x06, 0x00, 0x20, + 0xD8, 0x04, 0x00, 0x20, 0x98, 0x01, 0x00, 0x20, + 0xE4, 0x02, 0x00, 0x20, 0x53, 0x68, 0x6F, 0x72, + 0x74, 0x20, 0x4E, 0x47, 0x20, 0x44, 0x61, 0x74, + 0x61, 0x5B, 0x25, 0x64, 0x5D, 0x20, 0x3D, 0x20, + 0x25, 0x64, 0x0D, 0x0A, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x53, 0x68, 0x6F, 0x72, + 0x74, 0x20, 0x54, 0x65, 0x73, 0x74, 0x28, 0x54, + 0x48, 0x44, 0x3A, 0x25, 0x64, 0x29, 0x20, 0x5B, + 0x4E, 0x47, 0x5D, 0x0D, 0x0A, 0x00, 0x00, 0x00, + 0x53, 0x68, 0x6F, 0x72, 0x74, 0x20, 0x54, 0x65, + 0x73, 0x74, 0x28, 0x54, 0x48, 0x44, 0x3A, 0x25, + 0x64, 0x29, 0x20, 0x5B, 0x50, 0x61, 0x73, 0x73, + 0x5D, 0x0D, 0x0A, 0x00, 0x70, 0xB5, 0x0F, 0x48, + 0x01, 0x25, 0xC4, 0x7F, 0x0E, 0x48, 0x80, 0x88, + 0x00, 0x07, 0x01, 0xD4, 0x01, 0x20, 0x70, 0xBD, + 0xFE, 0xF7, 0xBE, 0xFB, 0xFE, 0xF7, 0x26, 0xFA, + 0xFF, 0xF7, 0x7E, 0xFF, 0x00, 0x28, 0x0A, 0xD1, + 0x00, 0x25, 0x00, 0x2C, 0x07, 0xD0, 0x64, 0x1E, + 0xE4, 0xB2, 0x21, 0x46, 0x05, 0xA0, 0x00, 0xF0, + 0x2C, 0xFC, 0x01, 0x25, 0xEE, 0xE7, 0x28, 0x46, + 0x70, 0xBD, 0x00, 0x00, 0xA8, 0x06, 0x00, 0x20, + 0x74, 0x06, 0x00, 0x20, 0x53, 0x68, 0x6F, 0x72, + 0x74, 0x20, 0x52, 0x65, 0x74, 0x72, 0x79, 0x20, + 0x28, 0x25, 0x64, 0x29, 0x0D, 0x0A, 0x00, 0x00, + 0xF8, 0xB5, 0x1B, 0x4E, 0x05, 0x46, 0x0C, 0x46, + 0x80, 0x21, 0x30, 0x46, 0xFD, 0xF7, 0xD1, 0xFC, + 0x00, 0x2C, 0x04, 0xD0, 0x31, 0x46, 0x28, 0x46, + 0x00, 0xF0, 0x42, 0xFA, 0x04, 0xE0, 0x80, 0x22, + 0x29, 0x46, 0x30, 0x46, 0xFD, 0xF7, 0xAC, 0xFC, + 0x29, 0x46, 0x12, 0xA0, 0x00, 0xF0, 0x01, 0xFC, + 0x00, 0x25, 0x14, 0x4F, 0x13, 0xE0, 0x00, 0x24, + 0x08, 0xE0, 0x68, 0x43, 0x00, 0x19, 0x40, 0x00, + 0x31, 0x5E, 0x11, 0xA0, 0x00, 0xF0, 0xF5, 0xFB, + 0x64, 0x1C, 0xE4, 0xB2, 0x38, 0x68, 0x00, 0x7E, + 0xA0, 0x42, 0xF2, 0xD8, 0x0E, 0xA0, 0x00, 0xF0, + 0xEC, 0xFB, 0x6D, 0x1C, 0xED, 0xB2, 0x38, 0x68, + 0x40, 0x7E, 0xA8, 0x42, 0xE7, 0xD8, 0x0A, 0xA0, + 0x00, 0xF0, 0xE3, 0xFB, 0xF8, 0xBD, 0x00, 0x00, + 0x4C, 0x00, 0x00, 0x20, 0x49, 0x6D, 0x61, 0x67, + 0x65, 0x3A, 0x30, 0x78, 0x25, 0x78, 0x0D, 0x0A, + 0x00, 0x00, 0x00, 0x00, 0x98, 0x01, 0x00, 0x20, + 0x25, 0x36, 0x64, 0x2C, 0x00, 0x00, 0x00, 0x00, + 0x0D, 0x0A, 0x00, 0x00, 0x08, 0x49, 0x8A, 0x78, + 0x52, 0x1E, 0x8A, 0x70, 0x4B, 0x78, 0x0A, 0x1D, + 0xD2, 0x5C, 0x02, 0x70, 0x48, 0x78, 0x40, 0x1C, + 0x48, 0x70, 0x48, 0x78, 0x10, 0x28, 0x01, 0xD1, + 0x00, 0x20, 0x48, 0x70, 0x70, 0x47, 0x00, 0x00, + 0xE0, 0x05, 0x00, 0x20, 0xF8, 0xB5, 0x01, 0x27, + 0x05, 0x46, 0xBF, 0x07, 0x38, 0x68, 0x08, 0x21, + 0x08, 0x43, 0x38, 0x60, 0x01, 0x23, 0x15, 0x48, + 0x80, 0x22, 0x02, 0x60, 0x14, 0x48, 0x00, 0x21, + 0x81, 0x70, 0x01, 0x70, 0x41, 0x70, 0xC3, 0x70, + 0x12, 0x4B, 0x98, 0x68, 0x02, 0x21, 0x88, 0x43, + 0x98, 0x60, 0xF8, 0x68, 0x10, 0x43, 0xF8, 0x60, + 0x0F, 0x4C, 0x61, 0x61, 0x0F, 0x48, 0x00, 0x68, + 0xE9, 0x00, 0x46, 0x06, 0x0E, 0x48, 0xFD, 0xF7, + 0x15, 0xFC, 0xE0, 0x60, 0x30, 0x20, 0xA0, 0x60, + 0x06, 0x49, 0x80, 0x20, 0x80, 0x39, 0x08, 0x60, + 0x08, 0x20, 0x78, 0x60, 0xE0, 0x68, 0x68, 0x43, + 0xC1, 0x00, 0x08, 0xA0, 0x00, 0xF0, 0x89, 0xFB, + 0xF8, 0xBD, 0x00, 0x00, 0x80, 0xE1, 0x00, 0xE0, + 0xE0, 0x05, 0x00, 0x20, 0x40, 0x09, 0x00, 0x50, + 0x00, 0x02, 0x00, 0x50, 0x00, 0x11, 0x00, 0x50, + 0x00, 0x36, 0x6E, 0x01, 0x55, 0x41, 0x52, 0x54, + 0x28, 0x25, 0x64, 0x29, 0x21, 0x0D, 0x0A, 0x00, + 0x70, 0xB5, 0x14, 0x4A, 0x91, 0x78, 0x14, 0x4C, + 0x0E, 0x29, 0x02, 0xD3, 0x61, 0x68, 0x49, 0x07, + 0xFC, 0xD5, 0x61, 0x69, 0x02, 0x25, 0xA9, 0x43, + 0x61, 0x61, 0x91, 0x78, 0x00, 0x26, 0x10, 0x29, + 0x0D, 0xD2, 0x0C, 0x49, 0x13, 0x78, 0x09, 0x1D, + 0xC8, 0x54, 0x10, 0x78, 0x40, 0x1C, 0x10, 0x70, + 0x10, 0x78, 0x10, 0x28, 0x00, 0xD1, 0x16, 0x70, + 0x90, 0x78, 0x40, 0x1C, 0x90, 0x70, 0xD0, 0x78, + 0x00, 0x28, 0x03, 0xD0, 0xD6, 0x70, 0x20, 0x46, + 0xFF, 0xF7, 0x80, 0xFF, 0x60, 0x69, 0x28, 0x43, + 0x60, 0x61, 0x70, 0xBD, 0xE0, 0x05, 0x00, 0x20, + 0x00, 0x02, 0x00, 0x50, 0xFE, 0xB5, 0x2F, 0x48, + 0x10, 0x26, 0x12, 0x27, 0x86, 0x5F, 0xC7, 0x5F, + 0x2D, 0x48, 0x01, 0x90, 0x2D, 0x48, 0x01, 0x25, + 0x01, 0x68, 0x2A, 0x04, 0x91, 0x43, 0x00, 0x24, + 0x01, 0x60, 0x2B, 0x48, 0x00, 0x68, 0x00, 0x19, + 0xC0, 0x7E, 0x41, 0x28, 0x26, 0xD0, 0x01, 0x99, + 0x60, 0x00, 0x09, 0x5A, 0x4A, 0x05, 0x52, 0x0D, + 0x26, 0x49, 0x53, 0x05, 0x0A, 0x52, 0x03, 0xD5, + 0x89, 0x23, 0x9B, 0x00, 0xD2, 0x1A, 0x0A, 0x52, + 0x23, 0x4A, 0x0B, 0x5A, 0x12, 0x5A, 0xD0, 0x1A, + 0x00, 0xB2, 0xB0, 0x42, 0x01, 0xDC, 0xB8, 0x42, + 0x0B, 0xDA, 0x00, 0x90, 0x21, 0x46, 0x1F, 0xA0, + 0x00, 0xF0, 0x17, 0xFB, 0x23, 0x48, 0x10, 0x22, + 0x01, 0x5D, 0x11, 0x43, 0x01, 0x55, 0x00, 0x25, + 0x04, 0xE0, 0x20, 0x48, 0xEF, 0x22, 0x01, 0x5D, + 0x11, 0x40, 0x01, 0x55, 0x64, 0x1C, 0xE4, 0xB2, + 0x30, 0x2C, 0xCE, 0xD3, 0x01, 0x2D, 0x18, 0xD0, + 0x10, 0x4A, 0x01, 0x20, 0x11, 0x68, 0x00, 0x04, + 0x01, 0x43, 0x19, 0xA0, 0x11, 0x60, 0x00, 0xF0, + 0xFC, 0xFA, 0x1C, 0xA0, 0x00, 0xF0, 0xF9, 0xFA, + 0x01, 0x21, 0x0C, 0x48, 0xFF, 0xF7, 0xDC, 0xFE, + 0x1A, 0xA0, 0x00, 0xF0, 0xF2, 0xFA, 0x01, 0x21, + 0x09, 0x48, 0xFF, 0xF7, 0xD5, 0xFE, 0x28, 0x46, + 0xFE, 0xBD, 0x19, 0xA0, 0x00, 0xF0, 0xE9, 0xFA, + 0xEB, 0xE7, 0x00, 0x00, 0x84, 0x06, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x50, 0x0C, 0x05, 0x00, 0x20, + 0x98, 0x01, 0x00, 0x20, 0x10, 0x04, 0x00, 0x20, + 0xEC, 0x06, 0x00, 0x20, 0x55, 0x43, 0x5B, 0x25, + 0x64, 0x5D, 0x20, 0x4E, 0x47, 0x21, 0x20, 0x25, + 0x64, 0x2D, 0x25, 0x64, 0x3D, 0x25, 0x64, 0x0D, + 0x0A, 0x00, 0x00, 0x00, 0xD8, 0x04, 0x00, 0x20, + 0x55, 0x43, 0x20, 0x54, 0x65, 0x73, 0x74, 0x20, + 0x5B, 0x4E, 0x47, 0x5D, 0x3A, 0x30, 0x78, 0x25, + 0x78, 0x0D, 0x0A, 0x00, 0x55, 0x43, 0x0D, 0x0A, + 0x00, 0x00, 0x00, 0x00, 0x47, 0x6F, 0x6C, 0x64, + 0x65, 0x6E, 0x20, 0x55, 0x43, 0x0D, 0x0A, 0x00, + 0x55, 0x43, 0x20, 0x54, 0x65, 0x73, 0x74, 0x20, + 0x5B, 0x50, 0x61, 0x73, 0x73, 0x5D, 0x0D, 0x0A, + 0x00, 0x00, 0x00, 0x00, 0x70, 0xB5, 0x0E, 0x48, + 0x01, 0x25, 0x44, 0x79, 0x0D, 0x48, 0x80, 0x88, + 0xC0, 0x06, 0x01, 0xD4, 0x01, 0x20, 0x70, 0xBD, + 0xFF, 0xF7, 0x5C, 0xFF, 0x00, 0x28, 0x0C, 0xD1, + 0x00, 0x25, 0x00, 0x2C, 0x09, 0xD0, 0x64, 0x1E, + 0xE4, 0xB2, 0xFE, 0xF7, 0x0B, 0xF9, 0x21, 0x46, + 0x05, 0xA0, 0x00, 0xF0, 0x96, 0xFA, 0x01, 0x25, + 0xEE, 0xE7, 0x28, 0x46, 0x70, 0xBD, 0x00, 0x00, + 0xC8, 0x06, 0x00, 0x20, 0x74, 0x06, 0x00, 0x20, + 0x55, 0x43, 0x20, 0x4E, 0x47, 0x20, 0x52, 0x65, + 0x74, 0x72, 0x79, 0x20, 0x28, 0x25, 0x64, 0x29, + 0x0D, 0x0A, 0x00, 0x00, 0xF8, 0xB5, 0x3D, 0x4F, + 0x01, 0x24, 0x38, 0x7B, 0x3C, 0x49, 0x0A, 0x78, + 0x3C, 0x4E, 0x3D, 0x4D, 0x90, 0x42, 0x05, 0xD1, + 0x28, 0x78, 0x81, 0x28, 0x02, 0xD0, 0x30, 0x78, + 0xC0, 0x07, 0x63, 0xD0, 0x00, 0x20, 0x30, 0x70, + 0x35, 0x48, 0x00, 0x78, 0x07, 0x28, 0x0C, 0xD3, + 0x36, 0x48, 0x00, 0x68, 0x40, 0x05, 0x40, 0x0F, + 0x38, 0x73, 0x01, 0x20, 0xFD, 0xF7, 0xDC, 0xFC, + 0x00, 0x20, 0x30, 0x70, 0x38, 0x7B, 0x02, 0x28, + 0x13, 0xD0, 0x38, 0x7B, 0x2C, 0x4F, 0x38, 0x70, + 0x81, 0x20, 0x28, 0x70, 0x2E, 0x4A, 0x01, 0x21, + 0x10, 0x88, 0x09, 0x03, 0x08, 0x43, 0x10, 0x80, + 0x38, 0x78, 0x03, 0x00, 0xFD, 0xF7, 0x76, 0xFB, + 0x07, 0x25, 0x27, 0x37, 0x3D, 0x0B, 0x18, 0x0B, + 0x3D, 0x00, 0x83, 0x20, 0x28, 0x70, 0x05, 0x20, + 0xFF, 0xF7, 0x36, 0xFC, 0xF8, 0xBD, 0x25, 0xA0, + 0x00, 0xF0, 0x43, 0xFA, 0x01, 0x20, 0xFE, 0xF7, + 0x1B, 0xFE, 0x00, 0x20, 0xFF, 0xF7, 0x2C, 0xFC, + 0x00, 0x28, 0x1E, 0xD0, 0x01, 0x20, 0x1A, 0xE0, + 0x20, 0xA0, 0x00, 0xF0, 0x36, 0xFA, 0x01, 0x20, + 0xFE, 0xF7, 0x0E, 0xFE, 0x01, 0x20, 0xFF, 0xF7, + 0x1F, 0xFC, 0x00, 0x28, 0x11, 0xD0, 0x04, 0x20, + 0x0D, 0xE0, 0x1C, 0xA0, 0x00, 0xE0, 0x1D, 0xA0, + 0x00, 0xF0, 0x27, 0xFA, 0x00, 0x20, 0xFE, 0xF7, + 0xFF, 0xFD, 0x02, 0x20, 0xFF, 0xF7, 0x10, 0xFC, + 0x00, 0x28, 0x02, 0xD0, 0x02, 0x20, 0x28, 0x70, + 0x07, 0xE0, 0x00, 0x24, 0x05, 0xE0, 0x83, 0x20, + 0x28, 0x70, 0x05, 0x20, 0xFF, 0xF7, 0x04, 0xFC, + 0x04, 0x46, 0x2A, 0x78, 0x39, 0x78, 0x13, 0xA0, + 0x00, 0xF0, 0x0F, 0xFA, 0x30, 0x78, 0xC0, 0x07, + 0x02, 0xD0, 0x81, 0x20, 0x28, 0x70, 0x00, 0x24, + 0x20, 0x46, 0xF8, 0xBD, 0x20, 0x00, 0x00, 0x20, + 0x39, 0x01, 0x00, 0x20, 0x30, 0x00, 0x00, 0x20, + 0x20, 0x08, 0x00, 0x20, 0x00, 0x11, 0x00, 0x50, + 0x4A, 0x01, 0x00, 0x20, 0x44, 0x41, 0x0D, 0x0A, + 0x00, 0x00, 0x00, 0x00, 0x44, 0x49, 0x0D, 0x0A, + 0x00, 0x00, 0x00, 0x00, 0x44, 0x53, 0x54, 0x42, + 0x0D, 0x0A, 0x00, 0x00, 0x44, 0x47, 0x0D, 0x0A, + 0x00, 0x00, 0x00, 0x00, 0x44, 0x53, 0x50, 0x3D, + 0x25, 0x64, 0x2C, 0x50, 0x57, 0x52, 0x3D, 0x25, + 0x64, 0x20, 0x0D, 0x0A, 0x00, 0x00, 0x00, 0x00, + 0x70, 0xB5, 0x05, 0x46, 0x01, 0x24, 0xFE, 0xF7, + 0x83, 0xFA, 0x00, 0x2D, 0x09, 0xD0, 0xF0, 0x20, + 0xFD, 0xF7, 0xCA, 0xFA, 0xFE, 0xF7, 0x4A, 0xFB, + 0xFE, 0xF7, 0xE0, 0xFA, 0x00, 0x20, 0xFE, 0xF7, + 0xAB, 0xFD, 0xFE, 0xF7, 0xDF, 0xFD, 0xFF, 0xF7, + 0xDF, 0xFA, 0x20, 0x46, 0x70, 0xBD, 0x00, 0x00, + 0x30, 0xB5, 0x08, 0x4A, 0x14, 0x68, 0x1B, 0x34, + 0x00, 0x22, 0xA3, 0x5C, 0x41, 0x2B, 0x03, 0xD0, + 0x55, 0x00, 0x45, 0x5B, 0x5B, 0x00, 0xCD, 0x52, + 0x52, 0x1C, 0xD2, 0xB2, 0x30, 0x2A, 0xF4, 0xD3, + 0x30, 0xBD, 0x00, 0x00, 0x98, 0x01, 0x00, 0x20, + 0x70, 0xB5, 0x17, 0x4D, 0x16, 0x20, 0x28, 0x70, + 0x68, 0x78, 0x29, 0x46, 0x00, 0x24, 0x14, 0x31, + 0x02, 0x28, 0x0C, 0xD8, 0x00, 0x28, 0x05, 0xD1, + 0xA8, 0x78, 0x00, 0x28, 0x02, 0xD0, 0x08, 0x46, + 0x1C, 0x30, 0x04, 0x70, 0x48, 0x88, 0x00, 0x28, + 0x01, 0xD1, 0x03, 0x20, 0x68, 0x71, 0xAC, 0x70, + 0x2C, 0x81, 0xEC, 0x80, 0xAC, 0x81, 0x6C, 0x81, + 0x4C, 0x80, 0x8C, 0x80, 0x0C, 0x72, 0x09, 0x48, + 0x44, 0x70, 0x84, 0x70, 0x04, 0x70, 0x06, 0x48, + 0xA0, 0x21, 0x30, 0x30, 0xFD, 0xF7, 0x4D, 0xFA, + 0xEC, 0x70, 0x5A, 0x20, 0xE8, 0x81, 0x2C, 0x71, + 0x2C, 0x61, 0x00, 0xF0, 0x11, 0xFA, 0x70, 0xBD, + 0x0C, 0x08, 0x00, 0x20, 0x90, 0x02, 0x00, 0x20, + 0x06, 0x4A, 0x07, 0x4B, 0x00, 0x21, 0x00, 0x20, + 0x40, 0x1C, 0x90, 0x42, 0xFC, 0xDB, 0x49, 0x1C, + 0x99, 0x42, 0xF8, 0xDB, 0x03, 0x48, 0x01, 0x21, + 0x01, 0x60, 0x70, 0x47, 0x10, 0x27, 0x00, 0x00, + 0xB8, 0x0B, 0x00, 0x00, 0x04, 0x00, 0x00, 0x40, + 0x70, 0xB5, 0x7B, 0x24, 0x21, 0x48, 0x24, 0x02, + 0x04, 0x60, 0xF7, 0x20, 0xC0, 0x01, 0x20, 0x4A, + 0xC1, 0x7C, 0x11, 0x70, 0x1F, 0x4A, 0x01, 0x7D, + 0x11, 0x70, 0x1F, 0x4A, 0x41, 0x7D, 0x11, 0x70, + 0x1E, 0x49, 0x80, 0x7D, 0x08, 0x70, 0x60, 0x7C, + 0x21, 0x7C, 0x00, 0x06, 0x00, 0x14, 0x08, 0x43, + 0x1B, 0x49, 0x05, 0x46, 0x08, 0x80, 0xE0, 0x7C, + 0xA1, 0x7C, 0x00, 0x06, 0x00, 0x14, 0x08, 0x43, + 0x18, 0x49, 0x08, 0x80, 0x20, 0x7E, 0x61, 0x7E, + 0x48, 0x43, 0x17, 0x49, 0x08, 0x80, 0x17, 0x48, + 0xFD, 0xF7, 0x0F, 0xFA, 0x10, 0x21, 0xC8, 0x41, + 0x15, 0x49, 0x08, 0x60, 0x04, 0x22, 0x21, 0x1D, + 0x14, 0x48, 0xFD, 0xF7, 0xE1, 0xF9, 0x11, 0x49, + 0x06, 0x22, 0x89, 0x1F, 0x12, 0x48, 0xFD, 0xF7, + 0xDB, 0xF9, 0x60, 0x7D, 0x22, 0x7D, 0x01, 0x02, + 0x11, 0x43, 0x28, 0x46, 0x0A, 0x22, 0x50, 0x43, + 0xFD, 0xF7, 0xBE, 0xF9, 0x0D, 0x49, 0x08, 0x80, + 0x70, 0xBD, 0x00, 0x00, 0x98, 0x01, 0x00, 0x20, + 0x0C, 0x00, 0x00, 0x20, 0x0D, 0x00, 0x00, 0x20, + 0x0E, 0x00, 0x00, 0x20, 0x0F, 0x00, 0x00, 0x20, + 0x1C, 0x00, 0x00, 0x20, 0x1E, 0x00, 0x00, 0x20, + 0x18, 0x00, 0x00, 0x20, 0x10, 0x7B, 0x00, 0x00, + 0xD8, 0x06, 0x00, 0x20, 0xE0, 0x06, 0x00, 0x20, + 0xE4, 0x06, 0x00, 0x20, 0x1A, 0x00, 0x00, 0x20, + 0x00, 0x20, 0x0E, 0x49, 0x08, 0x70, 0x17, 0xE0, + 0x0C, 0x48, 0x00, 0x78, 0x00, 0x28, 0x02, 0xD0, + 0x07, 0x28, 0x0F, 0xD1, 0x0B, 0xE0, 0x00, 0xF0, + 0x13, 0xF8, 0x01, 0x28, 0x03, 0xD1, 0x07, 0x20, + 0x06, 0x49, 0x08, 0x70, 0x02, 0xE0, 0x03, 0x20, + 0x04, 0x49, 0x08, 0x70, 0x03, 0xE0, 0xFD, 0xF7, + 0x63, 0xFA, 0x00, 0xE0, 0x00, 0xBF, 0x00, 0xBF, + 0xE6, 0xE7, 0x00, 0x00, 0xD2, 0x00, 0x00, 0x20, + 0x10, 0xB5, 0x00, 0x24, 0xFD, 0xF7, 0xC4, 0xFC, + 0x00, 0xF0, 0xF2, 0xF8, 0x01, 0x20, 0xFF, 0xF7, + 0x03, 0xFF, 0x04, 0x46, 0x00, 0x2C, 0x04, 0xD1, + 0x05, 0xA0, 0x00, 0xF0, 0xDE, 0xF8, 0x00, 0x20, + 0x10, 0xBD, 0x06, 0x49, 0x06, 0x48, 0x81, 0x70, + 0x09, 0x0A, 0xC1, 0x70, 0x01, 0x20, 0xF7, 0xE7, + 0x49, 0x4E, 0x49, 0x54, 0x20, 0x4E, 0x47, 0x0D, + 0x0A, 0x00, 0x00, 0x00, 0xAA, 0x55, 0x00, 0x00, + 0x88, 0x02, 0x00, 0x20, 0xF7, 0xB5, 0x00, 0x25, + 0x84, 0xB0, 0x0C, 0x46, 0x16, 0x46, 0x61, 0x27, + 0x5C, 0xE0, 0x25, 0x28, 0x54, 0xD1, 0x64, 0x1C, + 0x00, 0x22, 0x20, 0x78, 0x13, 0x46, 0x00, 0x28, + 0x57, 0xD0, 0x25, 0x28, 0x4C, 0xD0, 0x2D, 0x28, + 0x01, 0xD1, 0x64, 0x1C, 0x01, 0x23, 0x02, 0x20, + 0x21, 0x78, 0x30, 0x29, 0x07, 0xD1, 0x64, 0x1C, + 0x03, 0x43, 0xF9, 0xE7, 0x0A, 0x21, 0x4A, 0x43, + 0x30, 0x3A, 0x82, 0x18, 0x64, 0x1C, 0x20, 0x78, + 0x01, 0x46, 0x30, 0x39, 0x09, 0x29, 0xF5, 0xD9, + 0xC1, 0xB2, 0x73, 0x29, 0x0A, 0xD0, 0x64, 0x28, + 0x10, 0xD0, 0x78, 0x28, 0x13, 0xD0, 0x58, 0x28, + 0x19, 0xD0, 0x75, 0x28, 0x1F, 0xD0, 0x63, 0x28, + 0x23, 0xD0, 0x2E, 0xE0, 0x02, 0xCE, 0x00, 0x29, + 0x00, 0xD1, 0x1D, 0xA1, 0x04, 0x98, 0x00, 0xF0, + 0xD3, 0xF8, 0x0A, 0xE0, 0x68, 0x46, 0x8C, 0xC0, + 0x02, 0xCE, 0x01, 0x23, 0x13, 0xE0, 0x68, 0x46, + 0x8C, 0xC0, 0x08, 0xE0, 0x04, 0x98, 0x00, 0xF0, + 0x3C, 0xF8, 0x45, 0x19, 0x19, 0xE0, 0x41, 0x20, + 0x01, 0x93, 0x00, 0x92, 0x02, 0x90, 0x02, 0xCE, + 0x00, 0x23, 0x10, 0x22, 0xF2, 0xE7, 0x68, 0x46, + 0x8C, 0xC0, 0x02, 0xCE, 0x00, 0x23, 0x0A, 0x22, + 0xEC, 0xE7, 0x02, 0xCE, 0x68, 0x46, 0x01, 0x73, + 0x00, 0x21, 0x41, 0x73, 0x03, 0xA9, 0xD9, 0xE7, + 0xC1, 0xB2, 0x04, 0x98, 0x00, 0xF0, 0x14, 0xF8, + 0x6D, 0x1C, 0x64, 0x1C, 0x20, 0x78, 0x00, 0x28, + 0x9F, 0xD1, 0x04, 0x98, 0x00, 0x28, 0x03, 0xD0, + 0x04, 0x99, 0x00, 0x20, 0x09, 0x68, 0x08, 0x70, + 0x28, 0x46, 0x07, 0xB0, 0xF0, 0xBD, 0x00, 0x00, + 0x28, 0x6E, 0x75, 0x6C, 0x6C, 0x29, 0x00, 0x00, + 0x10, 0xB5, 0x00, 0x28, 0x05, 0xD0, 0x02, 0x68, + 0x11, 0x70, 0x01, 0x68, 0x49, 0x1C, 0x01, 0x60, + 0x10, 0xBD, 0xC8, 0xB2, 0xFF, 0xF7, 0xD4, 0xFC, + 0x10, 0xBD, 0xFF, 0xB5, 0x00, 0x27, 0x83, 0xB0, + 0x0C, 0x9D, 0x3E, 0x46, 0x08, 0x00, 0x3A, 0x46, + 0x05, 0xD0, 0x00, 0x2B, 0x12, 0xD0, 0x05, 0x9B, + 0x0A, 0x2B, 0x0B, 0xD0, 0x0E, 0xE0, 0x30, 0x20, + 0x69, 0x46, 0x08, 0x70, 0x4A, 0x70, 0x2A, 0x46, + 0x0D, 0x9B, 0x03, 0x98, 0x00, 0xF0, 0x74, 0xF8, + 0x07, 0xB0, 0xF0, 0xBD, 0x00, 0x29, 0x01, 0xDA, + 0x01, 0x27, 0x40, 0x42, 0x02, 0xAC, 0x69, 0x46, + 0x03, 0x34, 0xCA, 0x72, 0x0A, 0xE0, 0x05, 0x99, + 0xFD, 0xF7, 0xA0, 0xF8, 0x0A, 0x29, 0x02, 0xDB, + 0x0E, 0x9A, 0x89, 0x18, 0x3A, 0x39, 0x30, 0x31, + 0x64, 0x1E, 0x21, 0x70, 0x00, 0x28, 0xF2, 0xD1, + 0x00, 0x2F, 0x0E, 0xD0, 0x00, 0x2D, 0x09, 0xD0, + 0x0D, 0x98, 0x80, 0x07, 0x06, 0xD5, 0x2D, 0x21, + 0x03, 0x98, 0xFF, 0xF7, 0xB9, 0xFF, 0x76, 0x1C, + 0x6D, 0x1E, 0x02, 0xE0, 0x2D, 0x20, 0x64, 0x1E, + 0x20, 0x70, 0x2A, 0x46, 0x21, 0x46, 0x0D, 0x9B, + 0x03, 0x98, 0x00, 0xF0, 0x45, 0xF8, 0x80, 0x19, + 0xCE, 0xE7, 0x0F, 0xB4, 0x10, 0xB5, 0x03, 0xAA, + 0x00, 0x20, 0x02, 0x99, 0xFF, 0xF7, 0x2E, 0xFF, + 0x10, 0xBC, 0x08, 0xBC, 0x04, 0xB0, 0x18, 0x47, + 0x10, 0xB5, 0xFF, 0xF7, 0x85, 0xFE, 0x00, 0xF0, + 0x8D, 0xF8, 0x10, 0xBD, 0x00, 0x00, 0x00, 0x00, + 0x36, 0x03, 0x35, 0x03, 0x2C, 0x03, 0x9C, 0x03, + 0xE9, 0x02, 0x28, 0x03, 0x29, 0x03, 0x1C, 0x03, + 0x9D, 0x03, 0x2F, 0x03, 0x4A, 0x03, 0x4D, 0x03, + 0xFF, 0x03, 0xFF, 0x03, 0xFF, 0x03, 0xFF, 0x03, + 0x3D, 0x03, 0x3C, 0x03, 0x2C, 0x03, 0x9C, 0x03, + 0xD3, 0x02, 0x28, 0x03, 0x29, 0x03, 0xD6, 0x02, + 0x9E, 0x03, 0x30, 0x03, 0x37, 0x03, 0x3A, 0x03, + 0xFF, 0x03, 0xFF, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xB5, 0x00, 0x25, 0x20, 0x27, 0x81, 0xB0, + 0x0E, 0x46, 0x14, 0x46, 0x00, 0x2A, 0x0E, 0xDD, + 0x00, 0x20, 0x01, 0xE0, 0x40, 0x1C, 0x49, 0x1C, + 0x0A, 0x78, 0x00, 0x2A, 0xFA, 0xD1, 0xA0, 0x42, + 0x01, 0xDB, 0x00, 0x24, 0x00, 0xE0, 0x24, 0x1A, + 0x98, 0x07, 0x00, 0xD5, 0x30, 0x27, 0xD8, 0x07, + 0x06, 0xD0, 0x0D, 0xE0, 0x39, 0x46, 0x01, 0x98, + 0xFF, 0xF7, 0x4A, 0xFF, 0x6D, 0x1C, 0x64, 0x1E, + 0x00, 0x2C, 0xF7, 0xDC, 0x04, 0xE0, 0x01, 0x98, + 0xFF, 0xF7, 0x42, 0xFF, 0x6D, 0x1C, 0x76, 0x1C, + 0x31, 0x78, 0x00, 0x29, 0xF7, 0xD1, 0x05, 0xE0, + 0x39, 0x46, 0x01, 0x98, 0xFF, 0xF7, 0x38, 0xFF, + 0x6D, 0x1C, 0x64, 0x1E, 0x00, 0x2C, 0xF7, 0xDC, + 0x28, 0x46, 0x05, 0xB0, 0xF0, 0xBD, 0x00, 0x00, + 0x05, 0x48, 0x01, 0x78, 0x82, 0x29, 0x05, 0xD1, + 0xC0, 0x79, 0x01, 0x28, 0x02, 0xD1, 0x03, 0x49, + 0xFF, 0x20, 0x08, 0x70, 0x70, 0x47, 0x00, 0x00, + 0x20, 0x08, 0x00, 0x20, 0x40, 0x01, 0x00, 0x20, + 0x05, 0x48, 0x00, 0x21, 0x01, 0x80, 0x41, 0x80, + 0x04, 0x49, 0x81, 0x80, 0xC1, 0x80, 0x04, 0x49, + 0x03, 0x20, 0x08, 0x70, 0x70, 0x47, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x20, 0xFF, 0x7F, 0x00, 0x00, + 0x90, 0x01, 0x00, 0x20, 0x08, 0x49, 0x5A, 0x20, + 0x08, 0x70, 0x08, 0x49, 0x00, 0x20, 0x08, 0x70, + 0x08, 0x48, 0x07, 0x49, 0x81, 0x80, 0xC1, 0x80, + 0x07, 0x49, 0x03, 0x20, 0x08, 0x70, 0x07, 0x49, + 0x01, 0x20, 0x08, 0x70, 0x70, 0x47, 0x00, 0x00, + 0x94, 0x01, 0x00, 0x20, 0x0F, 0x08, 0x00, 0x20, + 0xFF, 0x7F, 0x00, 0x00, 0x10, 0x00, 0x00, 0x20, + 0x90, 0x01, 0x00, 0x20, 0x91, 0x01, 0x00, 0x20, + 0x00, 0x00, 0x00, 0x40, 0x10, 0x03, 0x42, 0x88, + 0x0C, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x09, 0x00, 0x50, 0x1F, 0x00, 0x00, 0x00, + 0x04, 0x03, 0x00, 0x50, 0x64, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x50, 0x77, 0x00, 0x01, 0x48, + 0x44, 0x09, 0x00, 0x50, 0x39, 0x5A, 0x5B, 0x00, + 0x10, 0x06, 0x00, 0x50, 0x00, 0x00, 0x06, 0x07, + 0x00, 0x06, 0x00, 0x50, 0x00, 0x00, 0x00, 0x78, + 0x08, 0x06, 0x00, 0x50, 0x0C, 0x30, 0x00, 0x00, + 0x28, 0x06, 0x00, 0x50, 0x06, 0x00, 0x00, 0x00, + 0x2C, 0x06, 0x00, 0x50, 0x0A, 0x66, 0x00, 0x00, + 0x30, 0x06, 0x00, 0x50, 0xCC, 0x02, 0x00, 0x20, + 0x34, 0x06, 0x00, 0x50, 0x00, 0x20, 0x00, 0x00, + 0x44, 0x00, 0x00, 0x50, 0x01, 0x00, 0x00, 0x00, + 0x1C, 0x00, 0x00, 0x50, 0x50, 0x71, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x50, 0x24, 0x29, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x40, 0x04, 0x04, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x40, 0x14, 0x33, 0x43, 0xC8, + 0x0C, 0x00, 0x00, 0x40, 0x29, 0x0A, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x40, 0x10, 0x32, 0x00, 0x00, + 0x1C, 0x0E, 0x00, 0x50, 0x03, 0x00, 0x00, 0x00, + 0x54, 0x00, 0x00, 0x50, 0x14, 0x07, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x2C, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, + 0x58, 0x10, 0x00, 0x50, 0x01, 0x00, 0x00, 0x00, + 0x04, 0x11, 0x00, 0x50, 0x78, 0x11, 0x00, 0x00, + 0x0C, 0x11, 0x00, 0x50, 0x01, 0x00, 0x00, 0x00, + 0x10, 0x11, 0x00, 0x50, 0x78, 0x01, 0x00, 0x00, + 0x14, 0x11, 0x00, 0x50, 0xC8, 0x03, 0x60, 0x00, + 0x4C, 0x00, 0x00, 0x50, 0x31, 0x00, 0x00, 0x00, + 0x3C, 0x10, 0x00, 0x50, 0x00, 0x00, 0x10, 0x00, + 0xB4, 0x10, 0x00, 0x50, 0x00, 0x26, 0x31, 0x00, + 0xC0, 0x10, 0x00, 0x50, 0x33, 0x03, 0x33, 0x03, + 0xC4, 0x10, 0x00, 0x50, 0x33, 0x03, 0x33, 0x03, + 0xC8, 0x10, 0x00, 0x50, 0x0C, 0x0A, 0x00, 0x00, + 0xCC, 0x10, 0x00, 0x50, 0x1A, 0x00, 0x00, 0x00, + 0xD0, 0x10, 0x00, 0x50, 0x03, 0x19, 0x19, 0x00, + 0xF0, 0x11, 0x00, 0x50, 0x12, 0x00, 0x00, 0x00, + 0xEC, 0x11, 0x00, 0x50, 0x5C, 0x00, 0x00, 0x00, + 0xF4, 0x11, 0x00, 0x50, 0x01, 0x00, 0x01, 0x00, + 0x2C, 0x10, 0x00, 0x50, 0x10, 0x00, 0x90, 0x00, + 0x30, 0x10, 0x00, 0x50, 0x20, 0x0C, 0x90, 0x00, + 0x34, 0x10, 0x00, 0x50, 0x30, 0x0C, 0x30, 0x0C, + 0x38, 0x10, 0x00, 0x50, 0xFF, 0x0F, 0x00, 0x00, + 0x7C, 0x10, 0x00, 0x50, 0x88, 0x88, 0xFE, 0x88, + 0x80, 0x10, 0x00, 0x50, 0x88, 0xFF, 0x00, 0x00, + 0x84, 0x10, 0x00, 0x50, 0x55, 0x55, 0x55, 0x55, + 0x88, 0x10, 0x00, 0x50, 0x55, 0x55, 0x55, 0x55, + 0x8C, 0x10, 0x00, 0x50, 0x55, 0x55, 0x55, 0x55, + 0xE8, 0x10, 0x00, 0x50, 0x3F, 0x16, 0x3F, 0x15, + 0x04, 0x00, 0x00, 0x40, 0x04, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x30, 0x3B, 0x00, 0x00, 0x04, 0x00, 0x00, 0x20, + 0x08, 0x00, 0x00, 0x00, 0x5C, 0x0A, 0x00, 0x00, + 0x38, 0x3B, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x20, + 0x54, 0x0E, 0x00, 0x00, 0x6A, 0x0A, 0x00, 0x00, + 0xFF, 0x00, 0x00, 0x00, 0x78, 0x7F, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x52, 0x57, 0x41, 0x66, +}; +const unsigned char u8_rad_testpara_30[] = { + 0xA1, 0x00, 0x03, 0xF3, 0x02, 0x02, 0x40, 0x01, + 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x40, 0x01, 0x68, 0x01, 0x44, 0x01, 0x44, 0x01, + 0x05, 0x05, 0x00, 0x00, 0x01, 0x05, 0x41, 0x06, + 0x0A, 0x0B, 0x41, 0x0F, 0x10, 0x14, 0x41, 0x15, + 0x02, 0x07, 0x41, 0x16, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41, 0x41, 0x04, 0x03, 0x09, 0x41, 0x08, + 0x0E, 0x0D, 0x41, 0x13, 0x12, 0x18, 0x41, 0x17, + 0x0C, 0x11, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0C, 0x3E, 0x0C, 0x3E, 0x08, 0x08, 0x02, 0x02, + 0x14, 0x03, 0x08, 0x08, 0x01, 0x01, 0xC8, 0x00, + 0x1A, 0x01, 0x08, 0x14, 0x82, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5E, 0x01, + 0xA0, 0x00, 0x64, 0x00, 0x4A, 0x00, 0x0A, 0x78, + 0x0F, 0x08, 0x1A, 0x4A, 0x4A, 0x3C, 0x3C, 0x00, + 0x00, 0x00, 0x6A, 0x00, 0x38, 0x00, 0x1C, 0x00, + 0x06, 0x32, 0x69, 0xE3, 0x0D, 0x00, 0x00, 0x72, + 0x45, 0x00, 0x00, 0x06, 0x00, 0xA0, 0x00, 0x78, + 0x00, 0x64, 0xDB, 0x13, 0xB5, 0x0C, 0x2D, 0x03, + 0xCB, 0x00, 0x14, 0x0A, 0xB1, 0x01, 0xF1, 0x05, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x55, 0xAA, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x09, 0x5D, 0xA5, 0xB2, 0x5E, 0xA0, 0x80, 0x7C, +}; diff --git a/qcom/opensource/touch-drivers/raydium/raydium_driver.c b/qcom/opensource/touch-drivers/raydium/raydium_driver.c new file mode 100644 index 0000000000..3ebb570971 --- /dev/null +++ b/qcom/opensource/touch-drivers/raydium/raydium_driver.c @@ -0,0 +1,2703 @@ +/* raydium_driver.c + * + * Raydium TouchScreen driver. + * + * Copyright (c) 2021 Raydium tech Ltd. + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "raydium_driver.h" +#include +#include +#if defined(CONFIG_FB) +#include +#include +#elif defined(CONFIG_HAS_EARLYSUSPEND) +#include +#endif /*end of CONFIG_FB*/ +#if defined(CONFIG_PANEL_NOTIFIER) +#include +#endif + +struct raydium_slot_status { + unsigned char pt_id; /*Occupied point ID*/ + unsigned char need_update; /*Mark as info need to be updated*/ + unsigned char pt_report_offset; /*point info offset in report*/ +}; +/*The first 3 elements are currently occupied. therest is new coming points*/ +struct raydium_slot_status gst_slot[MAX_TOUCH_NUM * 2]; +struct raydium_slot_status gst_slot_init = {0xFF, 0, 0}; + +static int raydium_enable_regulator(struct raydium_ts_data *cd, bool en); + +#if (defined(CONFIG_RM_SYSFS_DEBUG)) +const struct attribute_group raydium_attr_group; +#endif /*end of CONFIG_RM_SYSFS_DEBUG*/ + +#if defined(CONFIG_DRM) || defined(CONFIG_PANEL_NOTIFIER) + static struct drm_panel *active_panel; +#endif + +unsigned char g_u8_addr; +unsigned char g_u8_raydium_flag; +unsigned char g_u8_i2c_mode; +unsigned char g_u8_upgrade_type; +unsigned char g_u8_raw_data_type; +unsigned int g_u32_raw_data_len; /* 128 bytes*/ +unsigned long g_u32_addr; +unsigned int g_u32_length; +unsigned int g_u32_driver_version; +unsigned char *g_rad_fw_image, *g_rad_init_image; +unsigned char *g_rad_boot_image, *g_rad_para_image; +unsigned char *g_rad_testfw_image, *g_rad_testpara_image; +unsigned char g_u8_table_setting, g_u8_table_init; +unsigned char g_u8_resetflag; +unsigned char g_u8_wakeup_flag; +#ifdef ESD_SOLUTION_EN +unsigned char g_u8_checkflag; +#endif +unsigned char g_u8_log_level; +struct raydium_ts_data *g_raydium_ts; + +/******************************************************************************* + * Name: raydium_variable_init + * Brief: + * Input: + * Output: + * Return: + *******************************************************************************/ +static void raydium_variable_init(void) +{ + g_u8_addr = RAYDIUM_PDA2_PDA_CFG_ADDR; + g_u8_raydium_flag = NORMAL_MODE; + g_u8_i2c_mode = PDA2_MODE; + g_u8_upgrade_type = 0; + g_u8_raw_data_type = RAYDIUM_FT_UPDATE; + g_u32_raw_data_len = 64 * 2; /* 128 bytes*/ + g_u32_addr = RAYDIUM_CHK_I2C_CMD; + g_u32_length = 1; + g_u8_table_setting = 0; + g_u8_table_init = 0; + g_rad_fw_image = NULL; + g_rad_init_image = NULL; + g_rad_boot_image = NULL; + g_rad_para_image = NULL; + g_rad_testfw_image = NULL; + g_rad_testpara_image = NULL; + g_u32_driver_version = ((RAD_MAIN_VERSION << 24) | + (RAD_MINOR_VERSION << 16) | + (RAD_CUSTOMER_VERSION)); + g_u8_resetflag = false; + g_u8_wakeup_flag = false; +#ifdef ESD_SOLUTION_EN + g_u8_checkflag = false; +#endif + g_u8_log_level = LOG_INFO; +} + + +/******************************************************************************* + * Name: raydium_gpio_configure + * Brief: + * Input: + * Output: + * Return: + *******************************************************************************/ + +static int raydium_gpio_configure(bool on) +{ + int i32_err = 0; + + if (on) { + if (gpio_is_valid(g_raydium_ts->irq_gpio)) { + i32_err = gpio_request(g_raydium_ts->irq_gpio, + "raydium_irq_gpio"); + if (i32_err) { + LOGD(LOG_ERR, "[touch]irq gpio request failed"); + goto err_irq_gpio_req; + } + + i32_err = gpio_direction_input(g_raydium_ts->irq_gpio); + if (i32_err) { + LOGD(LOG_ERR, "[touch]set_direction for irq gpio failed\n"); + goto err_irq_gpio_dir; + } + } + if (gpio_is_valid(g_raydium_ts->rst_gpio)) { + i32_err = gpio_request(g_raydium_ts->rst_gpio, + "raydium_rst_gpio"); + if (i32_err) { + LOGD(LOG_ERR, "[touch]rst gpio request failed"); + goto err_irq_gpio_req; + } + + i32_err = gpio_direction_output(g_raydium_ts->rst_gpio, 0); + msleep(RAYDIUM_RESET_INTERVAL_10MSEC); + if (i32_err) { + LOGD(LOG_ERR, + "[touch]set_direction for rst gpio failed\n"); + goto err_rst_gpio_dir; + } + + i32_err = gpio_direction_output(g_raydium_ts->rst_gpio, 1); + if (i32_err) { + LOGD(LOG_ERR, + "[touch]set_direction for irq gpio failed\n"); + goto err_rst_gpio_dir; + } + } + } else { + if (gpio_is_valid(g_raydium_ts->irq_gpio)) + gpio_free(g_raydium_ts->irq_gpio); + } + return 0; +err_rst_gpio_dir: + if (gpio_is_valid(g_raydium_ts->rst_gpio)) + gpio_free(g_raydium_ts->rst_gpio); + return i32_err; +err_irq_gpio_dir: + if (gpio_is_valid(g_raydium_ts->irq_gpio)) + gpio_free(g_raydium_ts->irq_gpio); +err_irq_gpio_req: + return i32_err; +} + +/******************************************************************************* + * Name: raydium_ts_pinctrl_init + * Brief: + * Input: + * Output: + * Return: + *******************************************************************************/ +#ifdef MSM_NEW_VER +static int raydium_ts_pinctrl_init(void) +{ + int i32_ret; + + /* Get pinctrl if target uses pinctrl */ + g_raydium_ts->ts_pinctrl = devm_pinctrl_get(&(g_raydium_ts->client->dev)); + if (IS_ERR_OR_NULL(g_raydium_ts->ts_pinctrl)) { + i32_ret = PTR_ERR(g_raydium_ts->ts_pinctrl); + LOGD(LOG_ERR, "[touch]target does not use pinctrl %d\n", i32_ret); + goto err_pinctrl_get; + } + + g_raydium_ts->pinctrl_state_active + = pinctrl_lookup_state(g_raydium_ts->ts_pinctrl, PINCTRL_STATE_ACTIVE); + if (IS_ERR_OR_NULL(g_raydium_ts->pinctrl_state_active)) { + i32_ret = PTR_ERR(g_raydium_ts->pinctrl_state_active); + LOGD(LOG_ERR, "[touch]Can not lookup %s pinstate %d\n", + PINCTRL_STATE_ACTIVE, i32_ret); + goto err_pinctrl_lookup; + } + + g_raydium_ts->pinctrl_state_suspend + = pinctrl_lookup_state(g_raydium_ts->ts_pinctrl, + PINCTRL_STATE_SUSPEND); + if (IS_ERR_OR_NULL(g_raydium_ts->pinctrl_state_suspend)) { + i32_ret = PTR_ERR(g_raydium_ts->pinctrl_state_suspend); + LOGD(LOG_ERR, "[touch]Can not lookup %s pinstate %d\n", + PINCTRL_STATE_SUSPEND, i32_ret); + goto err_pinctrl_lookup; + } + + g_raydium_ts->pinctrl_state_release + = pinctrl_lookup_state(g_raydium_ts->ts_pinctrl, + PINCTRL_STATE_RELEASE); + if (IS_ERR_OR_NULL(g_raydium_ts->pinctrl_state_release)) { + i32_ret = PTR_ERR(g_raydium_ts->pinctrl_state_release); + LOGD(LOG_ERR, "[touch]Can not lookup %s pinstate %d\n", + PINCTRL_STATE_RELEASE, i32_ret); + } + + return 0; + +err_pinctrl_lookup: + devm_pinctrl_put(g_raydium_ts->ts_pinctrl); +err_pinctrl_get: + g_raydium_ts->ts_pinctrl = NULL; + return i32_ret; +} +#endif/*end of MSM_NEW_VER*/ +#ifdef ESD_SOLUTION_EN +static int raydium_hw_reset_fun(struct i2c_client *client) +{ + int i32_ret = SUCCESS; + + LOGD(LOG_INFO, "[touch]HW reset\n"); + g_u8_raydium_flag |= ENG_MODE; + + g_u8_resetflag = true; + /*HW reset*/ + gpio_set_value(g_raydium_ts->rst_gpio, 1); + gpio_set_value(g_raydium_ts->rst_gpio, 0); + msleep(RAYDIUM_RESET_INTERVAL_MSEC); + gpio_set_value(g_raydium_ts->rst_gpio, 1); + + g_u8_i2c_mode = PDA2_MODE; + + i32_ret = wait_irq_state(client, 300, 2000); + if (i32_ret != ERROR) + msleep(35); + + g_u8_raydium_flag &= ~ENG_MODE; + + LOGD(LOG_INFO, "[touch]Raydium HW reset : %d\n", i32_ret); + return i32_ret; +} +#endif + +int raydium_i2c_write_pda_via_pda2(struct i2c_client *client, + unsigned int u32_addr, unsigned char *u8_w_data, + unsigned short u16_length) +{ + int i32_ret = -1; + /*unsigned char u8_retry;*/ + unsigned char u8_mode = 0x00; + unsigned char u8_buf[MAX_WRITE_PACKET_SIZE + 6]; + + struct i2c_msg msg[] = { + { + .addr = RAYDIUM_I2C_NID, + .flags = RAYDIUM_I2C_WRITE, + .len = u16_length + 6, + .buf = u8_buf, + }, + }; + if (u16_length > MAX_WRITE_PACKET_SIZE) + return -EINVAL; + + + /*I2C access for register need to word mode*/ + + if ((u16_length == 4) && + ((u32_addr & 0x50000000) || (u32_addr & 0x40000000))) + u8_mode = I2C_PDA2_WORD_MODE; + else + u8_mode = I2C_PDA2_BYTE_MODE; + + if (u16_length > MAX_WRITE_PACKET_SIZE) + return -EINVAL; + g_u8_i2c_mode = PDA2_MODE; + + u8_buf[0] = RAYDIUM_I2C_PDA_CMD; + u8_buf[1] = (unsigned char)u32_addr; + u8_buf[2] = (unsigned char)(u32_addr >> 8); + u8_buf[3] = (unsigned char)(u32_addr >> 16); + u8_buf[4] = (unsigned char)(u32_addr >> 24); + u8_buf[5] = u8_mode; + + memcpy(&u8_buf[6], u8_w_data, u16_length); + + /*for (u8_retry = 0; u8_retry < SYN_I2C_RETRY_TIMES; u8_retry++) {*/ + if (i2c_transfer(client->adapter, msg, 1) == 1) { + i32_ret = u16_length; + /*break;*/ + } + i32_ret = u16_length; + /*usleep_range(500, 1500);*/ + /*}*/ + + /*if (u8_retry == SYN_I2C_RETRY_TIMES) {*/ + /* LOGD(LOG_ERR, "[touch]%s: I2C write over retry limit\n", __func__);*/ + /* i32_ret = -EIO;*/ + /*}*/ + + return i32_ret; + +} +int raydium_i2c_read_pda_via_pda2(struct i2c_client *client, + unsigned int u32_addr, unsigned char *u8_r_data, + unsigned short u16_length) +{ + int i32_ret; + /*unsigned char u8_retry;*/ + unsigned char u8_mode = 0x00; + unsigned char u8_buf[6]; + + struct i2c_msg msg[] = { + { + .addr = RAYDIUM_I2C_NID, + .flags = RAYDIUM_I2C_WRITE, + .len = 6, + .buf = u8_buf, + }, + { + .addr = RAYDIUM_I2C_NID, + .flags = RAYDIUM_I2C_READ, + .len = u16_length, + .buf = u8_r_data, + }, + }; + + if ((u32_addr & 0x50000000) || (u32_addr & 0x40000000)) + u8_mode = I2C_PDA2_WORD_MODE; + else + u8_mode = I2C_PDA2_BYTE_MODE; + + g_u8_i2c_mode = PDA2_MODE; + u8_buf[0] = RAYDIUM_I2C_PDA_CMD; + u8_buf[1] = (unsigned char)u32_addr; + u8_buf[2] = (unsigned char)(u32_addr >> 8); + u8_buf[3] = (unsigned char)(u32_addr >> 16); + u8_buf[4] = (unsigned char)(u32_addr >> 24); + u8_buf[5] = u8_mode; + + /*for (u8_retry = 0; u8_retry < SYN_I2C_RETRY_TIMES; u8_retry++) {*/ + if (i2c_transfer(g_raydium_ts->client->adapter, msg, 2) == 2) { + i32_ret = u16_length; + /*break;*/ + } + i32_ret = u16_length; + /*usleep_range(500, 1500);*/ + /*}*/ + + /*if (u8_retry == SYN_I2C_RETRY_TIMES) {*/ + /* LOGD(LOG_ERR, "[touch]%s: I2C read over retry limit\n", __func__);*/ + /* i32_ret = -EIO;*/ + /*}*/ + + return i32_ret; +} +int raydium_i2c_pda_set_address(unsigned int u32_address, + unsigned char u8_mode) +{ + int i32_ret = 0; + unsigned char u8_retry; + unsigned char u8_buf[RAD_I2C_PDA_ADDRESS_LENGTH]; + struct i2c_client *client = g_raydium_ts->client; + + client->addr = RAYDIUM_I2C_EID; + u8_buf[0] = (u32_address & 0x0000FF00) >> 8; + u8_buf[1] = (u32_address & 0x00FF0000) >> 16; + u8_buf[2] = (u32_address & 0xFF000000) >> 24; + u8_buf[3] = u8_mode; + + for (u8_retry = 0; u8_retry < SYN_I2C_RETRY_TIMES; u8_retry++) { + i32_ret = i2c_master_send(client, u8_buf, + RAD_I2C_PDA_ADDRESS_LENGTH); + if (i32_ret != RAD_I2C_PDA_ADDRESS_LENGTH) { + LOGD(LOG_ERR, "[touch]%s: I2C retry %d\n", + __func__, u8_retry + 1); + usleep_range(500, 1500); + } else { + break; + } + } + + return (i32_ret == RAD_I2C_PDA_ADDRESS_LENGTH) ? i32_ret : -EIO; +} + +/*device attribute raydium_i2c_pda2_mode used*/ +int raydium_i2c_pda_read(struct i2c_client *client, + unsigned int u32_addr, unsigned char *u8_r_data, + unsigned short u16_length) +{ + int i32_ret; + unsigned char u8_retry; + unsigned char u8_mode = 0x00; + unsigned char u8_buf; + + struct i2c_msg msg[] = { + { + .addr = RAYDIUM_I2C_NID, + .flags = RAYDIUM_I2C_WRITE, + .len = 1, + .buf = &u8_buf, + }, + { + .addr = RAYDIUM_I2C_NID, + .flags = RAYDIUM_I2C_READ, + .len = u16_length, + .buf = u8_r_data, + }, + }; + + if (u16_length == 4) + u8_mode |= RAD_I2C_PDA_MODE_ENABLE | + RAD_I2C_PDA_2_MODE_DISABLE | + RAD_I2C_PDA_MODE_WORD_MODE; + else + u8_mode |= RAD_I2C_PDA_MODE_ENABLE | + RAD_I2C_PDA_2_MODE_DISABLE; + + u8_mode |= 0x03; + + u8_buf = u32_addr & MASK_8BIT; + + i32_ret = raydium_i2c_pda_set_address(u32_addr, u8_mode); + if (i32_ret != RAD_I2C_PDA_ADDRESS_LENGTH) + goto exit; + usleep_range(50, 80); + + for (u8_retry = 0; u8_retry < SYN_I2C_RETRY_TIMES; u8_retry++) { + if (i2c_transfer(g_raydium_ts->client->adapter, msg, 2) == 2) { + i32_ret = u16_length; + break; + } + LOGD(LOG_ERR, "%s: I2C retry %d\n", __func__, u8_retry + 1); + usleep_range(500, 1500); + } + + if (u8_retry == SYN_I2C_RETRY_TIMES) { + LOGD(LOG_ERR, "%s: I2C read over retry limit\n", __func__); + i32_ret = -EIO; + } +exit: + return i32_ret; +} + +int raydium_i2c_pda_write(struct i2c_client *client, + unsigned int u32_addr, unsigned char *u8_w_data, + unsigned short u16_length) +{ + int i32_ret; + unsigned char u8_retry; + unsigned char u8_mode = 0x00; + unsigned char u8_buf[MAX_WRITE_PACKET_SIZE + 1]; + + struct i2c_msg msg[] = { + { + .addr = RAYDIUM_I2C_NID, + .flags = RAYDIUM_I2C_WRITE, + .len = u16_length + 1, + .buf = u8_buf, + }, + }; + + if (u16_length > MAX_WRITE_PACKET_SIZE) + return -EINVAL; + + if (u16_length == 4) + u8_mode |= RAD_I2C_PDA_MODE_ENABLE | + RAD_I2C_PDA_2_MODE_DISABLE | + RAD_I2C_PDA_MODE_WORD_MODE; + else + u8_mode |= RAD_I2C_PDA_MODE_ENABLE | + RAD_I2C_PDA_2_MODE_DISABLE; + + u8_buf[0] = u32_addr & MASK_8BIT; + memcpy(&u8_buf[1], u8_w_data, u16_length); + + i32_ret = raydium_i2c_pda_set_address(u32_addr, u8_mode); + if (i32_ret != RAD_I2C_PDA_ADDRESS_LENGTH) + goto exit; + usleep_range(50, 80); + + for (u8_retry = 0; u8_retry < SYN_I2C_RETRY_TIMES; u8_retry++) { + if (i2c_transfer(client->adapter, msg, 1) == 1) { + i32_ret = u16_length; + break; + } + LOGD(LOG_ERR, "[touch]%s: I2C retry %d\n", __func__, u8_retry + 1); + usleep_range(500, 1500); + } + + if (u8_retry == SYN_I2C_RETRY_TIMES) { + LOGD(LOG_ERR, "[touch]%s: I2C write over retry limit\n", __func__); + i32_ret = -EIO; + } +exit: + return i32_ret; +} + +int handle_i2c_pda_read(struct i2c_client *client, + unsigned int u32_addr, unsigned char *u8_r_data, + unsigned short u16_length) +{ + if ((g_u8_i2c_mode & PDA_MODE) != 0) { + if (raydium_i2c_pda_read(client, u32_addr, u8_r_data, u16_length) == ERROR) { + LOGD(LOG_ERR, "[touch] handle_ic_write I2C NG!\r\n"); + return ERROR; + } + } else { + if (raydium_i2c_read_pda_via_pda2(client, u32_addr, u8_r_data, u16_length) == ERROR) { + LOGD(LOG_ERR, "[touch] handle_ic_write I2C via_pda2 NG!\r\n"); + return ERROR; + } + } + return SUCCESS; +} + +int handle_i2c_pda_write(struct i2c_client *client, + unsigned int u32_addr, unsigned char *u8_w_data, + unsigned short u16_length) +{ + if ((g_u8_i2c_mode & PDA_MODE) != 0) { + if (raydium_i2c_pda_write(client, u32_addr, u8_w_data, u16_length) == ERROR) { + LOGD(LOG_ERR, "[touch] handle_ic_write I2C NG!\r\n"); + return ERROR; + } + } else { + if (raydium_i2c_write_pda_via_pda2(client, u32_addr, u8_w_data, u16_length) == ERROR) { + LOGD(LOG_ERR, "[touch] handle_ic_write I2C via_pda2 NG!\r\n"); + return ERROR; + } + } + return SUCCESS; +} +int raydium_i2c_pda2_set_page(struct i2c_client *client, + unsigned int is_suspend, + unsigned char u8_page) +{ + int i32_ret = -1; + unsigned char u8_retry; + unsigned int u8_ret = (is_suspend) ? 10 : 2; + unsigned char u8_buf[RAYDIUM_I2C_PDA2_PAGE_LENGTH]; + + struct i2c_msg msg[] = { + { + .addr = RAYDIUM_I2C_NID, + .flags = RAYDIUM_I2C_WRITE, + .len = RAYDIUM_I2C_PDA2_PAGE_LENGTH, + .buf = u8_buf, + }, + }; + + u8_buf[0] = RAYDIUM_PDA2_PAGE_ADDR; + u8_buf[1] = u8_page; + for (; u8_ret > 0; u8_ret--) { + for (u8_retry = 0; u8_retry < SYN_I2C_RETRY_TIMES; u8_retry++) { + if (i2c_transfer(client->adapter, msg, 1) == 1) { + i32_ret = RAYDIUM_I2C_PDA2_PAGE_LENGTH; + break; + } + usleep_range(500, 1500); + } + if (i32_ret == RAYDIUM_I2C_PDA2_PAGE_LENGTH) + break; + usleep_range(2000, 5000); + } + + if (u8_ret == 0) { + LOGD(LOG_ERR, "[touch]%s: I2C write over retry limit\n", __func__); + i32_ret = -EIO; + } + + return i32_ret; +} + +int raydium_i2c_pda2_read(struct i2c_client *client, + unsigned char u8_addr, + unsigned char *u8_r_data, + unsigned short u16_length) +{ + int i32_ret = -1; + unsigned char u8_retry; + + struct i2c_msg msg[] = { + { + .addr = RAYDIUM_I2C_NID, + .flags = RAYDIUM_I2C_WRITE, + .len = 1, + .buf = &u8_addr, + }, + { + .addr = RAYDIUM_I2C_NID, + .flags = RAYDIUM_I2C_READ, + .len = u16_length, + .buf = u8_r_data, + }, + }; + g_u8_i2c_mode = PDA2_MODE; + for (u8_retry = 0; u8_retry < SYN_I2C_RETRY_TIMES; u8_retry++) { + if (i2c_transfer(g_raydium_ts->client->adapter, msg, 2) == 2) { + i32_ret = u16_length; + break; + } + usleep_range(500, 1500); + } + + if (u8_retry == SYN_I2C_RETRY_TIMES) { + LOGD(LOG_ERR, "[touch]%s: I2C read over retry limit\n", __func__); + i32_ret = -EIO; + } + + return i32_ret; +} + +int raydium_i2c_pda2_write(struct i2c_client *client, + unsigned char u8_addr, + unsigned char *u8_w_data, + unsigned short u16_length) +{ + int i32_ret = -1; + unsigned char u8_retry; + unsigned char u8_buf[MAX_WRITE_PACKET_SIZE + 1]; + + struct i2c_msg msg[] = { + { + .addr = RAYDIUM_I2C_NID, + .flags = RAYDIUM_I2C_WRITE, + .len = u16_length + 1, + .buf = u8_buf, + }, + }; + + if (u16_length > MAX_WRITE_PACKET_SIZE) + return -EINVAL; + g_u8_i2c_mode = PDA2_MODE; + u8_buf[0] = u8_addr; + memcpy(&u8_buf[1], u8_w_data, u16_length); + + for (u8_retry = 0; u8_retry < SYN_I2C_RETRY_TIMES; u8_retry++) { + if (i2c_transfer(client->adapter, msg, 1) == 1) { + i32_ret = u16_length; + break; + } + usleep_range(500, 1500); + } + + if (u8_retry == SYN_I2C_RETRY_TIMES) { + LOGD(LOG_ERR, "[touch]%s: I2C write over retry limit\n", __func__); + i32_ret = -EIO; + } + + return i32_ret; +} + +void raydium_irq_control(bool enable) +{ + if (enable) { + if (g_raydium_ts->irq_enabled) { + /*mutex_unlock(&ts->lock);*/ + LOGD(LOG_INFO, "[touch]Already enable irq\n"); + return; + } + + /* Clear interrupts first */ +#if defined(CONFIG_PANEL_NOTIFIER) + if (g_raydium_ts->blank != DRM_PANEL_EVENT_BLANK) { +#else + if (g_raydium_ts->blank != DRM_PANEL_BLANK_POWERDOWN) { +#endif + if (g_u8_i2c_mode == PDA2_MODE) { + mutex_lock(&g_raydium_ts->lock); + if (raydium_i2c_pda2_set_page(g_raydium_ts->client, + g_raydium_ts->is_suspend, + RAYDIUM_PDA2_PAGE_0) < 0) + LOGD(LOG_ERR, "[touch]set page fail%s\n", __func__); + mutex_unlock(&g_raydium_ts->lock); + usleep_range(500, 1500); + } + } + while (g_raydium_ts->irq_desc->depth > 0) { + LOGD(LOG_INFO, "[touch]irq enable\n"); + g_raydium_ts->irq_enabled = true; + enable_irq(g_raydium_ts->irq); + } + } else { + if (g_raydium_ts->irq_enabled) { + if (g_raydium_ts->irq_desc->depth == 0) { + disable_irq(g_raydium_ts->irq); + g_raydium_ts->irq_enabled = false; + LOGD(LOG_INFO, "[touch]irq disable\n"); + } + } + } +} + +unsigned char raydium_disable_i2c_deglitch(void) +{ + unsigned int u32_buf = 0; + unsigned char u8_retry = 3, u8_comfirm_time = 3; + unsigned char u8_check = 0, u8_i = 0; + unsigned int u32_i2c_deglitch = 0x07060000; + unsigned char u8_buf[4]; + + while (u8_retry--) { + u32_buf = 0; + handle_i2c_pda_read(g_raydium_ts->client, RAYDIUM_CHK_I2C_CMD, + (unsigned char *)(&u32_buf), 4); + if ((u32_buf & 0xFFFF0000) == 0xF3030000) + u8_check++; + } + if (u8_check == 3) { + LOGD(LOG_INFO, "[touch]PDA2 OK\r\n"); + return SUCCESS; + } + + g_u8_i2c_mode = PDA_MODE; + u8_retry = 100; + while (u8_retry--) { + u8_check = 0; + for (u8_i = 0; u8_i < u8_comfirm_time; u8_i++) { + /*check I2C*/ + u32_buf = 0; + if (handle_i2c_pda_read(g_raydium_ts->client, + RAYDIUM_PDA_I2CENG, + (unsigned char *)(&u32_buf), 4) == ERROR) { + LOGD(LOG_ERR, "[touch]%s: 1.handle_ic_read I2C NG!\r\n", __func__); + break; + } + + if (u32_buf == u32_i2c_deglitch) + u8_check++; + else + break; + } + + if (u8_check == u8_comfirm_time) + break; + + if (handle_i2c_pda_write(g_raydium_ts->client, RAYDIUM_PDA_I2CENG, + (unsigned char *)(&u32_i2c_deglitch), 4) == ERROR) { + LOGD(LOG_ERR, "[touch]%s:handle_ic_write I2C NG!\r\n", __func__); + continue; + } + + u8_check = 0; + for (u8_i = 0; u8_i < u8_comfirm_time; u8_i++) { + /*check I2C*/ + u32_buf = 0; + if (handle_i2c_pda_read(g_raydium_ts->client, + RAYDIUM_PDA_I2CENG, + (unsigned char *)(&u32_buf), 4) == ERROR) { + LOGD(LOG_ERR, "[touch]%s:2.handle_ic_read I2C NG!\r\n", __func__); + break; + } + + if (u32_buf == u32_i2c_deglitch) + u8_check++; + else + break; + } + + if (u8_check == u8_comfirm_time) + break; + } + + if (u8_retry == 0) + return ERROR; + + u32_buf = 0x03; + if (handle_i2c_pda_write(g_raydium_ts->client, RAYDIUM_REG_GPIO_DEGLITCH, + (unsigned char *)(&u32_buf), 4) == ERROR) { + LOGD(LOG_ERR, "[touch]%s:3.handle_ic_write I2C NG!\r\n", __func__); + return ERROR; + } + + /*Disable PDA*/ + handle_i2c_pda_read(g_raydium_ts->client, RAYDIUM_PDA_I2CREG, u8_buf, 4); + u8_buf[0] |= RAD_ENABLE_PDA2 | RAD_ENABLE_SI2; + handle_i2c_pda_write(g_raydium_ts->client, RAYDIUM_PDA_I2CREG, u8_buf, 4); + raydium_i2c_pda_set_address(0x50000628, DISABLE); + + g_u8_i2c_mode = PDA2_MODE; + + return SUCCESS; +} + +#ifdef CONFIG_RM_SYSFS_DEBUG + +int raydium_i2c_mode_control(struct i2c_client *client, + unsigned char u8_mode) +{ + unsigned char u8_buf[4]; + + switch (u8_mode) { + case 0: /* Disable INT flag */ + LOGD(LOG_INFO, "[touch]RAD INT flag : %d\n", g_raydium_ts->irq_enabled); + disable_irq(g_raydium_ts->irq); + g_raydium_ts->irq_enabled = false; + LOGD(LOG_INFO, "[touch]RAD irq disable\n"); + break; + case 1: /* Enable INT flag */ + LOGD(LOG_INFO, "[touch]RAD INT flag : %d\n", g_raydium_ts->irq_enabled); + enable_irq(g_raydium_ts->irq); + g_raydium_ts->irq_enabled = true; + LOGD(LOG_INFO, "[touch]RAD irq enable\n"); + break; + case 2: /* Disable INT by raydium_irq_control */ + raydium_irq_control(DISABLE); + break; + case 3: /* Enable INT by raydium_irq_control */ + raydium_irq_control(ENABLE); + break; + case 4: /* Show RAD INT depth */ + LOGD(LOG_INFO, "[touch]RAD INT depth : %d\n", g_raydium_ts->irq_desc->depth); + break; + case 7: + raydium_i2c_pda2_set_page(client, + g_raydium_ts->is_suspend, RAYDIUM_PDA2_2_PDA); + g_u8_i2c_mode = PDA_MODE; + LOGD(LOG_INFO, "[touch]Disable PDA2_MODE\n"); + break; + case 8: + raydium_i2c_pda_read(client, RAYDIUM_PDA_I2CREG, u8_buf, 4); + u8_buf[0] |= RAD_ENABLE_PDA2 | RAD_ENABLE_SI2; + raydium_i2c_pda_write(client, RAYDIUM_PDA_I2CREG, u8_buf, 4); + raydium_i2c_pda_set_address(RAYDIUM_PDA_I2CREG, DISABLE); + + g_u8_i2c_mode = PDA2_MODE; + LOGD(LOG_INFO, "[touch]Enable PDA2_MODE\n"); + break; + } + return 0; +} + + +const struct attribute_group raydium_attr_group = { + .attrs = raydium_attributes +}; + +/*create sysfs for debug update firmware*/ +static int raydium_create_sysfs(struct i2c_client *client) +{ + int ret = -1; + + ret = sysfs_create_group(&(client->dev.kobj), &raydium_attr_group); + if (ret) { + LOGD(LOG_ERR, "[touch]failed to register sysfs\n"); + sysfs_remove_group(&client->dev.kobj, &raydium_attr_group); + ret = -EIO; + } else { + LOGD(LOG_DEBUG, "[touch]create raydium sysfs attr_group successful\n"); + } + return ret; +} + +static void raydium_release_sysfs(struct i2c_client *client) +{ + sysfs_remove_group(&client->dev.kobj, &raydium_attr_group); +} +#endif /*end of CONFIG_RM_SYSFS_DEBUG*/ + +#ifdef ESD_SOLUTION_EN +int raydium_esd_check(void) +{ + int i32_ret = 0; + unsigned char u8_esd_status[MAX_TCH_STATUS_PACKET_SIZE]; + + mutex_lock(&g_raydium_ts->lock); + if (g_u8_i2c_mode == PDA2_MODE) { + i32_ret = raydium_i2c_pda2_set_page(g_raydium_ts->client, + g_raydium_ts->is_suspend, + RAYDIUM_PDA2_PAGE_0); + if (i32_ret < 0) + goto exit; + /*read esd status*/ + i32_ret = raydium_i2c_pda2_read(g_raydium_ts->client, + RAYDIUM_PDA2_TCH_RPT_STATUS_ADDR, + u8_esd_status, MAX_TCH_STATUS_PACKET_SIZE); + if (i32_ret < 0) { + LOGD(LOG_ERR, "[touch]%s: failed to read data: %d\n", + __func__, __LINE__); + goto exit; + } + + if (u8_esd_status[POS_FW_STATE] != 0x1A && + u8_esd_status[POS_FW_STATE] != 0xAA) { + if (g_u8_resetflag == true) { + LOGD(LOG_ERR, "[touch]%s -> filter abnormal irq\n" + , __func__); + goto exit; + } + LOGD(LOG_ERR, "[touch]%s -> abnormal irq, FW state = 0x%x\n", + __func__, u8_esd_status[POS_FW_STATE]); + g_u8_resetflag = false; + i32_ret = -1; + goto exit; + + } + g_u8_resetflag = false; + } +exit: + mutex_unlock(&g_raydium_ts->lock); + LOGD(LOG_INFO, "[touch]%s\n", __func__); + return i32_ret; +} +#endif + + + + +static int raydium_touch_report(unsigned char *p_u8_buf, + unsigned char u8_points_amount) +{ + unsigned char u8_i, u8_j, u8_offset = 0, u8_pt_id; + signed short i16_wx, i16_wy; + /* number of touch points */ + unsigned char u8_touch_count = 0; + //DECLARE_BITMAP(ids, g_raydium_ts->u8_max_touchs); + unsigned long *ids = NULL; + + ids = kzalloc(sizeof(*ids)*BITS_TO_LONGS(g_raydium_ts->u8_max_touchs), GFP_KERNEL); + if (!ids) + return -ENOMEM; + + bitmap_zero(ids, g_raydium_ts->u8_max_touchs); + + for (u8_i = 0; u8_i < (g_raydium_ts->u8_max_touchs * 2); u8_i++) { + gst_slot[u8_i].need_update = 0; + gst_slot[u8_i].pt_report_offset = 0; + } + + /*Check incoming point info*/ + for (u8_i = 0; u8_i < u8_points_amount; u8_i++) { + u8_pt_id = p_u8_buf[POS_PT_ID + u8_i * LEN_PT]; + /* Current*/ + for (u8_j = 0; u8_j < g_raydium_ts->u8_max_touchs; u8_j++) { + if (u8_pt_id == gst_slot[u8_j].pt_id) { + gst_slot[u8_j].need_update = 1; + gst_slot[u8_j].pt_report_offset = u8_i; + break; + } + } + /* New coming*/ + if (u8_j == g_raydium_ts->u8_max_touchs) { + for (u8_j = g_raydium_ts->u8_max_touchs; + u8_j < (g_raydium_ts->u8_max_touchs * 2); u8_j++) { + if (!gst_slot[u8_j].need_update) { + gst_slot[u8_j].pt_id = u8_pt_id; + gst_slot[u8_j].need_update = 1; + gst_slot[u8_j].pt_report_offset = u8_i; + LOGD(LOG_DEBUG, "[touch]x:%d,y:%d\n", + p_u8_buf[POS_X_L + u8_offset] | + p_u8_buf[POS_X_H + u8_offset] << 8, + p_u8_buf[POS_Y_L + u8_offset] | + p_u8_buf[POS_Y_H + u8_offset] << 8); + break; + } + } + } + } + + /*Release slot with non-occupied point*/ + for (u8_i = 0; u8_i < g_raydium_ts->u8_max_touchs; u8_i++) { + if (!gst_slot[u8_i].need_update) { + input_mt_slot(g_raydium_ts->input_dev, u8_i); + input_mt_report_slot_state(g_raydium_ts->input_dev, + MT_TOOL_FINGER, false); + gst_slot[u8_i].pt_id = 0xFF; + gst_slot[u8_i].pt_report_offset = 0; + gst_slot[u8_i].need_update = 0; + } + } + /*Assign new one to non-occupied slot*/ + for (u8_i = g_raydium_ts->u8_max_touchs; + u8_i < (g_raydium_ts->u8_max_touchs * 2); u8_i++) { + if (gst_slot[u8_i].need_update) { + for (u8_j = 0; u8_j < g_raydium_ts->u8_max_touchs; u8_j++) { + if (!gst_slot[u8_j].need_update) { + gst_slot[u8_j] = gst_slot[u8_i]; + gst_slot[u8_i] = gst_slot_init; + break; + } + } + } else { + break; + } + } + for (u8_i = 0; u8_i < g_raydium_ts->u8_max_touchs; u8_i++) { + if (gst_slot[u8_i].need_update) { + u8_offset = gst_slot[u8_i].pt_report_offset * LEN_PT; + g_raydium_ts->x_pos[u8_i] = p_u8_buf[POS_X_L + u8_offset] | + p_u8_buf[POS_X_H + u8_offset] << BYTE_SHIFT; + g_raydium_ts->y_pos[u8_i] = p_u8_buf[POS_Y_L + u8_offset] | + p_u8_buf[POS_Y_H + u8_offset] << BYTE_SHIFT; + g_raydium_ts->pressure = p_u8_buf[POS_PRESSURE_L + u8_offset] | + p_u8_buf[POS_PRESSURE_H + u8_offset] << BYTE_SHIFT; + i16_wx = p_u8_buf[POS_WX_L + u8_offset] | + p_u8_buf[POS_WX_H + u8_offset] << BYTE_SHIFT; + i16_wy = p_u8_buf[POS_WY_L + u8_offset] | + p_u8_buf[POS_WY_H + u8_offset] << BYTE_SHIFT; + + input_mt_slot(g_raydium_ts->input_dev, u8_i); + input_mt_report_slot_state(g_raydium_ts->input_dev, + MT_TOOL_FINGER, true); + __set_bit(u8_i, ids); + + input_report_abs(g_raydium_ts->input_dev, + ABS_MT_POSITION_X, g_raydium_ts->x_pos[u8_i]); + input_report_abs(g_raydium_ts->input_dev, + ABS_MT_POSITION_Y, g_raydium_ts->y_pos[u8_i]); + input_report_abs(g_raydium_ts->input_dev, + ABS_MT_PRESSURE, g_raydium_ts->pressure); + input_report_abs(g_raydium_ts->input_dev, + ABS_MT_TOUCH_MAJOR, max(i16_wx, i16_wy)); + input_report_abs(g_raydium_ts->input_dev, + ABS_MT_TOUCH_MINOR, min(i16_wx, i16_wy)); + LOGD(LOG_DEBUG, "[touch:%d]x:%d,y:%d\n", + u8_i, + p_u8_buf[POS_X_L + u8_offset] | + p_u8_buf[POS_X_H + u8_offset] << 8, + p_u8_buf[POS_Y_L + u8_offset] | + p_u8_buf[POS_Y_H + u8_offset] << 8); + u8_touch_count++; + } + } + input_report_key(g_raydium_ts->input_dev, + BTN_TOUCH, u8_touch_count > 0); + input_report_key(g_raydium_ts->input_dev, + BTN_TOOL_FINGER, u8_touch_count > 0); + + for (u8_i = 0; u8_i < g_raydium_ts->u8_max_touchs; u8_i++) { + if (test_bit(u8_i, ids)) + continue; + input_mt_slot(g_raydium_ts->input_dev, u8_i); + input_mt_report_slot_state(g_raydium_ts->input_dev, + MT_TOOL_FINGER, false); + } + + input_sync(g_raydium_ts->input_dev); + kfree(ids); + + return 0; +} + + +int raydium_read_touchdata(unsigned char *p_u8_tp_status, unsigned char *p_u8_buf) +{ + + int i32_ret = 0; + unsigned char u8_points_amount; + static unsigned char u8_seq_no; + unsigned char u8_retry; + unsigned char u8_read_size; + unsigned char u8_read_buf[MAX_REPORT_PACKET_SIZE]; + u8_retry = 100; + + mutex_lock(&g_raydium_ts->lock); + while (u8_retry != 0) { + i32_ret = raydium_i2c_pda2_set_page(g_raydium_ts->client, + g_raydium_ts->is_suspend, RAYDIUM_PDA2_PAGE_0); + if (i32_ret < 0) { + msleep(250); + u8_retry--; + } else + break; + } + if (u8_retry == 0) { + LOGD(LOG_ERR, "[touch]%s: failed to set page\n", __func__); + + goto reset_error; + } + + memset(u8_read_buf, 0, MAX_REPORT_PACKET_SIZE); + memset(p_u8_buf, 0, MAX_REPORT_PACKET_SIZE); + memset(p_u8_tp_status, 0, MAX_TCH_STATUS_PACKET_SIZE); + u8_read_size = 4 + MAX_TOUCH_NUM * LEN_PT + 1; + /*read touch point information*/ + i32_ret = raydium_i2c_pda2_read(g_raydium_ts->client, + RAYDIUM_PDA2_TCH_RPT_STATUS_ADDR, + u8_read_buf, u8_read_size); + if (i32_ret < 0) { + LOGD(LOG_ERR, "[touch]%s: failed to read data: %d\n", + __func__, __LINE__); + goto exit_error; + } + memcpy(p_u8_tp_status, &u8_read_buf[0], MAX_TCH_STATUS_PACKET_SIZE); + +#ifdef ESD_SOLUTION_EN + if (p_u8_tp_status[POS_FW_STATE] != 0x1A && + p_u8_tp_status[POS_FW_STATE] != 0xAA) { + if (g_u8_resetflag == true) { + LOGD(LOG_ERR, "[touch]%s -> filter irq, FW state = 0x%x\n", + __func__, p_u8_tp_status[POS_FW_STATE]); + i32_ret = -1; + g_u8_resetflag = false; + goto exit_error; + } + LOGD(LOG_ERR, "[touch]%s -> abnormal irq, FW state = 0x%x\n", + __func__, p_u8_tp_status[POS_FW_STATE]); + i32_ret = -1; + goto reset_error; + + } +#endif + /* inform IC to prepare next report*/ + if (u8_seq_no == p_u8_tp_status[POS_SEQ]) { + p_u8_tp_status[POS_SEQ] = 0; + i32_ret = raydium_i2c_pda2_write(g_raydium_ts->client, + RAYDIUM_PDA2_TCH_RPT_STATUS_ADDR, p_u8_tp_status, 1); + if (i32_ret < 0) { + LOGD(LOG_ERR, "[touch]%s: write data failed: %d\n", __func__, i32_ret); + goto exit_error; + } + LOGD(LOG_DEBUG, "[touch]%s -> report not updated.\n", __func__); + goto exit_error; + } + u8_seq_no = p_u8_tp_status[POS_SEQ]; + p_u8_tp_status[POS_SEQ] = 0; + i32_ret = raydium_i2c_pda2_write(g_raydium_ts->client, + RAYDIUM_PDA2_TCH_RPT_STATUS_ADDR, p_u8_tp_status, 1); + if (i32_ret < 0) { + LOGD(LOG_ERR, "[touch]%s: write data failed: %d\n", __func__, i32_ret); + goto exit_error; + } + + u8_points_amount = p_u8_tp_status[POS_PT_AMOUNT]; + + if (u8_points_amount > MAX_TOUCH_NUM) + goto exit_error; + memcpy(p_u8_buf, &u8_read_buf[4], u8_points_amount * LEN_PT); + + raydium_touch_report(p_u8_buf, u8_points_amount); + +exit_error: + mutex_unlock(&g_raydium_ts->lock); + + return i32_ret; + +reset_error: + mutex_unlock(&g_raydium_ts->lock); +#ifdef ESD_SOLUTION_EN + u8_retry = 3; + while (u8_retry != 0) { + i32_ret = raydium_hw_reset_fun(g_raydium_ts->client); + LOGD(LOG_ERR, "[touch]%s: HW reset\n", __func__); + if (i32_ret < 0) { + msleep(100); + u8_retry--; + } else + break; + } +#endif + return i32_ret; +} + +static void raydium_work_handler(struct work_struct *work) +{ + int i32_ret = 0; + unsigned char u8_tp_status[MAX_TCH_STATUS_PACKET_SIZE] = {0}; + unsigned char u8_buf[MAX_REPORT_PACKET_SIZE] = {0}; + +#ifdef GESTURE_EN + unsigned char u8_i; + LOGD(LOG_DEBUG, "[touch]ts->blank:%x, g_u8_i2c_mode:%x\n", + g_raydium_ts->blank, g_u8_i2c_mode); + LOGD(LOG_DEBUG, "[touch]u8_tp_status:%x, g_raydium_ts->is_palm:%x\n", + u8_tp_status[POS_GES_STATUS], g_raydium_ts->is_palm); + + if (g_u8_i2c_mode == PDA2_MODE) { + i32_ret = raydium_read_touchdata(u8_tp_status, u8_buf); + if (i32_ret < 0) { + LOGD(LOG_ERR, "[touch]%s, read_touchdata error, ret:%d\n", + __func__, i32_ret); + return; + } + } +#if defined(CONFIG_PANEL_NOTIFIER) + if (g_raydium_ts->blank == DRM_PANEL_EVENT_BLANK_LP || + g_raydium_ts->blank == DRM_PANEL_EVENT_BLANK || g_raydium_ts->fb_state == FB_OFF) { +#else + if (g_raydium_ts->blank == DRM_PANEL_BLANK_LP || + g_raydium_ts->blank == DRM_PANEL_BLANK_POWERDOWN || g_raydium_ts->fb_state == FB_OFF) { +#endif + LOGD(LOG_DEBUG, "[touch] elseif u8_tp_status:%x\n", u8_tp_status[POS_GES_STATUS]); + /*need check small area*/ + if (u8_tp_status[POS_GES_STATUS] == RAD_WAKE_UP + && g_u8_wakeup_flag == false) { + /*if (u8_tp_status[POS_GES_STATUS] == 0) {*/ + input_report_key(g_raydium_ts->input_dev, KEY_WAKEUP, true); + usleep_range(9500, 10500); + input_sync(g_raydium_ts->input_dev); + + input_report_key(g_raydium_ts->input_dev, KEY_WAKEUP, false); + input_sync(g_raydium_ts->input_dev); + LOGD(LOG_DEBUG, "[touch]display wake up with g_u8_resetflag true\n"); + /*goto exit;*/ + } + } + /*when display on can use palm to suspend*/ +#if defined(CONFIG_PANEL_NOTIFIER) + else if (g_raydium_ts->blank == DRM_PANEL_EVENT_UNBLANK) { +#else + else if (g_raydium_ts->blank == DRM_PANEL_BLANK_UNBLANK) { +#endif + if (u8_tp_status[POS_GES_STATUS] == RAD_PALM_ENABLE) { + if (g_raydium_ts->is_palm == 0) { + /* release all touches*/ + for (u8_i = 0; u8_i < g_raydium_ts->u8_max_touchs; + u8_i++) { + input_mt_slot(g_raydium_ts->input_dev, + u8_i); + input_mt_report_slot_state( + g_raydium_ts->input_dev, + MT_TOOL_FINGER, false); + } + input_mt_report_pointer_emulation( + g_raydium_ts->input_dev, + false); + /*press sleep key*/ + input_report_key(g_raydium_ts->input_dev, + KEY_SLEEP, true); + input_sync(g_raydium_ts->input_dev); + + LOGD(LOG_INFO, "[touch]palm_status = %d.\n", + u8_tp_status[POS_GES_STATUS]); + + g_raydium_ts->is_palm = 1; + /*goto exit;*/ + } + } else if ((u8_tp_status[POS_GES_STATUS] + == RAD_PALM_DISABLE) + && (g_raydium_ts->is_palm == 1)) { + LOGD(LOG_INFO, "[touch]leave palm mode.\n"); + input_report_key(g_raydium_ts->input_dev, + KEY_SLEEP, false); + input_sync(g_raydium_ts->input_dev); + + /*raydium_irq_control(raydium_ts, DISABLE);*/ + g_raydium_ts->is_palm = 0; + /*goto exit;*/ + } + } +#else + if (g_u8_i2c_mode == PDA2_MODE) { + i32_ret = raydium_read_touchdata(u8_tp_status, u8_buf); + if (i32_ret < 0) { + LOGD(LOG_ERR, "[touch]%s, read_touchdata error, ret:%d\n", + __func__, i32_ret); + } + } +#endif +} + + +/*The raydium device will signal the host about TRIGGER_FALLING. + *Processed when the interrupt is asserted. + */ +static irqreturn_t raydium_ts_interrupt(int irq, void *dev_id) +{ + bool result = false; + + LOGD(LOG_DEBUG, "[touch]%s\n", __func__); + + /*For bootloader wrt/erase flash and software reset interrupt*/ + if ((g_u8_raydium_flag & ENG_MODE) != 0) { + LOGD(LOG_INFO, "[touch]RAD_ENG_MODE\n"); + g_u8_raydium_flag |= INT_FLAG; + } else { + if (!work_pending(&g_raydium_ts->work)) { + /* Clear interrupts*/ + if (g_raydium_ts->workqueue) { + result = queue_work(g_raydium_ts->workqueue, + &g_raydium_ts->work); + } + + if (!result) { + /*queue_work fail*/ + LOGD(LOG_ERR, "[touch]queue_work fail.\n"); + } + + + } else { + /*work pending*/ + mutex_lock(&g_raydium_ts->lock); + if (raydium_i2c_pda2_set_page(g_raydium_ts->client, + g_raydium_ts->is_suspend, + RAYDIUM_PDA2_PAGE_0) < 0) { + + LOGD(LOG_ERR, "[touch]%s: failed to set page in work_pending\n", + __func__); + } + mutex_unlock(&g_raydium_ts->lock); + + LOGD(LOG_DEBUG, "[touch]work_pending\n"); + } + } + return IRQ_HANDLED; +} + +static int raydium_check_i2c_ready(unsigned short *u16_i2c_data) +{ + unsigned char u8_buf[4]; + int i32_ret = ERROR; + + mutex_lock(&g_raydium_ts->lock); + + if (g_u8_i2c_mode == PDA2_MODE) { + i32_ret = handle_i2c_pda_read(g_raydium_ts->client, + RAYDIUM_CHK_I2C_CMD, u8_buf, 4); + if (i32_ret < 0) + goto exit_error; + + if (u8_buf[3] != 0xF3) { + LOGD(LOG_ERR, "[touch]PDA2 read i2c fail\n"); + g_u8_i2c_mode = PDA_MODE; + i32_ret = handle_i2c_pda_read(g_raydium_ts->client, + RAYDIUM_CHK_I2C_CMD, u8_buf, 4); + if (i32_ret < 0) + goto exit_error; + } + } else { + i32_ret = handle_i2c_pda_read(g_raydium_ts->client, + RAYDIUM_CHK_I2C_CMD, u8_buf, 4); + if (i32_ret < 0) + goto exit_error; + + } + + *u16_i2c_data = u8_buf[3] << 8 | u8_buf[2]; + + LOGD(LOG_DEBUG, "[touch]RAD check I2C : 0x%02X%02X\n", u8_buf[3], u8_buf[2]); + +exit_error: + mutex_unlock(&g_raydium_ts->lock); + return i32_ret; +} + +#if defined(CONFIG_PM) +static void raydium_ts_do_suspend(void) +{ + unsigned char u8_i = 0; + + LOGD(LOG_INFO, "[touch]%s.\n", __func__); + + if (g_u8_raw_data_type == 0) + g_u8_resetflag = false; + if (g_raydium_ts->is_suspend == 1 && (pm_suspend_via_firmware() == false)) { + LOGD(LOG_WARNING, "[touch]Already in suspend state\n"); + return; + } + /*#ifndef GESTURE_EN*/ + raydium_irq_control(DISABLE); + /*#endif*/ + + /*clear workqueue*/ + if (!cancel_work_sync(&g_raydium_ts->work)) + LOGD(LOG_DEBUG, "[touch]workqueue is empty!\n"); + + /* release all touches */ + for (u8_i = 0; u8_i < g_raydium_ts->u8_max_touchs; u8_i++) { + input_mt_slot(g_raydium_ts->input_dev, u8_i); + input_mt_report_slot_state(g_raydium_ts->input_dev, + MT_TOOL_FINGER, + false); + } + input_mt_report_pointer_emulation(g_raydium_ts->input_dev, false); + input_sync(g_raydium_ts->input_dev); + +#ifdef GESTURE_EN + if (pm_suspend_via_firmware() == false) + { + if (device_may_wakeup(&g_raydium_ts->client->dev)) { + LOGD(LOG_INFO, "[touch]Device may wakeup\n"); + if (!enable_irq_wake(g_raydium_ts->irq)) + g_raydium_ts->irq_wake = true; + + } else { + LOGD(LOG_INFO, "[touch]Device not wakeup\n"); + } + raydium_irq_control(ENABLE); + } +#endif + + g_raydium_ts->is_suspend = 1; +} + +static void raydium_ts_do_resume(void) +{ +#ifdef ESD_SOLUTION_EN + int i32_ret = 0; + unsigned char u8_retry = 0; +#endif + + LOGD(LOG_INFO, "[touch]%s, %d.\n", __func__, g_raydium_ts->is_suspend); + if (g_raydium_ts->is_suspend == 0) { + LOGD(LOG_WARNING, "[touch]Already in resume state\n"); + return; + } + + /* clear interrupts*/ + mutex_lock(&g_raydium_ts->lock); + if (raydium_i2c_pda2_set_page(g_raydium_ts->client, + g_raydium_ts->is_suspend, RAYDIUM_PDA2_PAGE_0) < 0) { + LOGD(LOG_ERR, "[touch]%s: failed to set page\n", __func__); + mutex_unlock(&g_raydium_ts->lock); + return; + } + mutex_unlock(&g_raydium_ts->lock); + + /* clear workqueue*/ + if (!cancel_work_sync(&g_raydium_ts->work)) + LOGD(LOG_DEBUG, "[ raydium ]workqueue is empty!\n"); +#ifdef ESD_SOLUTION_EN + if (g_u8_checkflag == true) { + i32_ret = raydium_esd_check(); + if (i32_ret < 0) { + u8_retry = 3; + while (u8_retry != 0) { + i32_ret = raydium_hw_reset_fun(g_raydium_ts->client); + if (i32_ret < 0) { + msleep(100); + u8_retry--; + } else + break; + } + + } + g_u8_checkflag = false; + } +#endif + raydium_irq_control(ENABLE); +#ifdef GESTURE_EN + if (device_may_wakeup(&g_raydium_ts->client->dev)) { + LOGD(LOG_INFO, "[touch]Device may wakeup\n"); + if (g_raydium_ts->irq_wake) { + disable_irq_wake(g_raydium_ts->irq); + g_raydium_ts->irq_wake = false; + } + } else + LOGD(LOG_INFO, "[touch]Device not wakeup\n"); +#endif + + g_raydium_ts->is_suspend = 0; +} + +static int raydium_ts_suspend(struct device *dev) +{ + raydium_ts_do_suspend(); + return 0; +} + +static int raydium_ts_resume(struct device *dev) +{ + raydium_ts_do_resume(); + return 0; +} + + +static const struct dev_pm_ops raydium_ts_pm_ops = { +#if (!defined(CONFIG_FB) && !defined(CONFIG_HAS_EARLYSUSPEND)) + .suspend = raydium_ts_suspend, + .resume = raydium_ts_resume, +#endif /*end of CONFIG_PM*/ +}; + +/*used for touch lock feature*/ +static int raydium_ts_open(struct input_dev *input_dev) +{ + //int i32_ret = 0; + + LOGD(LOG_DEBUG, "[touch]%s()+\n", __func__); + + LOGD(LOG_DEBUG, "[touch]ts->blank:%x\n", g_raydium_ts->blank); + + if (g_raydium_ts->is_sleep == 1) { + mutex_lock(&g_raydium_ts->lock); + if (gpio_is_valid(g_raydium_ts->rst_gpio)) { + + g_u8_resetflag = true; + gpio_set_value(g_raydium_ts->rst_gpio, 1); + gpio_set_value(g_raydium_ts->rst_gpio, 0); + msleep(RAYDIUM_RESET_INTERVAL_MSEC);/*5ms*/ + gpio_set_value(g_raydium_ts->rst_gpio, 1); + msleep(RAYDIUM_RESET_DELAY_MSEC);/*100ms*/ + g_u8_i2c_mode = PDA2_MODE; + } + mutex_unlock(&g_raydium_ts->lock); + raydium_irq_control(ENABLE); + g_raydium_ts->is_sleep = 0; + LOGD(LOG_INFO, "[touch]disable touch lock.\n"); + } + //return i32_ret; + return 0; +} + +static void raydium_ts_close(struct input_dev *input_dev) +{ + int i32_ret = 0; + unsigned char u8_i = 0; + unsigned char u8_wbuffer[1]; + + LOGD(LOG_INFO, "[touch]%s()+\n", __func__); + + if (g_raydium_ts->is_sleep == 1) { + LOGD(LOG_INFO, "[touch]touch lock already enabled.\n"); + return; + } + + raydium_irq_control(DISABLE); + + for (u8_i = 0; u8_i < g_raydium_ts->u8_max_touchs; u8_i++) { + input_mt_slot(g_raydium_ts->input_dev, u8_i); + input_mt_report_slot_state(g_raydium_ts->input_dev, + MT_TOOL_FINGER, + false); + } + input_mt_report_pointer_emulation(g_raydium_ts->input_dev, false); + input_sync(g_raydium_ts->input_dev); + mutex_lock(&g_raydium_ts->lock); + i32_ret = raydium_i2c_pda2_set_page(g_raydium_ts->client, + g_raydium_ts->is_suspend, RAYDIUM_PDA2_PAGE_0); + if (i32_ret < 0) { + LOGD(LOG_ERR, "[touch]ret:%d\n", i32_ret); + goto exit_i2c_error; + } + u8_wbuffer[0] = RAYDIUM_HOST_CMD_PWR_SLEEP; + i32_ret = raydium_i2c_pda2_write(g_raydium_ts->client, + RAYDIUM_PDA2_HOST_CMD_ADDR, + u8_wbuffer, + 1); + if (i32_ret < 0) { + LOGD(LOG_ERR, "[touch]ret:%d\n", i32_ret); + goto exit_i2c_error; + } + + mutex_unlock(&g_raydium_ts->lock); + g_raydium_ts->is_sleep = 1; + LOGD(LOG_INFO, "[touch]enable touch lock.\n"); + return; + +exit_i2c_error: + mutex_unlock(&g_raydium_ts->lock); + raydium_irq_control(ENABLE); +} + +#else +static int raydium_ts_suspend(struct device *dev) +{ + return 0; +} + +static int raydium_ts_resume(struct device *dev) +{ + return 0; +} +#endif /*end of CONFIG_FB*/ + +#if defined(CONFIG_PANEL_NOTIFIER) +static void panel_event_notifier_callback(enum panel_event_notifier_tag tag, + struct panel_event_notification *notification, void *client_data) +{ + + struct raydium_ts_data *g_raydium_ts = client_data; + if(!notification) + { + LOGD(LOG_INFO, "%s: Invalid notification\n", __func__); + return ; + } + + LOGD(LOG_INFO, "%s: DRM notifier called!\n", __func__); + + LOGD(LOG_INFO, "%s: DRM event:%d fb_state %d ", + __func__, notification->notif_type, g_raydium_ts->fb_state); + LOGD(LOG_INFO, "%s: DRM Power - %d- FB state %d ", + __func__, (notification->notif_type == DRM_PANEL_EVENT_UNBLANK)?"UP":"DOWN", g_raydium_ts->fb_state); + + if (notification->notif_type == DRM_PANEL_EVENT_UNBLANK) { + LOGD(LOG_INFO, "%s: UNBLANK!\n", __func__); + + if (notification->notif_data.early_trigger) { + LOGD(LOG_INFO, "%s: resume: event = %d not care\n", + __func__, notification->notif_type); + } else { + LOGD(LOG_INFO, "%s: Resume notifier called!\n", + __func__); +#ifdef GESTURE_EN + /* clear palm status */ + g_raydium_ts->is_palm = 0; +#endif + +#if defined(CONFIG_PM) + raydium_ts_resume(&g_raydium_ts->client->dev); +#endif + g_raydium_ts->fb_state = FB_ON; + LOGD(LOG_INFO, "%s: Resume notified!\n", __func__); + } + } else if (notification->notif_type == DRM_PANEL_EVENT_BLANK_LP || + notification->notif_type == DRM_PANEL_EVENT_BLANK) { + if (notification->notif_type == DRM_PANEL_EVENT_BLANK_LP) { + LOGD(LOG_INFO, "%s: LOWPOWER!\n", __func__); + } else { + LOGD(LOG_INFO, "%s: BLANK!\n", __func__); + } + if (notification->notif_data.early_trigger) { + LOGD(LOG_INFO, "%s: Suspend notifier called!\n", + __func__); +#ifdef GESTURE_EN + /* clear palm status */ + g_raydium_ts->is_palm = 0; +#endif + +#if defined(CONFIG_PM) + raydium_ts_suspend(&g_raydium_ts->client->dev); +#endif + g_raydium_ts->fb_state = FB_OFF; + LOGD(LOG_INFO, "%s: Suspend notified!\n", __func__); + } else { + LOGD(LOG_INFO, "%s: suspend: event = %d not care\n", + __func__, notification->notif_type); + } + } else { + LOGD(LOG_INFO, "%s: Undefined notif type (%d) do not need process\n", + __func__, notification->notif_type); + } + return ; +} + +/******************************************************************************* + * FUNCTION: raydium_setup_drm_notifier + * + * SUMMARY: Set up call back function into drm notifier. + * + * PARAMETERS: + * g_raydium_ts - pointer to core data + *******************************************************************************/ +static void raydium_setup_panel_notifier(struct raydium_ts_data *g_raydium_ts) +{ + void *cookie = NULL; + if (!active_panel) + LOGD(LOG_ERR, "[touch]%s: Active panel not registered!\n", __func__); + + cookie = panel_event_notifier_register(PANEL_EVENT_NOTIFICATION_PRIMARY, + PANEL_EVENT_NOTIFIER_CLIENT_PRIMARY_TOUCH, + active_panel,&panel_event_notifier_callback, g_raydium_ts); + g_raydium_ts->entry = cookie; +} + +#elif defined(CONFIG_DRM) +/******************************************************************************* + * FUNCTION: drm_notifier_callback + * + * SUMMARY: Call back function for DRM notifier to allow to call + * resume/suspend attention list. + * + * RETURN: + * 0 = success + * + * PARAMETERS: + * self - pointer to notifier_block structure + * event - event type of fb notifier + * data - pointer to fb_event structure + ******************************************************************************/ +static int drm_notifier_callback(struct notifier_block *self, + unsigned long event, void *data) +{ + struct raydium_ts_data *g_raydium_ts = + container_of(self, struct raydium_ts_data, fb_notif); + struct drm_panel_notifier *evdata = data; + int *blank; + + LOGD(LOG_INFO, "%s: DRM notifier called!\n", __func__); + + if (!evdata) + goto exit; + + if (!(event == DRM_PANEL_EARLY_EVENT_BLANK || + event == DRM_PANEL_EVENT_BLANK)) { + LOGD(LOG_INFO, "%s: Event(%lu) do not need process\n", + __func__, event); + goto exit; + } + + blank = evdata->data; + g_raydium_ts->blank = (*blank); + LOGD(LOG_INFO, "%s: DRM event:%lu,blank:%d fb_state %d ", + __func__, event, *blank, g_raydium_ts->fb_state); + LOGD(LOG_INFO, "%s: DRM Power - %s - FB state %d ", + __func__, (*blank == DRM_PANEL_BLANK_UNBLANK)?"UP":"DOWN", g_raydium_ts->fb_state); + + if (*blank == DRM_PANEL_BLANK_UNBLANK) { + LOGD(LOG_INFO, "%s: UNBLANK!\n", __func__); + + if (event == DRM_PANEL_EARLY_EVENT_BLANK) { + LOGD(LOG_INFO, "%s: resume: event = %lu, not care\n", + __func__, event); + } else if (event == DRM_PANEL_EVENT_BLANK) { + if (g_raydium_ts->fb_state != FB_ON) { + LOGD(LOG_INFO, "%s: Resume notifier called!\n", + __func__); +#ifdef GESTURE_EN + /* clear palm status */ + g_raydium_ts->is_palm = 0; +#endif + +#if defined(CONFIG_PM) + raydium_ts_resume(&g_raydium_ts->client->dev); +#endif + g_raydium_ts->fb_state = FB_ON; + LOGD(LOG_INFO, "%s: Resume notified!\n", __func__); + } + } + } else if (*blank == DRM_PANEL_BLANK_LP || *blank == DRM_PANEL_BLANK_POWERDOWN + || *blank == DRM_PANEL_BLANK_FPS_CHANGE) { + LOGD(LOG_INFO, "%s: LOWPOWER!\n", __func__); + if (event == DRM_PANEL_EARLY_EVENT_BLANK) { + if (g_raydium_ts->fb_state != FB_OFF) { + LOGD(LOG_INFO, "%s: Suspend notifier called!\n", + __func__); +#ifdef GESTURE_EN + /* clear palm status */ + g_raydium_ts->is_palm = 0; +#endif + +#if defined(CONFIG_PM) + raydium_ts_suspend(&g_raydium_ts->client->dev); +#endif + g_raydium_ts->fb_state = FB_OFF; + LOGD(LOG_INFO, "%s: Suspend notified!\n", __func__); + } + } else if (event == DRM_PANEL_EVENT_BLANK) { + LOGD(LOG_INFO, "%s: suspend: event = %lu, not care\n", + __func__, event); + } + } else { + LOGD(LOG_INFO, "%s: DRM BLANK(%d) do not need process\n", + __func__, *blank); + } +exit: + return 0; +} + +static void raydium_setup_drm_unregister_notifier(void) +{ + if (active_panel && drm_panel_notifier_unregister(active_panel, + &g_raydium_ts->fb_notif) < 0) + LOGD(LOG_ERR, "[touch]%s: DRM UnRegister notifier failed!\n", __func__); +} +/******************************************************************************* + * FUNCTION: raydium_setup_drm_notifier + * + * SUMMARY: Set up call back function into drm notifier. + * + * PARAMETERS: + * g_raydium_ts - pointer to core data + *******************************************************************************/ +static void raydium_setup_drm_notifier(struct raydium_ts_data *g_raydium_ts) +{ + g_raydium_ts->fb_state = FB_ON; + g_raydium_ts->fb_notif.notifier_call = drm_notifier_callback; + LOGD(LOG_INFO, "[touch]%s: Setting up drm notifier\n", __func__); + + if (!active_panel) + LOGD(LOG_ERR, "[touch]%s: Active panel not registered!\n", __func__); + + if (active_panel && drm_panel_notifier_register(active_panel, + &g_raydium_ts->fb_notif) < 0) + LOGD(LOG_ERR, "[touch]%s: Register notifier failed!\n", __func__); +} +#endif /*end of CONFIG_DRM*/ + +/******************************************************************************* + * FUNCTION: raydium_setup_drm_notifier + * + * SUMMARY: Set up call back function into fb notifier. + * + * PARAMETERS: + * g_raydium_ts - pointer to core data + *******************************************************************************/ +#if defined(CONFIG_FB) +static int fb_notifier_callback(struct notifier_block *self, + unsigned long event, + void *data) +{ + struct fb_event *evdata = data; + int *blank; + + if (evdata && evdata->data && event == FB_EVENT_BLANK && + g_raydium_ts && g_raydium_ts->client) { + blank = evdata->data; + g_raydium_ts->blank = (*blank); + switch (*blank) { + + /*screen on*/ + case FB_BLANK_UNBLANK: + LOGD(LOG_INFO, "[touch]FB_BLANK_UNBLANK\n"); +#ifdef GESTURE_EN + + /* clear palm status */ + + g_raydium_ts->is_palm = 0; +#endif + raydium_ts_resume(&g_raydium_ts->client->dev); + break; + + /*screen off*/ + case FB_BLANK_POWERDOWN: + LOGD(LOG_INFO, "[touch]FB_BLANK_POWERDOWN\n"); +#ifdef GESTURE_EN + + /* clear palm status */ + + g_raydium_ts->is_palm = 0; +#endif + raydium_ts_suspend(&g_raydium_ts->client->dev); + break; + + /*ambient mode*/ + case FB_BLANK_VSYNC_SUSPEND: + LOGD(LOG_INFO, "[touch]FB_BLANK_VSYNC_SUSPEND\n"); +#ifdef GESTURE_EN + + /* clear palm status */ + + g_raydium_ts->is_palm = 0; +#endif + + raydium_ts_suspend(&g_raydium_ts->client->dev); + break; + + default: + break; + } + } + + return 0; +} + +static void raydium_register_notifier(void) +{ + memset(&g_raydium_ts->fb_notif, 0, sizeof(g_raydium_ts->fb_notif)); + g_raydium_ts->fb_notif.notifier_call = fb_notifier_callback; + + /* register on the fb notifier and work with fb*/ + if (fb_register_client(&g_raydium_ts->fb_notif)) + LOGD(LOG_ERR, "[touch]register notifier failed\n"); +} + +static void raydium_unregister_notifier(void) +{ + fb_unregister_client(&g_raydium_ts->fb_notif); +} +#elif defined(CONFIG_HAS_EARLYSUSPEND) +static void raydium_ts_early_suspend(struct early_suspend *handler) +{ + + raydium_ts_do_suspend(); +} + +static void raydium_ts_late_resume(struct early_suspend *handler) +{ + raydium_ts_do_resume(); +} +#endif /*end of CONFIG_FB*/ + +#ifdef CONFIG_OF +static int raydium_get_dt_coords(struct device *dev, char *name, + struct raydium_ts_platform_data *pdata) +{ + u32 coords[COORDS_ARR_SIZE]; + struct property *prop; + struct device_node *np = dev->of_node; + int coords_size, rc; + + prop = of_find_property(np, name, NULL); + if (!prop) + return -EINVAL; + + if (!prop->value) + return -ENODATA; + + + coords_size = prop->length / sizeof(u32); + if (coords_size != COORDS_ARR_SIZE) { + LOGD(LOG_ERR, "[touch]invalid %s\n", name); + return -EINVAL; + } + + rc = of_property_read_u32_array(np, name, coords, coords_size); + if (rc && (rc != -EINVAL)) { + LOGD(LOG_ERR, "[touch]unable to read %s\n", name); + return rc; + } + + if (!strcmp(name, "raydium,display-coords")) { + pdata->x_min = coords[0]; + pdata->y_min = coords[1]; + pdata->x_max = coords[2]; + pdata->y_max = coords[3]; + } else { + LOGD(LOG_ERR, "[touch]unsupported property %s\n", name); + return -EINVAL; + } + + return 0; +} + +/******************************************************************************* + * FUNCTION: raydium_check_dsi_panel_dt + * + * SUMMARY: Get the DSI active panel information from dtsi + * + * RETURN: + * 0 = success + * !0 = fail + * + * PARAMETERS: + * np - pointer to device_node structure + * active_panel - name of active DSI panel + ******************************************************************************/ + +static int raydium_check_dsi_panel_dt(struct device_node *np, struct drm_panel **active_panel) +{ + int i = 0, rc = 0; + int count = 0; + struct device_node *node = NULL; + struct drm_panel *panel; + + count = of_count_phandle_with_args(np, "panel", NULL); + LOGD(LOG_INFO, "[touch]%s: Active panel count: %d\n", __func__, count); + if (count <= 0) + return 0; + + for (i = 0; i < count; i++) { + node = of_parse_phandle(np, "panel", i); + + if (node != NULL) + LOGD(LOG_ERR, "[touch]%s: Node handle successfully parsed !\n", __func__); + panel = of_drm_find_panel(node); + of_node_put(node); + + if (!IS_ERR(panel)) { + LOGD(LOG_ERR, "[touch]%s: Active panel selected !\n", __func__); + *active_panel = panel; + return 0; + } + } + LOGD(LOG_ERR, "[touch]%s: Active panel NOT selected !\n", __func__); + rc = PTR_ERR(panel); + return rc; +} + +static int raydium_parse_dt(struct device *dev, + struct raydium_ts_platform_data *pdata) +{ + struct device_node *np = dev->of_node; + struct drm_panel *active_panel = NULL; + int rc = 0; + u32 temp_val = 0; + + pdata->name = RAYDIUM_NAME; + + rc = raydium_get_dt_coords(dev, "raydium,display-coords", pdata); + if (rc) + return rc; + + + /* reset, irq gpio info */ + pdata->reset_gpio = of_get_named_gpio_flags(np, + "raydium,reset-gpio", + 0, + &pdata->reset_gpio_flags); + //if (pdata->reset_gpio < 0) + if ((s32)(pdata->reset_gpio) < 0) + return pdata->reset_gpio; + + + pdata->irq_gpio = of_get_named_gpio_flags(np, + "raydium,irq-gpio", + 0, + &pdata->irq_gpio_flags); + //if (pdata->irq_gpio < 0) + if ((s32)(pdata->irq_gpio) < 0) + return pdata->irq_gpio; + + rc = raydium_check_dsi_panel_dt(np, &active_panel); + LOGD(LOG_ERR, "[touch]%s: Panel not selected, rc=%d\n", __func__, rc); + if (rc) { + LOGD(LOG_ERR, "[touch]%s: Panel not selected, rc=%d\n", __func__, rc); + if (rc == -EPROBE_DEFER) { + LOGD(LOG_ERR, "[touch]%s: Probe defer selected, rc=%d\n", __func__, rc); + return rc; + } + } + pdata->active_panel = active_panel; + LOGD(LOG_ERR, "[touch]%s: Successful insert of active panel in core data\n", __func__); + + rc = of_property_read_u32(np, + "raydium,hard-reset-delay-ms", &temp_val); + if (!rc) + pdata->hard_rst_dly = temp_val; + else + return rc; + + + rc = of_property_read_u32(np, + "raydium,soft-reset-delay-ms", &temp_val); + if (!rc) + pdata->soft_rst_dly = temp_val; + else + return rc; + + + rc = of_property_read_u32(np, "raydium,num-max-touches", &temp_val); + if (!rc) + pdata->num_max_touches = temp_val; + else + return rc; +#ifdef FW_MAPPING_BYID_EN + rc = of_property_read_u32(np, "raydium,fw_id", &temp_val); + if (!rc) + pdata->fw_id = temp_val; + else + return rc; +#endif + return 0; +} +#else +static int raydium_parse_dt(struct device *dev, + struct raydium_ts_platform_data *pdata) +{ + return -ENODEV; +} +#endif /*end of CONFIG_OF*/ + +static void raydium_input_set(struct input_dev *input_dev) +{ + int ret = 0; + unsigned char i; + + input_dev->name = "raydium_ts";/*name need same with .idc*/ + input_dev->id.bustype = BUS_I2C; + input_dev->dev.parent = &g_raydium_ts->client->dev; + input_dev->open = raydium_ts_open;/*touch lock*/ + input_dev->close = raydium_ts_close; + input_set_drvdata(input_dev, g_raydium_ts); + + __set_bit(EV_KEY, input_dev->evbit); + __set_bit(EV_ABS, input_dev->evbit); + __set_bit(BTN_TOUCH, input_dev->keybit); + __set_bit(INPUT_PROP_DIRECT, input_dev->propbit); + LOGD(LOG_INFO, "[touch]set abs prarams x[%d], y[%d]\n", + g_raydium_ts->x_max, g_raydium_ts->y_max); + + /* Multitouch input params setup */ + input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, + g_raydium_ts->x_max, 0, 0); + input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, + g_raydium_ts->y_max, 0, 0); + input_set_abs_params(input_dev, ABS_MT_PRESSURE, 0, PRESS_MAX, 0, 0); + input_set_abs_params(input_dev, + ABS_MT_TOUCH_MAJOR, 0, WIDTH_MAX, 0, 0); + input_set_abs_params(input_dev, + ABS_MT_TOUCH_MINOR, 0, WIDTH_MAX, 0, 0); + + ret = input_mt_init_slots(input_dev, MAX_TOUCH_NUM, + INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED); + if (ret) + LOGD(LOG_ERR, "[touch]failed to initialize MT slots: %d\n", ret); + + for (i = 0; i < (MAX_TOUCH_NUM * 2); i++) + gst_slot[i] = gst_slot_init; + +} + +void touch_notify_glink_channel_state(bool state) +{ + LOGD(LOG_INFO, "%s:[touch] channel state: %d\n", __func__, state); +} + +void glink_touch_rx_msg(void *data, int len) +{ + int rc = 0; + + LOGD(LOG_INFO, "%s:[touch]TOUCH_RX_MSG Start:\n", __func__); + + if (len > TOUCH_GLINK_INTENT_SIZE) { + LOGD(LOG_ERR, "Invalid TOUCH glink intent size\n"); + return; + } + + /* check SLATE response */ + slate_ack_resp = *(uint32_t *)&data[8]; + LOGD(LOG_INFO, "[touch]slate_ack_resp :%0x\n", slate_ack_resp); + if (slate_ack_resp == 0x01) { + LOGD(LOG_INFO,"Bad SLATE response\n"); + rc = -EINVAL; + goto err_ret; + } + LOGD(LOG_INFO, "%s:[touch]TOUCH_RX_MSG End:\n", __func__); +err_ret: +return; +} + +static int raydium_set_resolution(void) +{ + unsigned char u8_buf[4]; + int i32_ret = -1; + unsigned int u32_x, u32_y; + + mutex_lock(&g_raydium_ts->lock); + + i32_ret = raydium_i2c_pda2_set_page(g_raydium_ts->client, + g_raydium_ts->is_suspend, + RAYDIUM_PDA2_PAGE_0); + if (i32_ret < 0) + goto exit_error; + + i32_ret = raydium_i2c_pda2_read(g_raydium_ts->client, + RAYDIUM_PDA2_DISPLAY_INFO_ADDR, + u8_buf, 4); + if (i32_ret < 0) + goto exit_error; + + u32_x = u8_buf[3] << 8 | u8_buf[2]; + u32_y = u8_buf[1] << 8 | u8_buf[0]; + + LOGD(LOG_DEBUG, "[touch]RAD display info x:%d, y:%d\n", u32_x, u32_y); + + if (u32_x > 100 && u32_y > 100 && + u32_x < 600 && u32_y < 600) { + g_raydium_ts->x_max = u32_x - 1; + g_raydium_ts->y_max = u32_y - 1; + } + +exit_error: + mutex_unlock(&g_raydium_ts->lock); + return i32_ret; +} +static int raydium_get_regulator(struct raydium_ts_data *cd, bool get) +{ + int rc; + + if (!get) { + rc = 0; + goto regulator_put; + } +#ifdef VDD_ANALOG_ENABLE + cd->vdd = regulator_get(&cd->client->dev, "vdd"); + if (IS_ERR(cd->vdd)) { + rc = PTR_ERR(cd->vdd); + dev_err(&cd->client->dev, + "Regulator get failed vdd rc=%d\n", rc); + goto regulator_put; + } +#endif + + cd->vcc_i2c = regulator_get(&cd->client->dev, "vcc_i2c"); + if (IS_ERR(cd->vcc_i2c)) { + rc = PTR_ERR(cd->vcc_i2c); + dev_err(&cd->client->dev, + "Regulator get failed vcc_i2c rc=%d\n", rc); + goto regulator_put; + } + + return 0; + +regulator_put: +#ifdef VDD_ANALOG_ENABLE + if (!IS_ERR(cd->vdd)) { + dev_err(&cd->client->dev, "Regulator put vdd\n"); + regulator_put(cd->vdd); + cd->vdd = NULL; + } +#endif + + if (!IS_ERR(cd->vcc_i2c)) { + dev_err(&cd->client->dev, "Regulator put vcc_i2c\n"); + regulator_put(cd->vcc_i2c); + cd->vcc_i2c = NULL; + } + + return rc; +} + +static int raydium_enable_regulator(struct raydium_ts_data *cd, bool en) +{ + int rc; + + if (!en) { + rc = 0; + goto disable_vcc_i2c_reg; + } +#ifdef VDD_ANALOG_ENABLE + if (cd->vdd) { + if (regulator_count_voltages(cd->vdd) > 0) { + rc = regulator_set_voltage(cd->vdd, VTG_MIN_UV, + VTG_MAX_UV); + if (rc) { + dev_err(&cd->client->dev, + "Regulator set_vtg failed vdd rc=%d\n", rc); + goto exit; + } + } + + rc = regulator_enable(cd->vdd); + if (rc) { + dev_err(&cd->client->dev, + "Regulator vdd enable failed rc=%d\n", rc); + goto exit; + } + } +#endif + + if (cd->vcc_i2c) { + if (regulator_count_voltages(cd->vcc_i2c) > 0) { + rc = regulator_set_voltage(cd->vcc_i2c, I2C_VTG_MIN_UV, + I2C_VTG_MAX_UV); + if (rc) { + dev_err(&cd->client->dev, + "Regulator set_vtg failed vcc_i2c rc=%d\n", rc); + goto disable_vdd_reg; + } + } + + rc = regulator_enable(cd->vcc_i2c); + if (rc) { + dev_err(&cd->client->dev, + "Regulator vcc_i2c enable failed rc=%d\n", rc); + goto disable_vdd_reg; + } + } + + return 0; + +disable_vcc_i2c_reg: + if (cd->vcc_i2c) { + if (regulator_count_voltages(cd->vcc_i2c) > 0) + regulator_set_voltage(cd->vcc_i2c, I2C_VTG_MIN_UV, + I2C_VTG_MAX_UV); + + regulator_disable(cd->vcc_i2c); + } + +disable_vdd_reg: +#ifdef VDD_ANALOG_ENABLE + if (cd->vdd) { + if (regulator_count_voltages(cd->vdd) > 0) + regulator_set_voltage(cd->vdd, VTG_MIN_UV, + VTG_MAX_UV); + + regulator_disable(cd->vdd); + } +#endif + +#ifdef VDD_ANALOG_ENABLE +exit: +#endif + return rc; +} + +static int raydium_ts_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct raydium_ts_platform_data *pdata = + (struct raydium_ts_platform_data *)client->dev.platform_data; + + struct input_dev *input_dev; + unsigned short u16_i2c_data; + int ret = 0; + + LOGD(LOG_INFO, "[touch] probe\n"); + + if (client->dev.of_node) { + pdata = devm_kzalloc(&client->dev, + sizeof(struct raydium_ts_platform_data), + GFP_KERNEL); + if (!pdata) { + LOGD(LOG_ERR, "[touch]failed to allocate memory\n"); + return -ENOMEM; + } + + ret = raydium_parse_dt(&client->dev, pdata); + if (ret) { + LOGD(LOG_ERR, "[touch]device tree parsing failed\n"); + goto parse_dt_failed; + } + } else + pdata = client->dev.platform_data; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + ret = -ENODEV; + goto exit_check_functionality_failed; + } + + g_raydium_ts = devm_kzalloc(&client->dev, + sizeof(struct raydium_ts_data), + GFP_KERNEL); + if (!g_raydium_ts) { + LOGD(LOG_ERR, "[touch]failed to allocate input driver data\n"); + return -ENOMEM; + } + + raydium_variable_init(); + + mutex_init(&g_raydium_ts->lock); + + i2c_set_clientdata(client, g_raydium_ts); + g_raydium_ts->irq_enabled = false; + g_raydium_ts->irq_wake = false; + + g_raydium_ts->irq_gpio = pdata->irq_gpio; + g_raydium_ts->rst_gpio = pdata->reset_gpio; + client->irq = g_raydium_ts->irq_gpio; + g_raydium_ts->u8_max_touchs = pdata->num_max_touches; + g_raydium_ts->client = client; + g_raydium_ts->x_max = pdata->x_max - 1; + g_raydium_ts->y_max = pdata->y_max - 1; + g_raydium_ts->is_suspend = 0; + g_raydium_ts->is_sleep = 0; + + +#ifdef GESTURE_EN + g_raydium_ts->is_palm = 0; +#endif + g_raydium_ts->fw_version = 0; + device_init_wakeup(&client->dev, 1); + +#ifdef MSM_NEW_VER + ret = raydium_ts_pinctrl_init(); + if (!ret && g_raydium_ts->ts_pinctrl) { + /* + * Pinctrl handle is optional. If pinctrl handle is found + * let pins to be configured in active state. If not + * found continue further without error. + */ + ret = pinctrl_select_state(g_raydium_ts->ts_pinctrl, + g_raydium_ts->pinctrl_state_active); + if (ret < 0) + LOGD(LOG_ERR, "[touch]failed to set pin to active state\n"); + } +#endif /*end of MSM_NEW_VER*/ + + ret = raydium_get_regulator(g_raydium_ts, true); + if (ret) { + dev_err(&client->dev, "Failed to get voltage regulators\n"); + goto error_alloc_data; + } + + ret = raydium_enable_regulator(g_raydium_ts, true); + if (ret) { + dev_err(&client->dev, "Failed to enable regulators: rc=%d\n", ret); + goto error_get_regulator; + } + ret = raydium_gpio_configure(true); + if (ret < 0) { + LOGD(LOG_ERR, "[touch]failed to configure the gpios\n"); + goto err_gpio_req; + } + /*modify dtsi to 360*/ + msleep(pdata->soft_rst_dly); + if (raydium_disable_i2c_deglitch() == ERROR) { + LOGD(LOG_ERR, "[touch]disable i2c deglicth NG!\r\n"); + ret = -ENODEV; + goto exit_check_i2c; + } + + /*print touch i2c ready*/ + ret = raydium_check_i2c_ready(&u16_i2c_data); + if (ret < 0 || (u16_i2c_data == 0)) { + LOGD(LOG_ERR, "[touch]Check I2C failed\n"); + ret = -EPROBE_DEFER; + goto exit_check_i2c; + } + + glink_touch_channel_init(&touch_notify_glink_channel_state, &glink_touch_rx_msg); + +#if defined(CONFIG_DRM) || defined(CONFIG_PANEL_NOTIFIER) + /* Setup active dsi panel */ + active_panel = pdata->active_panel; +#endif + /*input device initialization*/ + input_dev = input_allocate_device(); + if (!input_dev) { + ret = -ENOMEM; + LOGD(LOG_ERR, "[touch]failed to allocate input device\n"); + goto exit_input_dev_alloc_failed; + } + + raydium_set_resolution(); + + g_raydium_ts->input_dev = input_dev; + raydium_input_set(input_dev); + + ret = input_register_device(input_dev); + if (ret) { + LOGD(LOG_ERR, "[touch]failed to register input device: %s\n", + dev_name(&client->dev)); + goto exit_input_register_device_failed; + } + +#ifdef GESTURE_EN + input_set_capability(input_dev, EV_KEY, KEY_SLEEP); + input_set_capability(input_dev, EV_KEY, KEY_WAKEUP); +#endif + + /*suspend/resume routine*/ +#if defined(CONFIG_FB) + raydium_register_notifier(); +#elif defined(CONFIG_HAS_EARLYSUSPEND) + /*Early-suspend level*/ + g_raydium_ts->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; + g_raydium_ts->early_suspend.suspend = raydium_ts_early_suspend; + g_raydium_ts->early_suspend.resume = raydium_ts_late_resume; + register_early_suspend(&g_raydium_ts->early_suspend); +#endif/*end of CONFIG_FB*/ + +#ifdef CONFIG_RM_SYSFS_DEBUG + raydium_create_sysfs(client); +#endif/*end of CONFIG_RM_SYSFS_DEBUG*/ + + INIT_WORK(&g_raydium_ts->work, raydium_work_handler); + + g_raydium_ts->workqueue = create_singlethread_workqueue("raydium_ts"); + /*irq_gpio = 13 irqflags = 108*/ + LOGD(LOG_DEBUG, "[touch]pdata irq : %d\n", g_raydium_ts->irq_gpio); + LOGD(LOG_DEBUG, "[touch]client irq : %d, pdata flags : %d\n", + client->irq, pdata->irqflags); +#if defined(CONFIG_PANEL_NOTIFIER) + LOGD(LOG_DEBUG, "%s: Probe: Setup panel event notifier\n", __func__); + raydium_setup_panel_notifier(g_raydium_ts); +#elif defined(CONFIG_DRM) + LOGD(LOG_DEBUG, "%s: Probe: Setup drm notifier\n", __func__); + raydium_setup_drm_notifier(g_raydium_ts); +#endif/*end of CONFIG_DRM*/ + + g_raydium_ts->irq = gpio_to_irq(pdata->irq_gpio); + ret = request_threaded_irq(g_raydium_ts->irq, NULL, raydium_ts_interrupt, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + client->dev.driver->name, g_raydium_ts); + if (ret < 0) { + LOGD(LOG_ERR, "[touch]raydium_probe: request irq failed\n"); + goto exit_irq_request_failed; + } + + g_raydium_ts->irq_desc = irq_to_desc(g_raydium_ts->irq); + g_raydium_ts->irq_enabled = true; + g_raydium_ts->touch_offload = false; + + /*disable_irq then enable_irq for avoid Unbalanced enable for IRQ */ + + /*raydium_irq_control(ts, ENABLE);*/ + + LOGD(LOG_DEBUG, "[touch]RAD Touch driver ver :0x%X\n", g_u32_driver_version); + + /*fw update check*/ + ret = raydium_fw_update_init(u16_i2c_data); + if (ret < 0) { + LOGD(LOG_ERR, "[touch]FW update init failed\n"); + ret = -ENODEV; + goto exit_irq_request_failed; + } + + LOGD(LOG_INFO, "[touch] probe: done\n"); + return 0; + +exit_irq_request_failed: +#if defined(CONFIG_FB) + raydium_unregister_notifier(); +#elif defined(CONFIG_PANEL_NOTIFIER) + panel_event_notifier_unregister(g_raydium_ts->entry); +#elif defined(CONFIG_DRM) + raydium_setup_drm_unregister_notifier(); +#endif + + cancel_work_sync(&g_raydium_ts->work); + input_unregister_device(input_dev); + g_raydium_ts->input_dev = NULL; + +exit_input_register_device_failed: + if (g_raydium_ts->input_dev) + input_free_device(input_dev); + g_raydium_ts->input_dev = NULL; + +exit_input_dev_alloc_failed: +exit_check_i2c: +#ifdef MSM_NEW_VER + if (g_raydium_ts->ts_pinctrl) { + if (IS_ERR_OR_NULL(g_raydium_ts->pinctrl_state_release)) { + devm_pinctrl_put(g_raydium_ts->ts_pinctrl); + g_raydium_ts->ts_pinctrl = NULL; + } else { + if (pinctrl_select_state(g_raydium_ts->ts_pinctrl, + g_raydium_ts->pinctrl_state_release)) + LOGD(LOG_ERR, + "[touch]pinctrl_select_state failed\n"); + } + } +#endif/*end of MSM_NEW_VER*/ + + if (gpio_is_valid(pdata->reset_gpio)) + gpio_free(pdata->reset_gpio); + + if (gpio_is_valid(pdata->irq_gpio)) + gpio_free(pdata->irq_gpio); + +err_gpio_req: + raydium_enable_regulator(g_raydium_ts, false); + +error_get_regulator: + raydium_get_regulator(g_raydium_ts, false); +error_alloc_data: +parse_dt_failed: +exit_check_functionality_failed: + return ret; + +} + + +void raydium_ts_shutdown(struct i2c_client *client) +{ + + LOGD(LOG_INFO, "[touch] %s: start\n", __func__); + + cancel_work_sync(&g_raydium_ts->work); + if (g_raydium_ts->workqueue) { + destroy_workqueue(g_raydium_ts->workqueue); + g_raydium_ts->workqueue = NULL; + } +#if defined(CONFIG_FB) + raydium_unregister_notifier(); +#elif defined(CONFIG_HAS_EARLYSUSPEND) + unregister_early_suspend(&g_raydium_ts->early_suspend); +#elif defined(CONFIG_PANEL_NOTIFIER) +if (active_panel) + panel_event_notifier_unregister(g_raydium_ts->entry); +#elif defined(CONFIG_DRM) +if (active_panel) + drm_panel_notifier_unregister(active_panel, &g_raydium_ts->fb_notif); +#endif/*end of CONFIG_FB*/ + input_unregister_device(g_raydium_ts->input_dev); + g_raydium_ts->input_dev = NULL; + gpio_free(g_raydium_ts->rst_gpio); + +#ifdef CONFIG_RM_SYSFS_DEBUG + raydium_release_sysfs(client); +#endif /*end of CONFIG_RM_SYSFS_DEBUG*/ + + disable_irq(g_raydium_ts->irq); + free_irq(client->irq, g_raydium_ts); + + if (gpio_is_valid(g_raydium_ts->rst_gpio)) + gpio_free(g_raydium_ts->rst_gpio); + + if (gpio_is_valid(g_raydium_ts->irq_gpio)) + gpio_free(g_raydium_ts->irq_gpio); + + + raydium_enable_regulator(g_raydium_ts, false); + raydium_get_regulator(g_raydium_ts, false); + + devm_kfree(&client->dev, g_raydium_ts); + g_raydium_ts = NULL; + + i2c_set_clientdata(client, NULL); + LOGD(LOG_INFO, "[touch] %s: done\n", __func__); +} + +static int raydium_ts_remove(struct i2c_client *client) +{ + + LOGD(LOG_INFO, "[touch] %s: start\n", __func__); + + cancel_work_sync(&g_raydium_ts->work); + if (g_raydium_ts->workqueue) { + destroy_workqueue(g_raydium_ts->workqueue); + g_raydium_ts->workqueue = NULL; + } +#if defined(CONFIG_FB) + raydium_unregister_notifier(); +#elif defined(CONFIG_HAS_EARLYSUSPEND) + unregister_early_suspend(&g_raydium_ts->early_suspend); +#elif defined(CONFIG_PANEL_NOTIFIER) +if (active_panel) + panel_event_notifier_unregister(g_raydium_ts->entry); +#elif defined(CONFIG_DRM) +if (active_panel) + drm_panel_notifier_unregister(active_panel, &g_raydium_ts->fb_notif); +#endif/*end of CONFIG_FB*/ + input_unregister_device(g_raydium_ts->input_dev); + g_raydium_ts->input_dev = NULL; + gpio_free(g_raydium_ts->rst_gpio); + +#ifdef CONFIG_RM_SYSFS_DEBUG + raydium_release_sysfs(client); +#endif /*end of CONFIG_RM_SYSFS_DEBUG*/ + + disable_irq(g_raydium_ts->irq); + free_irq(client->irq, g_raydium_ts); + + if (gpio_is_valid(g_raydium_ts->rst_gpio)) + gpio_free(g_raydium_ts->rst_gpio); + + if (gpio_is_valid(g_raydium_ts->irq_gpio)) + gpio_free(g_raydium_ts->irq_gpio); + + + raydium_enable_regulator(g_raydium_ts, false); + raydium_get_regulator(g_raydium_ts, false); + + devm_kfree(&client->dev, g_raydium_ts); + g_raydium_ts = NULL; + + i2c_set_clientdata(client, NULL); + LOGD(LOG_INFO, "[touch] %s: done\n", __func__); + return 0; +} + +static const struct i2c_device_id raydium_ts_id[] = { + {RAYDIUM_NAME, 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, raydium_ts_id); + +#ifdef CONFIG_OF +static const struct of_device_id raydium_match_table[] = { + { .compatible = "raydium,raydium-ts",}, + { }, +}; +#else +#define raydium_match_table NULL +#endif/*end of CONFIG_OF*/ + +static struct i2c_driver raydium_ts_driver = { + .probe = raydium_ts_probe, + .remove = raydium_ts_remove, + .shutdown = raydium_ts_shutdown, + .id_table = raydium_ts_id, + .driver = { + .name = RAYDIUM_NAME, + .owner = THIS_MODULE, + .of_match_table = raydium_match_table, +#if defined(CONFIG_PM) + .pm = &raydium_ts_pm_ops, +#endif/*end of CONFIG_PM*/ + }, +}; + +static int __init raydium_ts_init(void) +{ + int ret; + + ret = i2c_add_driver(&raydium_ts_driver); + return ret; +} + +static void __exit raydium_ts_exit(void) +{ + i2c_del_driver(&raydium_ts_driver); +} + +module_init(raydium_ts_init); +module_exit(raydium_ts_exit); + +MODULE_AUTHOR(""); +MODULE_DESCRIPTION("Raydium TouchScreen driver"); +MODULE_LICENSE("GPL"); diff --git a/qcom/opensource/touch-drivers/raydium/raydium_driver.h b/qcom/opensource/touch-drivers/raydium/raydium_driver.h new file mode 100644 index 0000000000..78a7052e47 --- /dev/null +++ b/qcom/opensource/touch-drivers/raydium/raydium_driver.h @@ -0,0 +1,483 @@ +/* raydium_driver.h + * + * Raydium TouchScreen driver. + * + * Copyright (c) 2021 Raydium tech Ltd. + * + * 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. + * + */ + + +#ifndef __LINUX_RAYDIUM_H +#define __LINUX_RAYDIUM_H +#define RAYDIUM_NAME "raydium_ts" +#define COORDS_ARR_SIZE 4 +#define I2C_VTG_MIN_UV 1800000 +#define I2C_VTG_MAX_UV 1800000 +#ifdef VDD_ANALOG_ENABLE +#define VTG_MIN_UV 2800000 +#define VTG_MAX_UV 2800000 +#endif +#define RAD_MAIN_VERSION 0x01 +#define RAD_MINOR_VERSION 0x01 +#define RAD_CUSTOMER_VERSION 0x0100 + +#if defined(CONFIG_TOUCHSCREEN_RM_TS) +/* IC timing control arguments */ +#define RAYDIUM_POWERON_DELAY_USEC 500 +#define RAYDIUM_RESET_INTERVAL_MSEC 5 +#define RAYDIUM_RESET_RESTORE_USEC 200 +#define RAYDIUM_RESET_DELAY_MSEC 100 +#define RAYDIUM_RESET_INTERVAL_10MSEC 10 + +/* I2C bus slave address(ID) */ +#define RAYDIUM_I2C_EID (0x5A) +#define RAYDIUM_I2C_NID (0x39) +#define RAYDIUM_I2C_PDA_CMD 0x66 + +/* I2C R/W configuration literal */ +#define RAYDIUM_I2C_WRITE I2C_SMBUS_WRITE +#define RAYDIUM_I2C_READ I2C_SMBUS_READ +#define SYN_I2C_RETRY_TIMES 1 +#define MAX_WRITE_PACKET_SIZE 128 +#define MAX_READ_PACKET_SIZE 128 + +/* PDA address and bit definition*/ +#define RAD_READ_FT_DATA_CMD 0x2000019C +/* 1byte, disable:0x00 ; enable:0x20*/ +#define RAD_GESTURE_STATE_CMD 0x200005F4 +#define RAD_GESTURE_DISABLE 0x00 +#define RAD_GESTURE_ENABLE 0x20 +/* 4bytes, [0]:ready ; [1]:type ; [2]:direction*/ +#define RAD_GESTURE_RESULT_CMD 0x200005F0 +#define RAD_ENABLE_PDA2 0x04 +#define RAD_ENABLE_SI2 0x02 + +/* PDA literal */ +#define MASK_8BIT 0xFF +#define RAD_I2C_PDA_ADDRESS_LENGTH 4 +#define PDA_MODE 0x01 +#define PDA2_MODE 0x02 +#define RAD_I2C_PDA_MODE_DISABLE 0x00 +#define RAD_I2C_PDA_MODE_ENABLE 0x80 +/* Using byte mode due to data might be not word-aligment */ +#define RAD_I2C_PDA_MODE_WORD_MODE 0x40 +#define RAD_I2C_PDA_2_MODE_DISABLE 0x20 + +#define I2C_PDA2_BYTE_MODE 0x03 +#define I2C_PDA2_WORD_MODE 0x43 + +#define RAD_PALM_DISABLE 0x00 +#define RAD_PALM_ENABLE 0x01 +#define RAD_WAKE_UP 0x02 +#define RAYDIUM_TEST_FW 0x80 +#define RAYDIUM_TEST_PARA 0x40 +#define RAYDIUM_BOOTLOADER 0x20 +#define RAYDIUM_FIRMWARE 0x10 +#define RAYDIUM_PARA 0x08 +#define RAYDIUM_COMP 0x04 +#define RAYDIUM_BASELINE 0x02 +#define RAYDIUM_INIT 0x01 +#define FAIL 0 +#define ERROR -1 +#define SUCCESS 1 +#define DISABLE 0 +#define ENABLE 1 + +/* PDA2 setting */ +/* Page 0 ~ Page A */ +#define MAX_PAGE_AMOUNT 11 + +/* PDA2 address and setting definition*/ +#define RAYDIUM_PDA2_TCH_RPT_STATUS_ADDR 0x00 /* only in Page 0 */ +#define RAYDIUM_PDA2_TCH_RPT_ADDR 0x01 /* only in Page 0 */ +#define RAYDIUM_PDA2_HOST_CMD_ADDR 0x02 /* only in Page 0 */ +#define RAYDIUM_PDA2_PALM_AREA_ADDR 0x03 /* only in Page 0 */ +#define RAYDIUM_PDA2_GESTURE_RPT_ADDR 0x04 /* only in Page 0 */ +#define RAYDIUM_PDA2_PALM_STATUS_ADDR 0x05 /* only in Page 0 */ +#define RAYDIUM_PDA2_FW_VERSION_ADDR 0x06 /* only in Page 0 */ +#define RAYDIUM_PDA2_PANEL_VERSION_ADDR 0x07 /* only in Page 0 */ +#define RAYDIUM_PDA2_DISPLAY_INFO_ADDR 0x08 /* only in Page 0 */ +#define RAYDIUM_PDA2_PDA_CFG_ADDR 0x09 /* only in Page 0 */ +#define RAYDIUM_PDA2_RAWDATA_ADDR 0x0B /* only in Page 0 */ +/* Page 0 ~ Page 9 will be directed to Page 0 */ +#define RAYDIUM_PDA2_PAGE_ADDR 0x0A +#define RAYDIUM_PDA2_PAGE_0 0x00 +/* temporary switch to PDA once */ +#define RAYDIUM_PDA2_ENABLE_PDA 0x0A +/* permanently switch to PDA mode */ +#define RAYDIUM_PDA2_2_PDA (MAX_PAGE_AMOUNT + 2) + +/* Raydium host cmd */ +#define RAYDIUM_HOST_CMD_NO_OP 0x00 +#define RAYDIUM_HOST_CMD_PWR_SLEEP 0x30 +#define RAYDIUM_HOST_CMD_DISPLAY_MODE 0x33 +#define RAYDIUM_HOST_CMD_CALIBRATION 0x5C +#define RAYDIUM_HOST_CMD_TP_MODE 0x60 +#define RAYDIUM_HOST_CMD_FT_MODE 0x61 + +/* Raydium Register define */ +#define RAYDIUM_PDA_BOOTVERSION 0x00000080 +#define RAYDIUM_PDA_FIRMWAREADDR 0x00000800 +#define RAYDIUM_PDA_PARAADDR 0x00007B00 +#define RAYDIUM_PDA_FIRMWARELENGTH 0x00007300 +#define RAYDIUM_PDA_PARALENGTH 0x00000178 +#define RAYDIUM_PDA_CRCLENGTH 0x00007474 +#define RAYDIUM_PDA_SYNCDATA 0x20000200 +#define RAYDIUM_PDA_BOOTENG1 0x20000204 +#define RAYDIUM_PDA_BOOTENG2 0x20000208 +#define RAYDIUM_PDA_BOOTENG3 0x2000020C +#define RAYDIUM_PDA_BOOTENG4 0x20000210 +#define RAYDIUM_PDA_BOOTSTATE 0x20000214 +#define RAYDIUM_PDA_BOOTMODE 0x20000218 +#define RAYDIUM_PDA_BLKEN 0x40000000 +#define RAYDIUM_PDA_BLKRST 0x40000004 +#define RAYDIUM_PDA_MISCIER 0x40000014 +#define RAYDIUM_PDA_I2CENG 0x50000610 +#define RAYDIUM_PDA_FLASHPRO 0x50000624 +#define RAYDIUM_PDA_I2CREG 0x50000628 +#define RAYDIUM_PDA_PRAMLOCK 0x50000900 +#define RAYDIUM_PDA_PRAMTYPE 0x50000904 +#define RAYDIUM_PDA_PRAMADDR 0x50000908 +#define RAYDIUM_PDA_PRAMLENGTH 0x5000090C +#define RAYDIUM_PDA_FLHADDR 0x50000910 +#define RAYDIUM_PDA_FLHCTL 0x50000914 +#define RAYDIUM_PDA_BOOTREG 0x50000918 +#define RAYDIUM_PDA_FLKEY1 0x50000934 +#define RAYDIUM_PDA_FLKEY2 0x50000938 +#define RAYDIUM_PDA_FLDATA 0x5000093C +#define RAYDIUM_PDA_PRGCHKSUMENG 0x5000094C +#define RAYDIUM_PDA_PRGCHKSUMADDR 0x50000974 +#define RAYDIUM_PDA_PRGCHKSUMRESULT 0x50000978 +#define RAYDIUM_CHK_I2C_CMD 0x500009BC +#define RAYDIUM_REG_GPIO_DEGLITCH 0x50000E1C + +#define I2CTB_LOCK (0x00000001<<6) +#define BOTLR_LOCK (0x00000001<<5) +#define USEFW_LOCK (0x00000001<<4) +#define CONFIG_LOCK (0x00000001<<3) +#define COMP_LOCK (0x00000001<<2) +#define BASEL_LOCK (0x00000001<<1) +#define INICO_LOCK (0x00000001<<0) + +/* ['h5000_0904], [32'h0000_0000], Program RAM store type, PRAM_STORE_TYPE */ +#define BOTLR_AREA (0x00000001<<5) +#define USEFW_AREA (0x00000001<<4) +#define CONFIG_AREA (0x00000001<<3) +#define COMP_AREA (0x00000001<<2) +#define BASEL_AREA (0x00000001<<1) +#define INICO_AREA (0x00000001<<0) + + +/* PDA2 literal */ +/* entry byte + target page byte */ +#define RAYDIUM_I2C_PDA2_PAGE_LENGTH 2 + + +/* Touch report */ +#define MAX_TOUCH_NUM 2 +#define MAX_REPORT_PACKET_SIZE 35 +#define MAX_TCH_STATUS_PACKET_SIZE 4 +#define PRESS_MAX 0xFFFF +#define WIDTH_MAX 0xFFFF +#define BYTE_SHIFT 8 +#define TOUCH_PRESS 0 +#define TOUCH_RELEASE 1 +#define TOUCH_MOVE 2 +#define TOUCH_COVER 3 +#define TOUCH_SHORTCLICK 4 + +/* FW update literal */ +#define RAYDIUM_FW_BIN_PATH_LENGTH 256 + +#define RAD_BOOT_3X_SIZE 0x800 +#define RAD_INIT_3X_SIZE 0x80 +#define RAD_FW_3X_SIZE 0x7300 +#define RAD_PARA_3X_SIZE 0x174 +#define RAD_TESTFW_3X_SIZE (RAD_FW_3X_SIZE + RAD_PARA_3X_SIZE + 4) +#define RAD_ALLFW_3X_SIZE 0xF170 + +#define RAD_CMD_UPDATE_BIN 0x80 +#define RAD_CMD_UPDATE_END 0x81 +#define RAD_CMD_BURN_FINISH 0x82 + +/* FT APK literal */ +#define RAD_HOST_CMD_POS 0x00 +#define RAD_FT_CMD_POS 0x01 +#define RAD_FT_CMD_LENGTH 0x02 + +/* FT APK data type */ +#define RAYDIUM_FT_UPDATE 0x00 + +/*Raydium system flag*/ +#define INT_FLAG 0x01 +#define ENG_MODE 0x02 +#define NORMAL_MODE 0x00 +/* define display mode */ +#define ACTIVE_MODE 0x00 +#define AMBIENT_MODE 0x01 +#define SLEEP_MODE 0x02 + +/* Enable sysfs */ +#define CONFIG_RM_SYSFS_DEBUG + +/* Gesture switch */ +#define GESTURE_EN + +/* Enable FW update */ +#define FW_UPDATE_EN +/* #define FW_MAPPING_EN */ +#define MSM_NEW_VER + +/* enable ESD */ +/* #define ESD_SOLUTION_EN */ + +#define RAD_SELFTEST +#define PARA_FW_VERSION_OFFSET 4 + +#define ENABLE_FW_LOADER 1 +#define FW_NAME "RM6D030_v0.1.bin" + +#define PINCTRL_STATE_ACTIVE "pmx_ts_active" +#define PINCTRL_STATE_SUSPEND "pmx_ts_suspend" +#define PINCTRL_STATE_RELEASE "pmx_ts_release" + +/* Power Management Macros Enablement */ + +#ifndef CONFIG_PM +#define CONFIG_PM +#endif + + +#ifndef CONFIG_DRM +#define CONFIG_DRM +#endif + +#if IS_ENABLED(CONFIG_QCOM_PANEL_EVENT_NOTIFIER) +#define CONFIG_PANEL_NOTIFIER +#endif + +#include +#include +#include +#ifdef CONFIG_HAS_EARLYSUSPEND +#include +#elif defined(CONFIG_DRM) || defined(CONFIG_PANEL_NOTIFIER) +#include +#endif + +extern uint32_t slate_ack_resp; + +enum raydium_fb_state { + FB_ON, + FB_OFF, +}; + + +struct raydium_ts_data { + unsigned int irq; + unsigned int irq_gpio; + unsigned int rst_gpio; + unsigned int x_max; + unsigned int y_max; + unsigned int x_pos[2]; + unsigned int y_pos[2]; + unsigned int pressure; + unsigned int is_suspend; + unsigned int is_sleep; +#ifdef GESTURE_EN + unsigned int is_palm; +#endif + unsigned char u8_max_touchs; + + struct i2c_client *client; + struct input_dev *input_dev; + struct mutex lock; + struct work_struct work; + struct workqueue_struct *workqueue; + struct irq_desc *irq_desc; + bool irq_enabled; + bool irq_wake; +#if defined(CONFIG_PANEL_NOTIFIER) + struct panel_event_notifier_entry *entry; + int blank; + enum raydium_fb_state fb_state; +#elif defined(CONFIG_FB) || defined(CONFIG_DRM) || defined(CONFIG_PANEL_NOTIFIER) + struct notifier_block fb_notif; + int blank; + enum raydium_fb_state fb_state; +#elif defined(CONFIG_HAS_EARLYSUSPEND) + struct early_suspend early_suspend; +#endif /*end of CONFIG_FB*/ + + /*struct regulator *vdd;*/ +#ifdef VDD_ANALOG_ENABLE + struct regulator *vdd; +#endif + struct regulator *vcc_i2c; + unsigned int fw_version; + unsigned short id; + char *vcc_name; +#ifdef MSM_NEW_VER + struct pinctrl *ts_pinctrl; + struct pinctrl_state *pinctrl_state_active; + struct pinctrl_state *pinctrl_state_suspend; + struct pinctrl_state *pinctrl_state_release; +#endif /*end of MSM_NEW_VER*/ + bool touch_offload; + + +}; +struct raydium_platform_data { + char *vdd_name; + int irq_gpio_number; + int reset_gpio_number; + int x_max; + int y_max; +}; + +struct raydium_ts_platform_data { + char *name; + u32 irqflags; + u32 irq_gpio; + u32 irq_gpio_flags; + u32 reset_gpio; + u32 reset_gpio_flags; + u32 x_max; + u32 y_max; + u32 x_min; + u32 y_min; + u32 hard_rst_dly; + u32 soft_rst_dly; + u32 num_max_touches; + u32 fw_id; + struct drm_panel *active_panel; +}; + +/* TODO: Using struct+memcpy instead of array+offset*/ +enum raydium_pt_report_status { + POS_SEQ = 0,/*1:touch, 0:no touch*/ + POS_PT_AMOUNT, + POS_GES_STATUS, + POS_FW_STATE, +}; + +enum raydium_pt_report_idx { + POS_PT_ID = 0, + POS_X_L, + POS_X_H, + POS_Y_L, + POS_Y_H, + POS_PRESSURE_L, + POS_PRESSURE_H, + POS_WX_L, + POS_WX_H, + POS_WY_L, + POS_WY_H, + LEN_PT = 11 +}; + +enum raydium_log_level { + LOG_NONE = 0, + LOG_ALERT, + LOG_ERR, + LOG_WARNING, + LOG_INFO, + LOG_DEBUG = 5 +}; +extern int raydium_read_touchdata(unsigned char *tp_status, unsigned char *buf); +extern int raydium_mem_table_setting(void); +extern int wait_fw_state(struct i2c_client *client, unsigned int u32_addr, + unsigned int u32_state, unsigned long u32_delay_us, + unsigned short u16_retry); +extern int wait_irq_state(struct i2c_client *client, + unsigned int u32_retry_time, + unsigned int u32_delay_us); +extern void raydium_irq_control(bool enable); + +extern int raydium_i2c_mode_control(struct i2c_client *client, + unsigned char u8_mode); +extern int raydium_i2c_pda_read(struct i2c_client *client, + unsigned int u32_addr, unsigned char *u8_r_data, + unsigned short u16_length); +extern int raydium_i2c_pda_write(struct i2c_client *client, + unsigned int u32_addr, unsigned char *u8_w_data, + unsigned short u16_length); +extern int handle_i2c_pda_write(struct i2c_client *client, + unsigned int u32_addr, unsigned char *u8_w_data, + unsigned short u16_length); +extern int handle_i2c_pda_read(struct i2c_client *client, + unsigned int u32_addr, unsigned char *u8_r_data, + unsigned short u16_length); +extern int raydium_i2c_pda2_read(struct i2c_client *client, + unsigned char u8_addr, + unsigned char *u8_r_data, + unsigned short u16_length); +extern int raydium_i2c_pda2_write(struct i2c_client *client, + unsigned char u8_addr, + unsigned char *u8_w_data, + unsigned short u16_length); +extern int raydium_i2c_pda2_set_page(struct i2c_client *client, + unsigned int is_suspend, + unsigned char u8_page); +extern int raydium_i2c_write_pda_via_pda2(struct i2c_client *client, + unsigned int u32_addr, unsigned char *u8_w_data, + unsigned short u16_length); +extern int raydium_i2c_read_pda_via_pda2(struct i2c_client *client, + unsigned int u32_addr, unsigned char *u8_r_data, + unsigned short u16_length); +extern unsigned char raydium_disable_i2c_deglitch(void); +extern unsigned char raydium_selftest_stop_mcu(struct i2c_client *client); +extern int raydium_burn_comp(struct i2c_client *client); +extern int raydium_burn_fw(struct i2c_client *client); + +extern int raydium_load_test_fw(struct i2c_client *client); +extern int raydium_fw_update_init(unsigned short u16_i2c_data); +extern int raydium_fw_update_check(unsigned int u32_check_version); +extern int raydium_i2c_pda_set_address(unsigned int u32_address, + unsigned char u8_mode); +extern void raydium_mem_table_init(unsigned short u16_id); +extern int raydium_id_init(unsigned char u8_type); + +#ifdef RAD_SELFTEST +extern int raydium_do_selftest(struct raydium_ts_data *ts); +#endif +int raydium_esd_check(void); + +extern struct attribute *raydium_attributes[]; +extern const struct attribute_group raydium_attr_group; + +extern unsigned char g_u8_raydium_flag; +extern unsigned char g_u8_addr; +extern unsigned char g_u8_i2c_mode; +extern unsigned char g_u8_upgrade_type; +extern unsigned char g_u8_raw_data_type; +extern unsigned int g_u32_raw_data_len; /* 128 bytes*/ +extern unsigned int g_u32_length; +extern unsigned long g_u32_addr; +extern unsigned char *g_rad_fw_image, *g_rad_init_image; +extern unsigned char *g_rad_boot_image, *g_rad_para_image; +extern unsigned char *g_rad_testfw_image, *g_rad_testpara_image; +extern unsigned char g_u8_table_setting, g_u8_table_init; +extern unsigned int g_u32_driver_version; +extern unsigned char g_u8_resetflag; +extern struct raydium_ts_data *g_raydium_ts; +extern unsigned char g_u8_log_level; + +#define LOGD(a, fmt, ...) {\ + if (a <= g_u8_log_level) {\ + pr_info(pr_fmt(fmt), ##__VA_ARGS__);\ + } \ +} +#endif +#endif /*__LINUX_RAYDIUM_H*/ + diff --git a/qcom/opensource/touch-drivers/raydium/raydium_fw_update.c b/qcom/opensource/touch-drivers/raydium/raydium_fw_update.c new file mode 100644 index 0000000000..f5e68094bd --- /dev/null +++ b/qcom/opensource/touch-drivers/raydium/raydium_fw_update.c @@ -0,0 +1,1239 @@ +/* raydium_fw_update.c + * + * Raydium TouchScreen driver. + * + * Copyright (c) 2021 Raydium tech Ltd. + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "raydium_driver.h" +#if !ENABLE_FW_LOADER +#include "rad_fw_image_30.h" +#endif +#if defined(FW_MAPPING_EN) +#include "rad_fw_image_31.h" +#endif + +void raydium_mem_table_init(unsigned short u16_id) +{ + LOGD(LOG_INFO, "[touch]Raydium table init 0x%x\n", u16_id); + + g_rad_boot_image = kzalloc(RAD_BOOT_3X_SIZE, GFP_KERNEL); + g_rad_init_image = kzalloc(RAD_INIT_3X_SIZE, GFP_KERNEL); + g_rad_fw_image = kzalloc(RAD_FW_3X_SIZE, GFP_KERNEL); + g_rad_para_image = kzalloc(RAD_PARA_3X_SIZE + 4, GFP_KERNEL); + g_rad_testfw_image = kzalloc(RAD_TESTFW_3X_SIZE, GFP_KERNEL); + g_rad_testpara_image = kzalloc(RAD_PARA_3X_SIZE + 4, + GFP_KERNEL); + g_u8_table_init = SUCCESS; +} +#if ENABLE_FW_LOADER +static void raydium_cb(const struct firmware *fw, void *ctx) +{ + unsigned int u32_offset = 0; + unsigned int u32_image_version; +#ifdef FW_UPDATE_EN + int i32_ret = ERROR; +#endif + if (fw && (fw->size == RAD_ALLFW_3X_SIZE)) { + LOGD(LOG_DEBUG, "[touch]get firmware success size:%x\n", fw->size); + + memcpy(g_rad_boot_image, fw->data, RAD_BOOT_3X_SIZE); + u32_offset += RAD_BOOT_3X_SIZE; + memcpy(g_rad_init_image, fw->data + u32_offset, RAD_INIT_3X_SIZE); + u32_offset += RAD_INIT_3X_SIZE; + memcpy(g_rad_fw_image, fw->data + u32_offset, RAD_FW_3X_SIZE); + u32_offset += RAD_FW_3X_SIZE; + memcpy(g_rad_para_image, fw->data + u32_offset, RAD_PARA_3X_SIZE + 4); + u32_offset += RAD_PARA_3X_SIZE + 4; + memcpy(g_rad_testfw_image, fw->data + u32_offset, RAD_FW_3X_SIZE); + u32_offset += RAD_FW_3X_SIZE; + memcpy(g_rad_testpara_image, fw->data + u32_offset, RAD_PARA_3X_SIZE + 4); + + memcpy(g_rad_testfw_image + RAD_FW_3X_SIZE, g_rad_testpara_image + , RAD_PARA_3X_SIZE + 4); + u32_image_version = (g_rad_para_image[PARA_FW_VERSION_OFFSET] << 24) | + (g_rad_para_image[PARA_FW_VERSION_OFFSET + 1] << 16) | + (g_rad_para_image[PARA_FW_VERSION_OFFSET + 2] << 8) | + g_rad_para_image[PARA_FW_VERSION_OFFSET + 3]; + + LOGD(LOG_INFO, "[touch]RAD Image FW ver : 0x%x\n", u32_image_version); +#ifdef FW_UPDATE_EN + i32_ret = raydium_fw_update_check(u32_image_version); + if (i32_ret < 0) + LOGD(LOG_ERR, "[touch]fw update fail!\n"); +#endif + } else { + LOGD(LOG_ERR, "[touch]get firmware file fail!\n"); + } +} +int raydium_load_image(unsigned char *u8_buf, char *name) +{ + int i32_ret = SUCCESS; + struct i2c_client *client = g_raydium_ts->client; + + i32_ret = request_firmware_nowait(THIS_MODULE, true, FW_NAME, &client->dev, + GFP_KERNEL, g_raydium_ts, raydium_cb); + + if (i32_ret) { + LOGD(LOG_ERR, "[touch]failed to get firmware %s %d\n", + name, i32_ret); + return i32_ret; + } + + return i32_ret; +} +int raydium_mem_table_setting(void) +{ + int i32_ret = SUCCESS; + char name[RAYDIUM_FW_BIN_PATH_LENGTH]; + + snprintf((char *)name, RAYDIUM_FW_BIN_PATH_LENGTH, "%s", FW_NAME); + LOGD(LOG_DEBUG, "[touch]firmware path %s\n", name); + i32_ret = raydium_load_image(g_rad_fw_image, name); + + if (i32_ret < 0) + return ERROR; + + i32_ret = SUCCESS; + return i32_ret; +} + +#else +int raydium_mem_table_setting(void) +{ + int i32_ret = SUCCESS; + + LOGD(LOG_INFO, "[touch]Raydium ID is 0x%x\n", g_raydium_ts->id); + switch (g_raydium_ts->id) { + case RAD_30: + memcpy(g_rad_boot_image, u8_rad_boot_30, RAD_BOOT_3X_SIZE); + memcpy(g_rad_init_image, u8_rad_init_30, RAD_INIT_3X_SIZE); + memcpy(g_rad_fw_image, u8_rad_fw_30, RAD_FW_3X_SIZE); + memcpy(g_rad_testfw_image, u8_rad_testfw_30, RAD_FW_3X_SIZE); + memcpy(g_rad_testfw_image + RAD_FW_3X_SIZE, u8_rad_testpara_30 + , RAD_PARA_3X_SIZE + 4); + memcpy(g_rad_para_image, u8_rad_para_30, RAD_PARA_3X_SIZE + 4); + memcpy(g_rad_testpara_image, u8_rad_testpara_30, + RAD_PARA_3X_SIZE + 4); + break; +#if defined(FW_MAPPING_EN) + case RAD_31: + memcpy(g_rad_boot_image, u8_rad_boot_31, RAD_BOOT_3X_SIZE); + memcpy(g_rad_init_image, u8_rad_init_31, RAD_INIT_3X_SIZE); + memcpy(g_rad_fw_image, u8_rad_fw_31, RAD_FW_3X_SIZE); + memcpy(g_rad_testfw_image, u8_rad_testfw_31, RAD_FW_3X_SIZE); + memcpy(g_rad_testfw_image + RAD_FW_3X_SIZE, u8_rad_testpara_31, + RAD_PARA_3X_SIZE + 4); + memcpy(g_rad_para_image, u8_rad_para_31, RAD_PARA_3X_SIZE + 4); + memcpy(g_rad_testpara_image, u8_rad_testpara_31, + RAD_PARA_3X_SIZE + 4); + break; +#endif + default: + LOGD(LOG_DEBUG, "[touch]mapping ic setting use default fw\n"); + memcpy(g_rad_boot_image, u8_rad_boot_30, RAD_BOOT_3X_SIZE); + memcpy(g_rad_init_image, u8_rad_init_30, RAD_INIT_3X_SIZE); + memcpy(g_rad_fw_image, u8_rad_fw_30, RAD_FW_3X_SIZE); + memcpy(g_rad_testfw_image, u8_rad_testfw_30, RAD_FW_3X_SIZE); + memcpy(g_rad_testfw_image + RAD_FW_3X_SIZE, u8_rad_testpara_30 + , RAD_PARA_3X_SIZE + 4); + memcpy(g_rad_para_image, u8_rad_para_30, RAD_PARA_3X_SIZE + 4); + memcpy(g_rad_testpara_image, u8_rad_testpara_30, + RAD_PARA_3X_SIZE + 4); + g_raydium_ts->id = RAD_30; + i32_ret = SUCCESS; + break; + } + + g_u8_table_setting = 0; + return i32_ret; +} +#endif + +#if !ENABLE_FW_LOADER +int raydium_id_init(unsigned char u8_type) +{ + int i32_ret = ERROR; + + i32_ret = 0; + switch (u8_type) { + case 0: + g_raydium_ts->id = RAD_30; + i32_ret = SUCCESS; + break; +#if defined(FW_MAPPING_EN) + case 1: + g_raydium_ts->id = RAD_31; + i32_ret = SUCCESS; + break; +#endif + } + return i32_ret; +} +#endif +unsigned int bits_reverse(unsigned int u32_num, unsigned int bit_num) +{ + unsigned int reverse = 0, u32_i; + + for (u32_i = 0; u32_i < bit_num; u32_i++) { + if (u32_num & (1 << u32_i)) + reverse |= 1 << ((bit_num - 1) - u32_i); + } + return reverse; +} + +unsigned int rc_crc32(const char *buf, unsigned int u32_len, + unsigned int u32_crc) +{ + unsigned int u32_i; + unsigned char u8_flash_byte, u8_current, u8_j; + + for (u32_i = 0; u32_i < u32_len; u32_i++) { + u8_flash_byte = buf[u32_i]; + u8_current = (unsigned char)bits_reverse(u8_flash_byte, 8); + for (u8_j = 0; u8_j < 8; u8_j++) { + if ((u32_crc ^ u8_current) & 0x01) + u32_crc = (u32_crc >> 1) ^ 0xedb88320; + else + u32_crc >>= 1; + u8_current >>= 1; + } + } + return u32_crc; +} + +int wait_fw_state(struct i2c_client *client, unsigned int u32_addr, + unsigned int u32_state, unsigned long u32_delay_us, + unsigned short u16_retry) +{ + unsigned char u8_buf[4]; + unsigned int u32_read_data; + unsigned int u32_min_delay_us = u32_delay_us - 500; + unsigned int u32_max_delay_us = u32_delay_us + 500; + + do { + if (handle_i2c_pda_read(client, u32_addr, u8_buf, 4) == ERROR) + return ERROR; + + memcpy(&u32_read_data, u8_buf, 4); + u16_retry--; + usleep_range(u32_min_delay_us, u32_max_delay_us); + } while ((u32_read_data != u32_state) && (u16_retry != 0)); + + if (u32_read_data != u32_state) { + LOGD(LOG_ERR, "[touch]confirm data error : 0x%x\n", u32_read_data); + return ERROR; + } + + return SUCCESS; +} + +int wait_irq_state(struct i2c_client *client, unsigned int retry_time, + unsigned int u32_delay_us) +{ + int i32_ret = SUCCESS; + unsigned int u32_retry; + unsigned int u32_irq_value; + unsigned int u32_min_delay_us = u32_delay_us - 500; + unsigned int u32_max_delay_us = u32_delay_us + 500; + + u32_retry = retry_time; + u32_irq_value = 0; + while (u32_retry != 0 && u32_irq_value != 1) { + u32_irq_value = gpio_get_value(g_raydium_ts->irq_gpio); + usleep_range(u32_min_delay_us, u32_max_delay_us); + u32_retry--; + } + LOGD(LOG_DEBUG, "[touch]irq_value is %d\n", u32_irq_value); + + if (u32_retry == 0) { + LOGD(LOG_ERR, "[touch]%s, FW not ready, retry error!\n", __func__); + i32_ret = ERROR; + } + + return i32_ret; +} + +int raydium_do_software_reset(struct i2c_client *client) +{ + int i32_ret = SUCCESS; + + unsigned char u8_buf[4]; + + /*SW reset*/ + g_u8_resetflag = true; + memset(u8_buf, 0, sizeof(u8_buf)); + u8_buf[0] = 0x01; + LOGD(LOG_INFO, "[touch]SW reset\n"); + i32_ret = handle_i2c_pda_write(client, RAYDIUM_PDA_BLKRST, u8_buf, 4); + if (i32_ret < 0) + goto exit; + + msleep(35); + if (raydium_disable_i2c_deglitch() == ERROR) { + LOGD(LOG_ERR, "[touch] 1.disable_i2c_deglitch_3x NG!\r\n"); + goto exit; + } +exit: + return i32_ret; +} + +int set_skip_load(struct i2c_client *client) +{ + int i32_ret = SUCCESS; + unsigned int u32_retry_time = 1000; + unsigned char u8_buf[4]; + + /*Skip load*/ + memset(u8_buf, 0, sizeof(u8_buf)); + u8_buf[0] = 0x10; + u8_buf[1] = 0x08; + i32_ret = handle_i2c_pda_write(client, RAYDIUM_PDA_BOOTREG, u8_buf, 4); + if (i32_ret < 0) + goto exit_upgrade; + i32_ret = raydium_do_software_reset(client); + if (i32_ret < 0) + LOGD(LOG_ERR, "[touch]%s, SW reset error!\n", __func__); + msleep(35); + + i32_ret = wait_fw_state(client, RAYDIUM_PDA_BOOTSTATE, 0x82, 2000, u32_retry_time); + if (i32_ret < 0) + LOGD(LOG_ERR, "[touch]%s, wait_fw_state error!\n", __func__); +exit_upgrade: + return i32_ret; +} + +/*check pram crc32*/ +static int raydium_check_pram_crc_3X(struct i2c_client *client, + unsigned int u32_addr, + unsigned int u32_len) +{ + int i32_ret = SUCCESS; + unsigned int u32_crc_addr = u32_addr + u32_len; + unsigned int u32_end_addr = u32_crc_addr - 1; + unsigned int u32_crc_result, u32_read_data; + unsigned int u32_retry = 1000; + unsigned char u8_buf[4], u8_retry = 3; + + memset(u8_buf, 0, sizeof(u8_buf)); + u8_buf[0] = (unsigned char)(u32_addr & 0xFF); + u8_buf[1] = (unsigned char)((u32_addr & 0xFF00) >> 8); + u8_buf[2] = (unsigned char)(u32_end_addr & 0xFF); + u8_buf[3] = (unsigned char)((u32_end_addr & 0xFF00) >> 8); + + i32_ret = handle_i2c_pda_write(client, RAYDIUM_PDA_PRGCHKSUMADDR, u8_buf, 4); + if (i32_ret < 0) + goto exit_upgrade; + i32_ret = handle_i2c_pda_read(client, RAYDIUM_PDA_PRGCHKSUMENG, u8_buf, 4); + if (i32_ret < 0) + goto exit_upgrade; + u8_buf[3] |= 0x81; + i32_ret = handle_i2c_pda_write(client, RAYDIUM_PDA_PRGCHKSUMENG, u8_buf, 4); + + while (u8_buf[3] != 0x80 && u32_retry != 0) { + i32_ret = handle_i2c_pda_read(client, RAYDIUM_PDA_PRGCHKSUMENG, u8_buf, 4); + if (i32_ret < 0) + goto exit_upgrade; + + u32_retry--; + usleep_range(4500, 5500); + } + if (u32_retry == 0) { + LOGD(LOG_ERR, "[touch]%s, Cal CRC not ready, retry error!\n", + __func__); + i32_ret = ERROR; + } + + i32_ret = handle_i2c_pda_read(client, RAYDIUM_PDA_PRGCHKSUMRESULT, u8_buf, 4); + if (i32_ret < 0) + goto exit_upgrade; + memcpy(&u32_crc_result, u8_buf, 4); + i32_ret = handle_i2c_pda_read(client, u32_crc_addr, u8_buf, 4); + if (i32_ret < 0) + goto exit_upgrade; + memcpy(&u32_read_data, u8_buf, 4); + + while (u32_read_data != u32_crc_result && u8_retry > 0) { + i32_ret = handle_i2c_pda_read(client, RAYDIUM_PDA_PRGCHKSUMRESULT, u8_buf, 4); + if (i32_ret < 0) + goto exit_upgrade; + memcpy(&u32_crc_result, u8_buf, 4); + usleep_range(1500, 2500); + u8_retry--; + } + if (u32_read_data != u32_crc_result) { + LOGD(LOG_ERR, "[touch]check pram crc fail!!\n"); + LOGD(LOG_ERR, "[touch]u32_read_data 0x%x\n", u32_read_data); + LOGD(LOG_ERR, "[touch]u32_crc_result 0x%x\n", u32_crc_result); + i32_ret = ERROR; + goto exit_upgrade; + } else if (u8_retry != 3) { + LOGD(LOG_DEBUG, "[touch]check pram crc pass!!\n"); + LOGD(LOG_DEBUG, "[touch]u8_retry : %d\n", u8_retry); + LOGD(LOG_DEBUG, "[touch]u32_read_data 0x%x\n", u32_read_data); + LOGD(LOG_DEBUG, "[touch]u32_crc_result 0x%x\n", u32_crc_result); + i32_ret = ERROR; + goto exit_upgrade; + } else { + LOGD(LOG_DEBUG, "[touch]check pram crc pass!!\n"); + LOGD(LOG_DEBUG, "[touch]u8_retry : %d\n", u8_retry); + LOGD(LOG_DEBUG, "[touch]u32_read_data 0x%x\n", u32_read_data); + LOGD(LOG_DEBUG, "[touch]u32_crc_result 0x%x\n", u32_crc_result); + i32_ret = SUCCESS; + } + +exit_upgrade: + return i32_ret; +} + +/* upgrade firmware with image file */ +static int raydium_write_to_pram_3X(struct i2c_client *client, + unsigned int u32_fw_addr, + unsigned char u8_type) +{ + int i32_ret = ERROR; + unsigned int u32_fw_size = 0; + unsigned char *p_u8_firmware_data = NULL; + unsigned int u32_write_offset = 0; + unsigned short u16_write_length = 0; + + switch (u8_type) { + case RAYDIUM_INIT: + u32_fw_size = 0x80; + p_u8_firmware_data = g_rad_init_image; + break; + case RAYDIUM_BOOTLOADER: + u32_fw_size = 0x800; + p_u8_firmware_data = g_rad_boot_image; + break; + + case RAYDIUM_PARA: + u32_fw_size = RAYDIUM_PDA_PARALENGTH; + p_u8_firmware_data = g_rad_para_image; + break; + + case RAYDIUM_FIRMWARE: + u32_fw_size = RAYDIUM_PDA_FIRMWARELENGTH; + p_u8_firmware_data = g_rad_fw_image; + break; + case RAYDIUM_TEST_FW: + u32_fw_size = RAYDIUM_PDA_FIRMWARELENGTH + RAYDIUM_PDA_PARALENGTH; + p_u8_firmware_data = g_rad_testfw_image; + break; + + default: + goto exit_upgrate; + } + + u32_write_offset = 0; + while (u32_write_offset < u32_fw_size) { + if ((u32_write_offset + MAX_WRITE_PACKET_SIZE) < u32_fw_size) + u16_write_length = MAX_WRITE_PACKET_SIZE; + else + u16_write_length = + (unsigned short)(u32_fw_size - u32_write_offset); + + i32_ret = handle_i2c_pda_write( + client, + (u32_fw_addr + u32_write_offset), + (p_u8_firmware_data + u32_write_offset), + u16_write_length); + if (i32_ret < 0) + goto exit_upgrate; + + u32_write_offset += (unsigned long)u16_write_length; + } + u32_fw_addr += u32_write_offset; + +exit_upgrate: + if (i32_ret < 0) { + LOGD(LOG_ERR, "[touch]upgrade failed\n"); + return i32_ret; + } + LOGD(LOG_INFO, "[touch]upgrade success\n"); + return SUCCESS; +} + +unsigned char raydium_stop_mcu_3x(unsigned char u8_is_tp_reset) +{ + unsigned short u16_time_out = 100; + unsigned int u32_read_data; + unsigned int u32_write = 0; + + if (u8_is_tp_reset) { + gpio_set_value(g_raydium_ts->rst_gpio, 1); + gpio_set_value(g_raydium_ts->rst_gpio, 0); + msleep(RAYDIUM_RESET_INTERVAL_MSEC); + gpio_set_value(g_raydium_ts->rst_gpio, 1); + + g_u8_i2c_mode = PDA2_MODE; + msleep(35); + if (raydium_disable_i2c_deglitch() == ERROR) { + LOGD(LOG_ERR, "[touch] disable_i2c_deglitch_3x NG!\r\n"); + return ERROR; + } + } + LOGD(LOG_INFO, "[touch]%s\r\n", __func__); + + u32_write = 0x30; + if (handle_i2c_pda_write(g_raydium_ts->client, RAYDIUM_PDA_BOOTREG + , (unsigned char *)&u32_write, 4) == ERROR) { + return ERROR; + } + + u32_write = 0x01; + if (handle_i2c_pda_write(g_raydium_ts->client, RAYDIUM_PDA_BLKRST, (unsigned char *)&u32_write, 4 + ) == ERROR) { + return ERROR; + } + msleep(100); + + if (raydium_disable_i2c_deglitch() == ERROR) { + LOGD(LOG_ERR, "[touch] disable_i2c_deglitch_3x NG!\r\n"); + return ERROR; + } + LOGD(LOG_INFO, "[touch] stop_mcu 2\r\n"); + + if (handle_i2c_pda_read(g_raydium_ts->client, RAYDIUM_PDA_BOOTREG + , (unsigned char *)(&u32_read_data), 4) == ERROR) { + return ERROR; + } + + while ((u32_read_data & 0x2000) == 0 && u16_time_out-- > 0) { + msleep(20); + if (handle_i2c_pda_read(g_raydium_ts->client, RAYDIUM_PDA_BOOTREG + , (unsigned char *)(&u32_read_data), 4) == ERROR) { + return ERROR; + } + } + + LOGD(LOG_DEBUG, "[touch]Stop MCU=0x%X(0x%x)(%d)!!\r\n", u32_read_data, (u32_read_data & 0x2000), u16_time_out); + + if ((u32_read_data & 0x2000) == 0) + return ERROR; + + return SUCCESS; +} + +/* Raydium fireware upgrade flow */ +static int raydium_fw_upgrade_3X(struct i2c_client *client, + unsigned char u8_type, + unsigned char u8_check_crc) +{ + int i32_ret = 0; + unsigned char u8_buf[4]; + unsigned short u16_retry = 1000; + unsigned int u32_write = 0; + + /*##### wait for boot-loader start #####*/ + LOGD(LOG_INFO, "[touch]Type is %x\n", u8_type); + + /*read Boot version*/ + if (handle_i2c_pda_read(client, RAYDIUM_PDA_BOOTVERSION, u8_buf, 4) == ERROR) + return ERROR; + u32_write = (u8_buf[3] << 24) | (u8_buf[2] << 16) | (u8_buf[1] << 8) | u8_buf[0]; + LOGD(LOG_INFO, "[touch]bootloader ver: 0x%x\r\n", u32_write); + + if (u8_type != RAYDIUM_COMP) { + if (raydium_stop_mcu_3x(0) == ERROR) + return ERROR; + } + + /*#start write data to PRAM*/ + if (u8_type == RAYDIUM_FIRMWARE) { + /* unlock PRAM */ + u8_buf[0] = (BOTLR_LOCK | COMP_LOCK | BASEL_LOCK | INICO_LOCK); + i32_ret = handle_i2c_pda_write(client, + RAYDIUM_PDA_PRAMLOCK, + u8_buf, 4); + if (i32_ret < 0) + goto exit_upgrade; + i32_ret = raydium_write_to_pram_3X(client, RAYDIUM_PDA_FIRMWAREADDR, + RAYDIUM_FIRMWARE); + if (i32_ret < 0) + goto exit_upgrade; + + i32_ret = raydium_write_to_pram_3X(client, RAYDIUM_PDA_PARAADDR, + RAYDIUM_PARA); + if (i32_ret < 0) + goto exit_upgrade; + + i32_ret = raydium_check_pram_crc_3X(client, RAYDIUM_PDA_FIRMWAREADDR, + RAYDIUM_PDA_CRCLENGTH); + if (i32_ret < 0) + goto exit_upgrade; + + } else if (u8_type == RAYDIUM_BOOTLOADER) { + /* unlock PRAM */ + u8_buf[0] = (BOTLR_LOCK | CONFIG_LOCK | COMP_LOCK | BASEL_LOCK | INICO_LOCK); + + i32_ret = handle_i2c_pda_write(client, RAYDIUM_PDA_PRAMLOCK, + u8_buf, 4); + if (i32_ret < 0) + goto exit_upgrade; + i32_ret = raydium_write_to_pram_3X(client, 0x0800, + RAYDIUM_BOOTLOADER); + if (i32_ret < 0) + goto exit_upgrade; + i32_ret = raydium_write_to_pram_3X(client, 0x1000, + RAYDIUM_INIT); + if (i32_ret < 0) + goto exit_upgrade; + + i32_ret = raydium_check_pram_crc_3X(client, 0x800, + 0x7FC); + if (i32_ret < 0) + goto exit_upgrade; + i32_ret = raydium_check_pram_crc_3X(client, 0x1000, + 0x7C); + if (i32_ret < 0) + goto exit_upgrade; + } + + if (u8_type != RAYDIUM_COMP) { + /*release mcu hold*/ + /*Skip load*/ + i32_ret = set_skip_load(client); + if (i32_ret < 0) + LOGD(LOG_ERR, "[touch]%s, set skip_load error!\n", __func__); + /*#confirm in burn mode*/ + if (wait_fw_state(client, RAYDIUM_PDA_BOOTSTATE, 0x82, + 2000, u16_retry) == ERROR) { + LOGD(LOG_ERR, "[touch]Error, confirm in burn mode 1\n"); + i32_ret = ERROR; + goto exit_upgrade; + } + + } + + /*#setting burning mode*/ + memset(u8_buf, 0, sizeof(u8_buf)); + u8_buf[0] = 0x01; + i32_ret = handle_i2c_pda_write(client, RAYDIUM_PDA_BOOTENG1, u8_buf, 4); + if (i32_ret < 0) + goto exit_upgrade; + + memset(u8_buf, 0, sizeof(u8_buf)); + i32_ret = handle_i2c_pda_write(client, RAYDIUM_PDA_BOOTENG2, u8_buf, 4); + if (i32_ret < 0) + goto exit_upgrade; + + memset(u8_buf, 0, sizeof(u8_buf)); + i32_ret = handle_i2c_pda_write(client, RAYDIUM_PDA_BOOTENG3, u8_buf, 4); + if (i32_ret < 0) + goto exit_upgrade; + + memset(u8_buf, 0, sizeof(u8_buf)); + u8_buf[0] = 0x01; + i32_ret = handle_i2c_pda_write(client, RAYDIUM_PDA_BOOTMODE, u8_buf, 4); + if (i32_ret < 0) + goto exit_upgrade; + + /*#confirm in burn mode*/ + if (wait_fw_state(client, RAYDIUM_PDA_BOOTSTATE, 0xFF, + 2000, u16_retry) == ERROR) { + LOGD(LOG_ERR, "[touch]bootloader wrt empty flash fail\r\n"); + LOGD(LOG_ERR, "[touch]bootloader confirm in burn mode fail, Type=%d\r\n", u8_type); + i32_ret = ERROR; + goto exit_upgrade; + } + + /* burning setting */ + memset(u8_buf, 0, sizeof(u8_buf)); + u8_buf[0] = 0x10; + i32_ret = handle_i2c_pda_write(client, RAYDIUM_PDA_BOOTREG, u8_buf, 4); + if (i32_ret < 0) + goto exit_upgrade; + + memset(u8_buf, 0, sizeof(u8_buf)); + u8_buf[0] = u8_type; + i32_ret = handle_i2c_pda_write(client, RAYDIUM_PDA_PRAMTYPE, u8_buf, 4); + if (i32_ret < 0) + goto exit_upgrade; + + /*#set PRAM length (at 'h5000_090C)*/ + if (u8_type == RAYDIUM_COMP) { + memset(u8_buf, 0, sizeof(u8_buf)); + u8_buf[0] = 0x78; + u8_buf[1] = 0x7c; + i32_ret = handle_i2c_pda_write(client, RAYDIUM_PDA_PRAMADDR, + u8_buf, 4); + if (i32_ret < 0) + goto exit_upgrade; + + } else if (u8_type == RAYDIUM_FIRMWARE) { + memset(u8_buf, 0, sizeof(u8_buf)); + u8_buf[0] = 0x00; + u8_buf[1] = 0x08; + i32_ret = handle_i2c_pda_write(client, RAYDIUM_PDA_PRAMADDR, + u8_buf, 4); + if (i32_ret < 0) + goto exit_upgrade; + + } else if (u8_type == RAYDIUM_BOOTLOADER) { + memset(u8_buf, 0, sizeof(u8_buf)); + u8_buf[0] = 0x00; + u8_buf[1] = 0x08; + i32_ret = handle_i2c_pda_write(client, 0x50000908, + u8_buf, 4); + if (i32_ret < 0) + goto exit_upgrade; + } + + + /*#set sync_data(RAYDIUM_PDA_SYNCDATA) = 0 as WRT data finish*/ + memset(u8_buf, 0, sizeof(u8_buf)); + i32_ret = handle_i2c_pda_write(client, RAYDIUM_PDA_SYNCDATA, u8_buf, 4); + if (i32_ret < 0) + goto exit_upgrade; + + /*#wait for sync_data finish*/ + if (wait_fw_state(client, RAYDIUM_PDA_BOOTENG4, 168, 1000, + u16_retry) == ERROR) { + LOGD(LOG_ERR, "[touch]Error, wait for input unlock key\n"); + i32_ret = ERROR; + goto exit_upgrade; + } + + /*#flash unlock key*/ + memset(u8_buf, 0, sizeof(u8_buf)); + u8_buf[0] = 0xd7; + i32_ret = handle_i2c_pda_write(client, RAYDIUM_PDA_FLKEY2, u8_buf, 4); + if (i32_ret < 0) + goto exit_upgrade; + + memset(u8_buf, 0, sizeof(u8_buf)); + u8_buf[0] = 0xa5; + i32_ret = handle_i2c_pda_write(client, RAYDIUM_PDA_FLKEY1, u8_buf, 4); + if (i32_ret < 0) + goto exit_upgrade; + + memset(u8_buf, 0, sizeof(u8_buf)); + i32_ret = handle_i2c_pda_write(client, RAYDIUM_PDA_FLKEY1, u8_buf, 4); + if (i32_ret < 0) + goto exit_upgrade; + + memset(u8_buf, 0, sizeof(u8_buf)); + u8_buf[0] = 0xa5; + i32_ret = handle_i2c_pda_write(client, RAYDIUM_PDA_FLKEY1, u8_buf, 4); + if (i32_ret < 0) + goto exit_upgrade; + + memset(u8_buf, 0, sizeof(u8_buf)); + i32_ret = handle_i2c_pda_write(client, RAYDIUM_PDA_FLKEY2, u8_buf, 4); + if (i32_ret < 0) + goto exit_upgrade; + + memset(u8_buf, 0, sizeof(u8_buf)); + i32_ret = handle_i2c_pda_write(client, RAYDIUM_PDA_FLASHPRO, u8_buf, 4); + if (i32_ret < 0) + goto exit_upgrade; + + /*#ready to burn flash*/ + memset(u8_buf, 0, sizeof(u8_buf)); + u8_buf[0] = 0xa8; + i32_ret = handle_i2c_pda_write(client, RAYDIUM_PDA_BOOTSTATE, u8_buf, 4); + if (i32_ret < 0) + goto exit_upgrade; + + /*#clr sync_data(RAYDIUM_PDA_SYNCDATA) = 0 as finish*/ + memset(u8_buf, 0, sizeof(u8_buf)); + i32_ret = handle_i2c_pda_write(client, RAYDIUM_PDA_SYNCDATA, u8_buf, 4); + if (i32_ret < 0) + goto exit_upgrade; + + /* wait erase/wrt finish + * confirm burning_state result (gu8I2CSyncData.burning_state = + * BURNING_WRT_FLASH_FINISH at RAYDIUM_PDA_BOOTENG3) + */ + if (wait_fw_state(client, RAYDIUM_PDA_BOOTENG3, 6, 8000, + u16_retry) == ERROR) { + LOGD(LOG_ERR, "[touch]Error, wait erase/wrt finish\n"); + i32_ret = ERROR; + goto exit_upgrade; + } + LOGD(LOG_INFO, "[touch]Burn flash ok\n"); + + if (u8_check_crc) { + memset(u8_buf, 0, sizeof(u8_buf)); + i32_ret = handle_i2c_pda_write(client, RAYDIUM_PDA_SYNCDATA, + u8_buf, 4); + if (i32_ret < 0) + goto exit_upgrade; + + /*#wait software reset finish*/ + msleep(35); + + /* wait sw reset finished RAYDIUM_PDA_BOOTSTATE = 0x82 */ + if (wait_fw_state(client, RAYDIUM_PDA_BOOTSTATE, 0x82, 2000, + u16_retry) == ERROR) { + LOGD(LOG_ERR, "[touch]Error, wait sw reset finished\n"); + i32_ret = ERROR; + goto exit_upgrade; + } + + /*#set test_mode = 1 start to check crc*/ + memset(u8_buf, 0, sizeof(u8_buf)); + u8_buf[0] = 0x01; + i32_ret = handle_i2c_pda_write(client, RAYDIUM_PDA_BOOTMODE, + u8_buf, 4); + if (i32_ret < 0) + goto exit_upgrade; + + /*#wait crc check finish*/ + if (wait_fw_state(client, RAYDIUM_PDA_BOOTENG2, 2, + 2000, u16_retry) + == ERROR) { + LOGD(LOG_ERR, "[touch]Error, wait crc check finish\n"); + i32_ret = ERROR; + goto exit_upgrade; + } + + /*#crc check pass RAYDIUM_PDA_BOOTSTATE = 0x81*/ + if (wait_fw_state(client, RAYDIUM_PDA_BOOTSTATE, 0x81, + 2000, u16_retry) + == ERROR) { + LOGD(LOG_ERR, "[touch]Error, confirm crc result\n"); + i32_ret = ERROR; + goto exit_upgrade; + } + /*#run to next step*/ + LOGD(LOG_INFO, "[touch]Type 0x%x => Pass\n", u8_type); + + /* sw rest */ + + i32_ret = raydium_do_software_reset(client); + if (i32_ret < 0) + goto exit_upgrade; + + g_u8_i2c_mode = PDA2_MODE; + + + LOGD(LOG_INFO, "[touch]Burn FW finish!\n"); + } + +exit_upgrade: + return i32_ret; +} + +static int raydium_boot_upgrade_3X(struct i2c_client *client, unsigned char u8_is_tp_reset) +{ + int i32_ret = SUCCESS; + unsigned char u8_buf[4]; + unsigned short u16_retry = 1000; + + /*set mcu hold*/ + memset(u8_buf, 0, sizeof(u8_buf)); + if (raydium_stop_mcu_3x(u8_is_tp_reset) == ERROR) + return ERROR; + /*WRT boot-loader to PRAM first*/ + memset(u8_buf, 0, sizeof(u8_buf)); + u8_buf[0] = 0x1F; + i32_ret = handle_i2c_pda_write(client, RAYDIUM_PDA_PRAMLOCK, u8_buf, 4); + + /*Sending bootloader*/ + i32_ret = raydium_write_to_pram_3X(client, 0x0000, + RAYDIUM_BOOTLOADER); + if (i32_ret < 0) + goto exit_upgrade; + + i32_ret = raydium_check_pram_crc_3X(client, 0x000, 0x7FC); + if (i32_ret < 0) + goto exit_upgrade; + + /*Sending initial code*/ + i32_ret = raydium_write_to_pram_3X(client, 0x7f80, + RAYDIUM_INIT); + if (i32_ret < 0) + goto exit_upgrade; + + i32_ret = raydium_check_pram_crc_3X(client, 0x7F80, 0x7C); + if (i32_ret < 0) + goto exit_upgrade; + + /*release mcu hold*/ + /*Skip load*/ + i32_ret = set_skip_load(client); + if (i32_ret < 0) + LOGD(LOG_ERR, "[touch]%s, set skip_load error!\n", __func__); + msleep(35); + if (wait_fw_state(client, RAYDIUM_PDA_BOOTSTATE, 0x82, 1000, + u16_retry) == ERROR) { + LOGD(LOG_ERR, "[touch]Error, wait for input unlock key\n"); + i32_ret = ERROR; + goto exit_upgrade; + } +exit_upgrade: + return i32_ret; +} + +int raydium_fw_update_check(unsigned int u32_check_version) +{ + int i32_ret = ERROR; + unsigned int u32_fw_version; + unsigned char u8_rbuffer[4]; + + if (g_raydium_ts->fw_version != u32_check_version) { + + LOGD(LOG_INFO, "[touch]FW need update.\n"); + g_u8_raydium_flag |= ENG_MODE; + + i32_ret = raydium_burn_fw(g_raydium_ts->client); + if (i32_ret < 0) { + LOGD(LOG_ERR, "[touch]FW update fail:%d\n", i32_ret); + goto exit_error; + } + g_u8_raydium_flag &= ~ENG_MODE; + + i32_ret = raydium_i2c_pda2_set_page(g_raydium_ts->client, + g_raydium_ts->is_suspend, + RAYDIUM_PDA2_PAGE_0); + if (i32_ret < 0) + goto exit_error; + + i32_ret = raydium_i2c_pda2_read(g_raydium_ts->client, + RAYDIUM_PDA2_FW_VERSION_ADDR, + u8_rbuffer, + 4); + if (i32_ret < 0) + goto exit_error; + + u32_fw_version = (u8_rbuffer[0] << 24) | + (u8_rbuffer[1] << 16) | + (u8_rbuffer[2] << 8) | + u8_rbuffer[3]; + LOGD(LOG_INFO, "[touch]RAD FW ver is 0x%x\n", + u32_fw_version); + g_raydium_ts->fw_version = u32_fw_version; + } else + LOGD(LOG_INFO, "[touch]FW is the latest version.\n"); + + i32_ret = SUCCESS; + return i32_ret; + +exit_error: + return i32_ret; +} + +int raydium_burn_fw(struct i2c_client *client) +{ + int i32_ret = ERROR; + + g_u8_resetflag = true; + if ((g_raydium_ts->id & 0x3000) != 0) { + LOGD(LOG_INFO, "[touch]start burn function!\n"); + i32_ret = raydium_boot_upgrade_3X(client, 1); + if (i32_ret < 0) + goto exit_upgrade; + + i32_ret = raydium_fw_upgrade_3X(client, RAYDIUM_BOOTLOADER, 0); + if (i32_ret < 0) + goto exit_upgrade; + + i32_ret = raydium_fw_upgrade_3X(client, RAYDIUM_FIRMWARE, 1); + if (i32_ret < 0) + goto exit_upgrade; + } else { + LOGD(LOG_ERR, "[touch]FW ID ERROR!\n"); + } + +exit_upgrade: + return i32_ret; +} + +int raydium_fw_update_init(unsigned short u16_i2c_data) +{ + + unsigned char u8_rbuffer[4]; + + unsigned int u32_fw_version; +#if !ENABLE_FW_LOADER + unsigned int u32_image_version; +#endif + int i32_ret = ERROR; + + mutex_lock(&g_raydium_ts->lock); + i32_ret = raydium_i2c_pda2_set_page(g_raydium_ts->client, + g_raydium_ts->is_suspend, + RAYDIUM_PDA2_PAGE_0); + if (i32_ret < 0) + goto exit_error; + + i32_ret = raydium_i2c_pda2_read(g_raydium_ts->client, + RAYDIUM_PDA2_FW_VERSION_ADDR, + u8_rbuffer, + 4); + if (i32_ret < 0) + goto exit_error; + + mutex_unlock(&g_raydium_ts->lock); + + u32_fw_version = (u8_rbuffer[0] << 24) | (u8_rbuffer[1] << 16) | + (u8_rbuffer[2] << 8) | u8_rbuffer[3]; + LOGD(LOG_INFO, "[touch]RAD FW ver 0x%.8x\n", u32_fw_version); + + g_raydium_ts->fw_version = u32_fw_version; + + g_raydium_ts->id = ((u16_i2c_data & 0xF) << 12) | + ((u8_rbuffer[0] & 0xF) << 8) | u8_rbuffer[1]; + + raydium_mem_table_init(g_raydium_ts->id); + if (raydium_mem_table_setting() == SUCCESS) { +#if !ENABLE_FW_LOADER + + u32_image_version = (g_rad_para_image[PARA_FW_VERSION_OFFSET] << 24) | + (g_rad_para_image[PARA_FW_VERSION_OFFSET + 1] << 16) | + (g_rad_para_image[PARA_FW_VERSION_OFFSET + 2] << 8) | + g_rad_para_image[PARA_FW_VERSION_OFFSET + 3]; + + LOGD(LOG_INFO, "[touch]RAD Image FW ver : 0x%x\n", u32_image_version); +#endif + } else { + LOGD(LOG_ERR, "[touch]Mem setting failed, Stop fw upgrade!\n"); + return i32_ret; + } + +#ifdef FW_UPDATE_EN + +#if !ENABLE_FW_LOADER + i32_ret = raydium_fw_update_check(u32_image_version); + if (i32_ret < 0) + LOGD(LOG_ERR, "[touch]fw update fail!\n"); +#endif + +#endif + + + return i32_ret; + +exit_error: + mutex_unlock(&g_raydium_ts->lock); + return i32_ret; +} +int raydium_burn_comp(struct i2c_client *client) +{ + int i32_ret = ERROR; + + i32_ret = raydium_boot_upgrade_3X(client, 0); + if (i32_ret < 0) + goto exit_upgrade; + + i32_ret = set_skip_load(client); + if (i32_ret < 0) + goto exit_upgrade; + + + i32_ret = raydium_fw_upgrade_3X(client, RAYDIUM_COMP, 1); + if (i32_ret < 0) + goto exit_upgrade; + + i32_ret = SUCCESS; + +exit_upgrade: + return i32_ret; +} + +int raydium_check_fw_ready(struct i2c_client *client) +{ + int i32_ret = SUCCESS; + unsigned int u32_retry = 400; + unsigned char u8_buf[4]; + + u8_buf[1] = 0; + while (u8_buf[1] != 0x40 && u32_retry != 0) { + i32_ret = handle_i2c_pda_read(client, RAYDIUM_PDA_BOOTREG, u8_buf, 4); + if (i32_ret < 0) + goto exit; + + u32_retry--; + usleep_range(4500, 5500); + } + + if (u32_retry == 0) { + LOGD(LOG_ERR, "[touch]%s, FW not ready, retry error!\n", __func__); + i32_ret = ERROR; + } else { + LOGD(LOG_INFO, "[touch]%s, FW is ready!!\n", __func__); + usleep_range(4500, 5500); + } + +exit: + return i32_ret; +} +/* upgrade firmware with image file */ +int raydium_fw_upgrade_with_image(struct i2c_client *client, + unsigned int u32_fw_addr, + unsigned char u8_type) +{ + int i32_ret = ERROR; + unsigned int u32_fw_size = 0; + unsigned char *p_u8_firmware_data = NULL; + unsigned int u32_write_offset = 0; + unsigned short u16_write_length = 0; + unsigned int u32_checksum = 0xFFFFFFFF; + + switch (u8_type) { + case RAYDIUM_INIT: + u32_fw_size = 0x1fc; + p_u8_firmware_data = g_rad_init_image; + break; + case RAYDIUM_PARA: + u32_fw_size = 0x158; + p_u8_firmware_data = g_rad_para_image; + break; + case RAYDIUM_FIRMWARE: + u32_fw_size = 0x61fc; + p_u8_firmware_data = g_rad_fw_image; + break; + case RAYDIUM_BOOTLOADER: + u32_fw_size = 0x7FC; + p_u8_firmware_data = g_rad_boot_image; + break; + case RAYDIUM_TEST_FW: + u32_fw_size = 0x635C; + p_u8_firmware_data = g_rad_testfw_image; + break; + } + + LOGD(LOG_DEBUG, "[touch]CRC 0x%08X\n", + *(unsigned int *)(p_u8_firmware_data + u32_fw_size)); + + u32_checksum = rc_crc32(p_u8_firmware_data, + u32_fw_size, u32_checksum); + u32_checksum = bits_reverse(u32_checksum, 32); + memcpy((p_u8_firmware_data + u32_fw_size), &u32_checksum, 4); + LOGD(LOG_DEBUG, "[touch]CRC result 0x%08X\n", u32_checksum); + + u32_fw_size += 4; + + u32_write_offset = 0; + while (u32_write_offset < u32_fw_size) { + if ((u32_write_offset + MAX_WRITE_PACKET_SIZE) < u32_fw_size) + u16_write_length = MAX_WRITE_PACKET_SIZE; + else + u16_write_length = + (unsigned short) + (u32_fw_size - u32_write_offset); + + i32_ret = handle_i2c_pda_write( + client, + (u32_fw_addr + u32_write_offset), + (p_u8_firmware_data + u32_write_offset), + u16_write_length); + if (i32_ret < 0) + goto exit_upgrate; + + u32_write_offset += (unsigned long)u16_write_length; + } + u32_fw_addr += u32_write_offset; + +exit_upgrate: + if (i32_ret < 0) { + LOGD(LOG_ERR, "[touch]upgrade failed\n"); + return i32_ret; + } + LOGD(LOG_INFO, "[touch]upgrade success\n"); + return SUCCESS; +} +int raydium_load_test_fw(struct i2c_client *client) +{ + unsigned int u32_read_data; + unsigned int u32_write = 0; + int i32_ret = ERROR; + + /* set skip load & MCU hold then SW reset*/ + /* sync_data:210;cmd_type:210h;ret_data:214h;test_mode:218h*/ + if (raydium_stop_mcu_3x(1) == ERROR) + goto ERROR_EXIT; + + /*WRT boot-loader to PRAM first*/ + u32_write = 1; + if (handle_i2c_pda_write(client, RAYDIUM_PDA_PRAMLOCK, (unsigned char *)&u32_write, 4) == ERROR) + return ERROR; + + /*Sending bootloader*/ + + i32_ret = raydium_write_to_pram_3X(client, 0x0000, + RAYDIUM_BOOTLOADER); + if (i32_ret < 0) + goto ERROR_EXIT; + + i32_ret = raydium_check_pram_crc_3X(client, 0x000, 0x7FC); + if (i32_ret < 0) + goto ERROR_EXIT; + + i32_ret = raydium_write_to_pram_3X(client, RAYDIUM_PDA_FIRMWAREADDR, + RAYDIUM_TEST_FW); + if (i32_ret < 0) + goto ERROR_EXIT; + + i32_ret = raydium_check_pram_crc_3X(client, RAYDIUM_PDA_FIRMWAREADDR, + RAYDIUM_PDA_CRCLENGTH); + if (i32_ret < 0) + goto ERROR_EXIT; + + /*clear CC & BL info*/ + u32_write = 0x00000000; + if (handle_i2c_pda_write(client, 0x7F78, (unsigned char *)&u32_write, 4) == ERROR) + return ERROR; + + /* set skip load & MCU hold then SW reset*/ + /* sync_data:210;cmd_type:210h;ret_data:214h;test_mode:218h*/ + /* read bootloader version*/ + if (handle_i2c_pda_read(client, 0x80, (unsigned char *)(&u32_read_data), 4) == ERROR) + goto ERROR_EXIT; + LOGD(LOG_INFO, "[touch]Read bootloader ver=0x%x!!\r\n", u32_read_data); + + u32_write = 0x0410; + if (handle_i2c_pda_write(client, RAYDIUM_PDA_BOOTREG, (unsigned char *)&u32_write, 4) == ERROR) + return ERROR; + if (handle_i2c_pda_read(client, RAYDIUM_PDA_BOOTREG, (unsigned char *)(&u32_read_data), 4) == ERROR) + goto ERROR_EXIT; + LOGD(LOG_DEBUG, "[touch]write MCU STATUS=0x%x!!\r\n", u32_read_data); + /*wait sw rst finish*/ + u32_write = 1; + + handle_i2c_pda_write(client, RAYDIUM_PDA_BLKRST, (unsigned char *)&u32_write, 4); + msleep(35); + + if (handle_i2c_pda_read(client, RAYDIUM_PDA_BOOTREG, (unsigned char *)(&u32_read_data), 4) == ERROR) + goto ERROR_EXIT; + LOGD(LOG_INFO, "[touch]Read MCU STATUS=0x%x!!\r\n", u32_read_data); + if (handle_i2c_pda_read(client, 0x200006E4, (unsigned char *)(&u32_read_data), 4) == ERROR) + goto ERROR_EXIT; + LOGD(LOG_INFO, "[touch]Read test fw version=0x%x!!\r\n", u32_read_data); + + return SUCCESS; + +ERROR_EXIT: + + return ERROR; +} + + + diff --git a/qcom/opensource/touch-drivers/raydium/raydium_selftest.c b/qcom/opensource/touch-drivers/raydium/raydium_selftest.c new file mode 100644 index 0000000000..c04ee6a43a --- /dev/null +++ b/qcom/opensource/touch-drivers/raydium/raydium_selftest.c @@ -0,0 +1,626 @@ +/* raydium_selftest.c + * + * Raydium TouchScreen driver. + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * Qualcomm Innovation Center, Inc. chooses to use it under GPLv2 + * Copyright (c) 2021 Raydium tech Ltd. + * + * 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. + * + * BSD LICENSE + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. or Linaro Ltd. nor the names of + * its contributors may be used to endorse or promote products + * derived from this software without specific prior written + * permission. + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "drv_interface.h" +#include "chip_raydium/ic_drv_interface.h" +#include "raydium_selftest.h" +#include "chip_raydium/ic_drv_global.h" +#include "tpselftest_30.h" +#if defined(FW_MAPPING_EN) +#include "tpselftest_21.h" +#endif + +#define RM_SELF_TEST_CUSTOMER_VERSION 0x01 +#define RM_SELF_TEST_PLATFORM_VERSION 0x01 +#define RM_SELF_TEST_PROJECT_VERSION 0x40 +#define RM_SELF_TEST_MAIN_VERSION 1 +#define RM_SELF_TEST_SUB_VERSION 0 + +#define RESULT_SUCCESS 0 +#define RESULT_NG 1 + +#define RELATIVE_PATH 0 + +#define RM_SELF_TEST_MAX_STR_LENGTH 1000 + +unsigned char g_u8_normal_fw_version_buf[4]; +char str_ini_path[100]; + +static int self_test_all(void) +{ + int ret = 0; + + g_u8_raydium_flag |= ENG_MODE; + handle_ic_test(); + ret = g_u32_wearable_test_result; + + /*g_u8_raydium_flag &= ~ENG_MODE;*/ + DEBUGOUT("%s\r\n", __func__); + + return ret; +} + +int self_test_save_to_file(char *file_name, char *p_string, short len) +{ + struct file *filp = NULL; + mm_segment_t old_fs; + + filp = filp_open_block(file_name, O_RDWR | O_CREAT | O_APPEND, 0666); + if (IS_ERR(filp)) { + DEBUGOUT("can't open file:%s\n", RM_SELF_TEST_LOGFILE); + return 0; + } + old_fs = force_uaccess_begin(); + filp->f_op->write(filp, p_string, len, &filp->f_pos); + force_uaccess_end(old_fs); + filp_close(filp, NULL); + return 1; +} + +#if 1 +static int raydium_check_ini_version(void) +{ + int ret = 0; + unsigned int u32_test_version; + + memcpy(&u32_test_version, &g_rad_testpara_image[4], 4); + + if (u32_test_version != g_st_test_para_resv.u32_test_fw_version) { + DEBUGOUT("test fw versio 0x%X != ini version 0x%X\n" + , u32_test_version, g_st_test_para_resv.u32_test_fw_version); + ret = WEARABLE_FT_TEST_RESULT_TEST_FW_VER_NG; + } + if (g_u16_dev_id == DEVICE_ID_3X) { + + g_u32_dongle_flash_ini_addr = F303_DONGLE_FLASH_INI_ADDR; + g_u32_ini_threshold_addr = F303_INI_THRESHOLD_ADDR; + g_u32_ini_para_addr = F303_INI_PARA_ADDR; + g_u32_ini_raw_data_3_cc_addr = F303_INI_RAW_DATA_3_CC_ADDR; + g_u32_ini_uc_cc_addr = F303_INI_UC_CC_ADDR; + g_u32_initial_code_start_addr = F303_INITIAL_CODE_START_ADDR; + DEBUGOUT("[out_set_ic_version] F303 Set INI ADDR!\r\n"); + } else if (g_u16_dev_id == DEVICE_ID_2X) { + g_u32_dongle_flash_ini_addr = F302_DONGLE_FLASH_INI_ADDR; + g_u32_ini_threshold_addr = F302_INI_THRESHOLD_ADDR; + g_u32_ini_para_addr = F302_INI_PARA_ADDR; + g_u32_ini_raw_data_3_cc_addr = F302_INI_RAW_DATA_3_CC_ADDR; + g_u32_ini_uc_cc_addr = F302_INI_UC_CC_ADDR; + g_u32_initial_code_start_addr = F302_INITIAL_CODE_START_ADDR; + DEBUGOUT("[out_set_ic_version] F302 Set INI ADDR!\r\n"); + } + return ret; +} +#else +static int raydium_check_ini_version(void) +{ + int ret = 0; + unsigned int u32_test_version, u32_version_20, u32_version_21; + + memcpy(&u32_test_version, &g_st_test_para_resv.u32_test_fw_version, 4); + + if (g_u16_dev_id == DEVICE_ID_2X) { + switch (g_raydium_ts->id) { + case RAD_SELFTEST_20: + memcpy(&u32_version_20, &u8_rad_testpara_20[4], 4); + DEBUGOUT("ini version 0x%X, 20 version 0x%X\n" + , u32_test_version, u32_version_20); + + if (u32_test_version == u32_version_20) + DEBUGOUT("map version= 0x%X\r\n", u32_version_20); + else + ret = WEARABLE_FT_TEST_RESULT_TEST_FW_VER_NG; + case RAD_SELFTEST_21: + memcpy(&u32_version_21, &u8_rad_testpara_21[4], 4); + DEBUGOUT("ini version 0x%X, 21 version 0x%X\n" + , u32_test_version, u32_version_21); + + if (u32_test_version == u32_version_21) + DEBUGOUT("map version= 0x%X\r\n", u32_version_21); + else + ret = WEARABLE_FT_TEST_RESULT_TEST_FW_VER_NG; + } + } + + return ret; +} +#endif +static int self_test_init(void) +{ + int ret = 0; + unsigned int u32_dev_id; + + g_u8_drv_interface = I2C_INTERFACE; + g_u16_dev_id = DEVICE_ID_3X; + + if (handle_ic_read(RAYDIUM_CHK_I2C_CMD, 4, (unsigned char *)&u32_dev_id, g_u8_drv_interface, I2C_WORD_MODE) == ERROR) { + ret = WEARABLE_FT_TEST_RESULT_SYSFS_NG; + return ret; + } + g_u16_dev_id = ((u32_dev_id & 0xFFFF0000) >> 16); + + if (g_u16_dev_id == DEVICE_ID_2X) { + handle_ic_read(0x00006a04, 4, g_u8_normal_fw_version_buf, g_u8_drv_interface, I2C_WORD_MODE); + DEBUGOUT("FW Version=0x%.2X%.2X%.2X%.2X\n", g_u8_normal_fw_version_buf[0], g_u8_normal_fw_version_buf[1], + g_u8_normal_fw_version_buf[3], g_u8_normal_fw_version_buf[2]); + } else if (g_u16_dev_id == DEVICE_ID_3X) { + handle_ic_read(0x00007b04, 4, g_u8_normal_fw_version_buf, g_u8_drv_interface, I2C_WORD_MODE); + DEBUGOUT("FW Version=0x%.2X%.2X%.2X%.2X\n", g_u8_normal_fw_version_buf[0], g_u8_normal_fw_version_buf[1], + g_u8_normal_fw_version_buf[3], g_u8_normal_fw_version_buf[2]); + } else { + DEBUGOUT("read ic namd fail\n"); + ret = WEARABLE_FT_TEST_RESULT_TEST_INIT_NG; + return ret; + } + + if (raydium_check_ini_version() != 0) + ret = WEARABLE_FT_TEST_RESULT_TEST_FW_VER_NG; + + return ret; +} + +int self_test_save_test_raw_data_to_file(int i32_ng_type) +{ + /*struct tm *time_infor;*/ + /*time_t raw_time;*/ + char write_string[1000]; + unsigned char u8_i, u8_j; + short *p_i16_buf = (short *)g_u16_raw_data_tmp; + + /*FW Version*/ + memset(write_string, 0, strlen(write_string)); + if (g_u16_dev_id != 0) { + snprintf(write_string, RM_SELF_TEST_MAX_STR_LENGTH, + "FW Version=0x%.2X%.2X%.2X%.2X\n", + g_u8_normal_fw_version_buf[0], g_u8_normal_fw_version_buf[1], + g_u8_normal_fw_version_buf[3], g_u8_normal_fw_version_buf[2]); + } + self_test_save_to_file(RM_SELF_TEST_LOGFILE, write_string, strlen(write_string)); + + /*Version*/ + memset(write_string, 0, strlen(write_string)); + snprintf(write_string, RM_SELF_TEST_MAX_STR_LENGTH, "Selftest Version=%x.%x.%x.%x.%x\n\n", + RM_SELF_TEST_CUSTOMER_VERSION, RM_SELF_TEST_PLATFORM_VERSION, + RM_SELF_TEST_PROJECT_VERSION, RM_SELF_TEST_MAIN_VERSION, + RM_SELF_TEST_SUB_VERSION); + self_test_save_to_file(RM_SELF_TEST_LOGFILE, write_string, strlen(write_string)); + + /*Test result*/ + memset(write_string, 0, strlen(write_string)); + snprintf(write_string, RM_SELF_TEST_MAX_STR_LENGTH, "Test Result = 0x%08X\n\n", + i32_ng_type); + self_test_save_to_file(RM_SELF_TEST_LOGFILE, write_string, strlen(write_string)); + + if (i32_ng_type == 0) { + memset(write_string, 0, strlen(write_string)); + snprintf(write_string, RM_SELF_TEST_MAX_STR_LENGTH, "All pass\n\n\n"); + self_test_save_to_file(RM_SELF_TEST_LOGFILE, write_string, strlen(write_string)); + } else { + memset(write_string, 0, strlen(write_string)); + + if (i32_ng_type & WEARABLE_FT_TEST_RESULT_SYSFS_NG) { + snprintf(write_string, + RM_SELF_TEST_MAX_STR_LENGTH, "System NG "); + } + + if (i32_ng_type & WEARABLE_FT_TEST_RESULT_I2C_NG) { + snprintf(write_string, + RM_SELF_TEST_MAX_STR_LENGTH, "I2C NG "); + } + + if (i32_ng_type & WEARABLE_FT_TEST_RESULT_INT_NG) { + snprintf(write_string, + RM_SELF_TEST_MAX_STR_LENGTH, "INT NG "); + } + + if (i32_ng_type & WEARABLE_FT_TEST_RESULT_RESET_NG) { + snprintf(write_string, + RM_SELF_TEST_MAX_STR_LENGTH, "RESET NG "); + } + + if (i32_ng_type & WEARABLE_FT_TEST_RESULT_PRAM_NG) { + snprintf(write_string, + RM_SELF_TEST_MAX_STR_LENGTH, "PRAM NG "); + } + + if (i32_ng_type & WEARABLE_FT_TEST_RESULT_NORMAL_FW_NG) { + snprintf(write_string, + RM_SELF_TEST_MAX_STR_LENGTH, "NORMAL_FW_NG "); + } + + if (i32_ng_type & WEARABLE_FT_TEST_RESULT_OPEN_NG) { + snprintf(write_string, + RM_SELF_TEST_MAX_STR_LENGTH, "OPEN NG "); + } + + if (i32_ng_type & WEARABLE_FT_TEST_RESULT_SHORT_NG) { + snprintf(write_string, + RM_SELF_TEST_MAX_STR_LENGTH, "SHORT NG "); + } + + if (i32_ng_type & WEARABLE_FT_TEST_RESULT_BURN_CC_NG) { + snprintf(write_string, + RM_SELF_TEST_MAX_STR_LENGTH, "BURN CC NG "); + } + + if (i32_ng_type & WEARABLE_FT_TEST_RESULT_GET_DATA_NG) { + snprintf(write_string, + RM_SELF_TEST_MAX_STR_LENGTH, "GET DATA NG "); + } + + if (i32_ng_type & WEARABLE_FT_TEST_RESULT_FLASH_ID_NG) { + snprintf(write_string, + RM_SELF_TEST_MAX_STR_LENGTH, "FLASH ID NG "); + } + + if (i32_ng_type & WEARABLE_FT_TEST_RESULT_NORMAL_FW_VER_NG) { + snprintf(write_string, + RM_SELF_TEST_MAX_STR_LENGTH, "NORMAL FW VER NG "); + } + + if (i32_ng_type & WEARABLE_FT_TEST_RESULT_TEST_FW_VER_NG) { + snprintf(write_string, + RM_SELF_TEST_MAX_STR_LENGTH, "TEST FW VER NG "); + } + + if (i32_ng_type & WEARABLE_FT_TEST_RESULT_TEST_INIT_NG) { + snprintf(write_string, + RM_SELF_TEST_MAX_STR_LENGTH, "TEST INIT NG "); + } + + if (i32_ng_type & WEARABLE_FT_TEST_RESULT_LOAD_TESTFW_NG) { + snprintf(write_string, + RM_SELF_TEST_MAX_STR_LENGTH, "LOAD TESTFW NG "); + } + + if (i32_ng_type & WEARABLE_FT_TEST_RESULT_BURN_FW_NG) { + snprintf(write_string, + RM_SELF_TEST_MAX_STR_LENGTH, "BURN FW NG "); + } + + if (i32_ng_type & WEARABLE_FT_TEST_RESULT_SINGLE_CC_OPEN_NG) { + snprintf(write_string, + RM_SELF_TEST_MAX_STR_LENGTH, "Open NG (Single Pin CC) "); + } + + if (i32_ng_type & WEARABLE_FT_TEST_RESULT_SINGLE_CC_SHORT_NG) { + snprintf(write_string, + RM_SELF_TEST_MAX_STR_LENGTH, "Short NG (Single Pin CC) "); + } + + if (i32_ng_type & WEARABLE_FT_TEST_RESULT_UB_NG) { + snprintf(write_string, + RM_SELF_TEST_MAX_STR_LENGTH, "Uniformity Baseline NG "); + } + + if (i32_ng_type & WEARABLE_FT_TEST_RESULT_UC_NG) { + snprintf(write_string, + RM_SELF_TEST_MAX_STR_LENGTH, "Uniformity CC NG "); + } + + snprintf(write_string, RM_SELF_TEST_MAX_STR_LENGTH, "\n"); + self_test_save_to_file(RM_SELF_TEST_LOGFILE, write_string, strlen(write_string)); + } + + /*Threshold*/ + memset(write_string, 0, strlen(write_string)); + /*snprintf(write_string, RM_SELF_TEST_MAX_STR_LENGTH, "0x%02X, 0x%02X\n0x%02X, + 0x%02X\n0x%02X, 0x%02X\n0x%02X, 0x%02X\n0x%02X, 0x%02X\n0x%02X, 0x%02X\n0x%02X, + 0x%02X\n0x%02X, 0x%02X\n0x%02X, 0x%02X\n", + (g_st_test_thd.i16_ft_test_open_lower_thd >> 8) & 0xFF, (g_st_test_thd.i16_ft_test_open_lower_thd & 0xFF), + (g_st_test_thd.i16_ft_test_short_upper_thd >> 8) & 0xFF, (g_st_test_thd.i16_ft_test_short_upper_thd & 0xFF), + (g_st_test_thd.i16_ft_test_short_lower_thd >> 8) & 0xFF, (g_st_test_thd.i16_ft_test_short_lower_thd & 0xFF), + (g_st_test_thd.i16_ft_test_single_cc_upper_thd >> 8) & 0xFF, (g_st_test_thd.i16_ft_test_single_cc_upper_thd & 0xFF), + (g_st_test_thd.i16_ft_test_single_cc_lower_thd >> 8) & 0xFF, (g_st_test_thd.i16_ft_test_single_cc_lower_thd & 0xFF), + (g_st_test_thd.i16_ft_test_uniformity_bl_upper_thd >> 8) & 0xFF, (g_st_test_thd.i16_ft_test_uniformity_bl_upper_thd & 0xFF), + (g_st_test_thd.i16_ft_test_uniformity_bl_lower_thd >> 8) & 0xFF, (g_st_test_thd.i16_ft_test_uniformity_bl_lower_thd & 0xFF), + (g_st_test_thd.i16_ft_test_uniformity_cc_upper_thd >> 8) & 0xFF, (g_st_test_thd.i16_ft_test_uniformity_cc_upper_thd & 0xFF), + (g_st_test_thd.i16_ft_test_uniformity_cc_lower_thd >> 8) & 0xFF, (g_st_test_thd.i16_ft_test_uniformity_cc_lower_thd & 0xFF));*/ + + self_test_save_to_file(RM_SELF_TEST_LOGFILE, write_string, strlen(write_string)); + + + for (u8_i = 0; u8_i < MAX_SENSING_PIN_NUM; u8_i++) { + memset(write_string, 0, strlen(write_string)); + snprintf(write_string, RM_SELF_TEST_MAX_STR_LENGTH, "0x%2X,", + g_u16_uc_golden_cc_buf[u8_i]); + self_test_save_to_file(RM_SELF_TEST_LOGFILE, write_string, strlen(write_string)); + } + + memset(write_string, 0, strlen(write_string)); + snprintf(write_string, RM_SELF_TEST_MAX_STR_LENGTH, "\n"); + self_test_save_to_file(RM_SELF_TEST_LOGFILE, write_string, strlen(write_string)); + + for (u8_i = 0; u8_i < MAX_SENSING_PIN_NUM; u8_i++) { + memset(write_string, 0, strlen(write_string)); + snprintf(write_string, RM_SELF_TEST_MAX_STR_LENGTH, "0x%2X,", + g_u16_raw_data3_golden_cc_buf[u8_i]); + self_test_save_to_file(RM_SELF_TEST_LOGFILE, write_string, strlen(write_string)); + } + + memset(write_string, 0, strlen(write_string)); + snprintf(write_string, RM_SELF_TEST_MAX_STR_LENGTH, "\n\n\n\n\n\n\n\n\n\n\n\n\n"); + self_test_save_to_file(RM_SELF_TEST_LOGFILE, write_string, strlen(write_string)); + + if ((i32_ng_type & 0xFFF0FBF8) < 4) { + + if ((g_st_test_info.u16_ft_test_item & (IC_TEST_ITEMS_SHORT)) != 0) { + /*Raw data*/ + /*Raw data slow*/ + memset(write_string, 0, strlen(write_string)); + snprintf(write_string, RM_SELF_TEST_MAX_STR_LENGTH, "\r\n\n\n"); + self_test_save_to_file(RM_SELF_TEST_LOGFILE, write_string, strlen(write_string)); + memset(write_string, 0, strlen(write_string)); + snprintf(write_string, RM_SELF_TEST_MAX_STR_LENGTH, "Raw Data 1\n"); + self_test_save_to_file(RM_SELF_TEST_LOGFILE, write_string, strlen(write_string)); + /*Raw data1*/ + memset(p_i16_buf, 0, MAX_IMAGE_BUFFER_SIZE * 2); + for (u8_i = 0; u8_i < MAX_IMAGE_BUFFER_SIZE; u8_i++) + if (g_u8_wearable_pin_map[u8_i] != F303_NA_P) + p_i16_buf[g_u8_wearable_pin_map[u8_i]] = g_i16_raw_data_1_short_buf[u8_i]; + + for (u8_j = 0; u8_j < g_u8_channel_y; u8_j++) { + for (u8_i = 0; u8_i < g_u8_channel_x; u8_i++) { + memset(write_string, 0, strlen(write_string)); + snprintf(write_string, RM_SELF_TEST_MAX_STR_LENGTH, + "%05d,", p_i16_buf[u8_i + u8_j * g_u8_channel_x]); + self_test_save_to_file(RM_SELF_TEST_LOGFILE, write_string, strlen(write_string)); + } + + memset(write_string, 0, strlen(write_string)); + snprintf(write_string, RM_SELF_TEST_MAX_STR_LENGTH, "\r\n"); + self_test_save_to_file(RM_SELF_TEST_LOGFILE, write_string, strlen(write_string)); + + } + } + if ((g_st_test_info.u16_ft_test_item & (IC_TEST_ITEMS_OPEN)) != 0) { + memset(write_string, 0, strlen(write_string)); + snprintf(write_string, RM_SELF_TEST_MAX_STR_LENGTH, "\r\n\n\n"); + self_test_save_to_file(RM_SELF_TEST_LOGFILE, write_string, strlen(write_string)); + + /*Raw data slow*/ + memset(write_string, 0, strlen(write_string)); + snprintf(write_string, RM_SELF_TEST_MAX_STR_LENGTH, "Raw Data 2\n"); + self_test_save_to_file(RM_SELF_TEST_LOGFILE, write_string, strlen(write_string)); + /*Raw data2*/ + memset(p_i16_buf, 0, MAX_IMAGE_BUFFER_SIZE * 2); + for (u8_i = 0; u8_i < MAX_IMAGE_BUFFER_SIZE; u8_i++) + if (g_u8_wearable_pin_map[u8_i] != F303_NA_P) + p_i16_buf[g_u8_wearable_pin_map[u8_i]] = g_i16_raw_data_2_open_buf[u8_i]; + + for (u8_j = 0; u8_j < g_u8_channel_y; u8_j++) { + for (u8_i = 0; u8_i < g_u8_channel_x; u8_i++) { + memset(write_string, 0, strlen(write_string)); + snprintf(write_string, RM_SELF_TEST_MAX_STR_LENGTH, + "%05d,", p_i16_buf[u8_i + u8_j * g_u8_channel_x]); + self_test_save_to_file(RM_SELF_TEST_LOGFILE, write_string, strlen(write_string)); + } + + memset(write_string, 0, strlen(write_string)); + snprintf(write_string, RM_SELF_TEST_MAX_STR_LENGTH, "\r\n"); + self_test_save_to_file(RM_SELF_TEST_LOGFILE, write_string, strlen(write_string)); + + } + } + if ((g_st_test_info.u16_ft_test_item & (IC_TEST_ITEMS_OPEN | IC_TEST_ITEMS_SHORT)) != 0) { + memset(write_string, 0, strlen(write_string)); + snprintf(write_string, RM_SELF_TEST_MAX_STR_LENGTH, "\r\n\n\n"); + self_test_save_to_file(RM_SELF_TEST_LOGFILE, write_string, strlen(write_string)); + + /*Raw data 3*/ + memset(write_string, 0, strlen(write_string)); + snprintf(write_string, RM_SELF_TEST_MAX_STR_LENGTH, "Raw Data 3\n"); + self_test_save_to_file(RM_SELF_TEST_LOGFILE, write_string, strlen(write_string)); + /*Raw data3*/ + memset(p_i16_buf, 0, MAX_IMAGE_BUFFER_SIZE * 2); + for (u8_i = 0; u8_i < MAX_IMAGE_BUFFER_SIZE; u8_i++) + if (g_u8_wearable_pin_map[u8_i] != F303_NA_P) + p_i16_buf[g_u8_wearable_pin_map[u8_i]] = g_u16_raw_data3_cc_buf[u8_i]; + + for (u8_j = 0; u8_j < g_u8_channel_y; u8_j++) { + for (u8_i = 0; u8_i < g_u8_channel_x; u8_i++) { + memset(write_string, 0, strlen(write_string)); + snprintf(write_string, RM_SELF_TEST_MAX_STR_LENGTH, + "%05d,", p_i16_buf[u8_i + u8_j * g_u8_channel_x]); + self_test_save_to_file(RM_SELF_TEST_LOGFILE, write_string, strlen(write_string)); + } + + memset(write_string, 0, strlen(write_string)); + snprintf(write_string, RM_SELF_TEST_MAX_STR_LENGTH, "\r\n"); + self_test_save_to_file(RM_SELF_TEST_LOGFILE, write_string, strlen(write_string)); + + } + } + if ((g_st_test_info.u16_ft_test_item & (IC_TEST_ITEMS_UC)) != 0) { + memset(write_string, 0, strlen(write_string)); + snprintf(write_string, RM_SELF_TEST_MAX_STR_LENGTH, "\r\n\n\n"); + self_test_save_to_file(RM_SELF_TEST_LOGFILE, write_string, strlen(write_string)); + + /*Raw data Uniformity CC*/ + memset(write_string, 0, strlen(write_string)); + snprintf(write_string, RM_SELF_TEST_MAX_STR_LENGTH, "Raw Data_UC\n"); + self_test_save_to_file(RM_SELF_TEST_LOGFILE, write_string, strlen(write_string)); + /*Raw data uc*/ + memset(p_i16_buf, 0, MAX_IMAGE_BUFFER_SIZE * 2); + for (u8_i = 0; u8_i < MAX_IMAGE_BUFFER_SIZE; u8_i++) + if (g_u8_wearable_pin_map[u8_i] != F303_NA_P) + p_i16_buf[g_u8_wearable_pin_map[u8_i]] = g_u16_uc_buf[u8_i]; + + DEBUGOUT("Image:0x%x\r\n", p_i16_buf[0]); + + for (u8_j = 0; u8_j < g_u8_channel_y; u8_j++) { + for (u8_i = 0; u8_i < g_u8_channel_x; u8_i++) { + memset(write_string, 0, strlen(write_string)); + snprintf(write_string, RM_SELF_TEST_MAX_STR_LENGTH, + "%05d,", p_i16_buf[u8_i + u8_j * g_u8_channel_x]); + self_test_save_to_file(RM_SELF_TEST_LOGFILE, write_string, strlen(write_string)); + } + + memset(write_string, 0, strlen(write_string)); + snprintf(write_string, RM_SELF_TEST_MAX_STR_LENGTH, "\r\n"); + self_test_save_to_file(RM_SELF_TEST_LOGFILE, write_string, strlen(write_string)); + + } + } + } + + memset(write_string, 0, strlen(write_string)); + snprintf(write_string, RM_SELF_TEST_MAX_STR_LENGTH, "\r\n\n\n"); + self_test_save_to_file(RM_SELF_TEST_LOGFILE, write_string, strlen(write_string)); + + return 1; +} + +int self_test_read_setting_from_file(void) +{ + unsigned short u16_offset = 0; + + DEBUGOUT("[touch]g_raydium_ts->id = 0x%x\r\n", g_raydium_ts->id); + switch (g_raydium_ts->id) { + case RAD_SELFTEST_30: + u16_offset = 0; + memcpy(&g_u8_ini_flash[u16_offset], &u8_test_info_30, sizeof(u8_test_info_30)); + u16_offset += 16; + memcpy(&g_u8_ini_flash[u16_offset], &i8_ft_test_thd_30, sizeof(i8_ft_test_thd_30)); + u16_offset += 36; + memcpy(&g_u8_ini_flash[u16_offset], &u8_test_para_30, sizeof(u8_test_para_30)); + u16_offset += 48; + u16_offset += 128;/*reserve for BL*/ + memcpy(&g_u8_ini_flash[u16_offset], &u8_raw_data_3_cc_30, sizeof(u8_raw_data_3_cc_30)); + u16_offset += 128; + memcpy(&g_u8_ini_flash[u16_offset], &u8_raw_uc_cc_30, sizeof(u8_raw_uc_cc_30)); + u16_offset += 128; + + memcpy((void *)(&g_st_test_para_resv), &u8_test_para_30[0], sizeof(g_st_test_para_resv)); + DEBUGOUT("ini length = %d\r\n", u16_offset); + break; +#if defined(FW_MAPPING_EN) + case RAD_SELFTEST_31: + u16_offset = 0; + memcpy(&g_u8_ini_flash[u16_offset], &u8_test_info_31, sizeof(u8_test_info_31)); + u16_offset += 16; + memcpy(&g_u8_ini_flash[u16_offset], &i8_ft_test_thd_31, sizeof(i8_ft_test_thd_31)); + u16_offset += 36; + memcpy(&g_u8_ini_flash[u16_offset], &u8_test_para_31, sizeof(u8_test_para_31)); + u16_offset += 48; + u16_offset += 128;/*reserve for BL*/ + memcpy(&g_u8_ini_flash[u16_offset], &u8_raw_data_3_cc_31, sizeof(u8_raw_data_3_cc_31)); + u16_offset += 128; + memcpy(&g_u8_ini_flash[u16_offset], &u8_raw_uc_cc_31, sizeof(u8_raw_uc_cc_31)); + u16_offset += 128; + + memcpy((void *)(&g_st_test_para_resv), &u8_test_para_31[0], sizeof(g_st_test_para_resv)); + DEBUGOUT("ini length = %d\r\n", u16_offset); + break; +#endif + } + + return 0; +} + +int raydium_do_selftest(struct raydium_ts_data *ts) +{ + int ret = RESULT_SUCCESS; + unsigned int time_start, time_end, time_start2, time_end2; + + time_start = get_system_time(); + + pr_info("Selftest Version=%x.%x.%x.%x.%x\n", RM_SELF_TEST_CUSTOMER_VERSION, RM_SELF_TEST_PLATFORM_VERSION, + RM_SELF_TEST_PROJECT_VERSION, RM_SELF_TEST_MAIN_VERSION, RM_SELF_TEST_SUB_VERSION); + + self_test_read_setting_from_file(); + ic_drv_init(); + set_raydium_ts_data(ts); + + ret = self_test_init(); + if (ret != 0) + DEBUGOUT("mapping ic fw fail\n"); + else { + DEBUGOUT("Test all\n"); + ret |= self_test_all(); + } + + if (ret != WEARABLE_FT_TEST_RESULT_SYSFS_NG) { + gpio_touch_hw_reset(); + g_u8_raydium_flag &= ~ENG_MODE; + } + + raydium_i2c_mode_control(ts->client, ENABLE_TOUCH_INT); +#if ENABLE_TIME_MEASURMENT + time_start2 = get_system_time(); +#endif + self_test_save_test_raw_data_to_file(ret); + +#if ENABLE_TIME_MEASURMENT + time_end2 = get_system_time(); + DEBUGOUT("Write log Finish(%ums)\n", time_end2 - time_start2); +#endif + if (ret != 0) { + DEBUGOUT("Selftest Test Result=0x%x\n", ret); + ret = RESULT_NG; + DEBUGOUT("Selftest Result=%d\n", ret); + } else { + DEBUGOUT("Selftest Pass ^_^!!!\n"); + ret = RESULT_SUCCESS; + } + + time_end = get_system_time(); + DEBUGOUT("All Test Finish(%ums)\n", time_end - time_start); + + + return ret; +} diff --git a/qcom/opensource/touch-drivers/raydium/raydium_selftest.h b/qcom/opensource/touch-drivers/raydium/raydium_selftest.h new file mode 100644 index 0000000000..c6bfd79582 --- /dev/null +++ b/qcom/opensource/touch-drivers/raydium/raydium_selftest.h @@ -0,0 +1,34 @@ +/* raydium_selftest.h + * + * Raydium TouchScreen driver. + * + * Copyright (c) 2021 Raydium tech Ltd. + * + * 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. + * + */ + +#define DISABLE_PDA2_MODE_AND_INT 0 +#define ENABLE_PDA2_MODE_AND_INT 1 +#define DISABLE_TOUCH_INT 2 +#define ENABLE_TOUCH_INT 3 + +#define ENABLE_TIME_MEASURMENT 1 +/*Raydium system flag*/ +#define RAYDIUM_INTERRUPT_FLAG 0x01 + + +#define RM_SELF_TEST_THRESHOLD_FILE "/data/selftest/tpselftest.ini" +#define RM_SELF_TEST_LOGFILE "/sdcard/selftest.csv" + + +extern const unsigned char u8_rad_testpara_20[]; + diff --git a/qcom/opensource/touch-drivers/raydium/raydium_sysfs.c b/qcom/opensource/touch-drivers/raydium/raydium_sysfs.c new file mode 100644 index 0000000000..557f9161e5 --- /dev/null +++ b/qcom/opensource/touch-drivers/raydium/raydium_sysfs.c @@ -0,0 +1,1686 @@ +/*raydium_sysfs.c + * + * Raydium TouchScreen driver. + * + * Copyright (c) 2021 Raydium tech Ltd. + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "raydium_driver.h" +#include + + +static void raydium_ts_touch_entry(void); +static void raydium_ts_touch_exit(void); +static int raydium_ts_gpio_config(bool on); + +uint32_t slate_ack_resp; + +static ssize_t raydium_touch_calibration_show(struct device *dev, + struct device_attribute *attr, + char *p_i8_buf) +{ + unsigned char u8_rbuffer[1]; + unsigned short u16_len = 0; + int i32_ret = -1; + unsigned char u8_retry = 0; + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + + if (g_raydium_ts->is_suspend) + LOGD(LOG_DEBUG, "[touch]RAD is_suspend at %s\n", __func__); + + g_u8_raydium_flag |= ENG_MODE; + + mutex_lock(&g_raydium_ts->lock); + + i32_ret = raydium_i2c_pda2_set_page(client, + g_raydium_ts->is_suspend, + RAYDIUM_PDA2_PAGE_0); + if (i32_ret < 0) + goto exit_i2c_error; + + u8_rbuffer[0] = RAYDIUM_HOST_CMD_CALIBRATION; + i32_ret = raydium_i2c_pda2_write(client, RAYDIUM_PDA2_HOST_CMD_ADDR, + u8_rbuffer, 1); + if (i32_ret < 0) + goto exit_i2c_error; + + do { + if (u8_rbuffer[0] == RAYDIUM_HOST_CMD_NO_OP) + break; + + msleep(1000); + + i32_ret = raydium_i2c_pda2_read(client, + RAYDIUM_PDA2_HOST_CMD_ADDR, + u8_rbuffer, 1); + if (i32_ret < 0) + goto exit_i2c_error; + + LOGD(LOG_INFO, "[touch]RAD %s return 0x%02x!!\n", + __func__, u8_rbuffer[0]); + } while (u8_retry++ < (SYN_I2C_RETRY_TIMES * 2)); + + memcpy(p_i8_buf, u8_rbuffer, 1); + + u16_len = strlen(p_i8_buf); + i32_ret = u16_len + 1; + +exit_i2c_error: + mutex_unlock(&g_raydium_ts->lock); + g_u8_raydium_flag &= ~ENG_MODE; + return i32_ret; +} + +static ssize_t raydium_i2c_pda_access_show(struct device *dev, + struct device_attribute *attr, + char *p_i8_buf) +{ + unsigned char u8_rbuffer[4]; + unsigned short u16_len = 0; + int i32_ret = -1; + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + + if (g_u32_length > 4) + return -EINVAL; + memset(u8_rbuffer, 0x00, 4); + mutex_lock(&g_raydium_ts->lock); + i32_ret = raydium_i2c_pda_read(client, + g_u32_addr, + u8_rbuffer, + g_u32_length); + mutex_unlock(&g_raydium_ts->lock); + if (i32_ret < 0) + return i32_ret; + + snprintf(p_i8_buf, PAGE_SIZE, "0x%08X : 0x%02X%02X%02X%02X\n", + (unsigned int)g_u32_addr, u8_rbuffer[3], u8_rbuffer[2], + u8_rbuffer[1], u8_rbuffer[0]); + u16_len = strlen(p_i8_buf); + + return u16_len + 1; +} + + +static ssize_t raydium_i2c_pda_access_via_pda2_show(struct device *dev, + struct device_attribute *attr, + char *p_i8_buf) +{ + unsigned char u8_rbuffer[4]; + unsigned short u16_len = 0; + int i32_ret = -1; + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + + if (g_u32_length > 4) + return -EINVAL; + memset(u8_rbuffer, 0x00, 4); + mutex_lock(&g_raydium_ts->lock); + i32_ret = handle_i2c_pda_read(client, + g_u32_addr, + u8_rbuffer, + g_u32_length); + mutex_unlock(&g_raydium_ts->lock); + if (i32_ret < 0) + return i32_ret; + + snprintf(p_i8_buf, PAGE_SIZE, "0x%08X : 0x%02X%02X%02X%02X\n", + (unsigned int)g_u32_addr, u8_rbuffer[3], u8_rbuffer[2], + u8_rbuffer[1], u8_rbuffer[0]); + u16_len = strlen(p_i8_buf); + + return u16_len + 1; +} + +static ssize_t raydium_check_i2c_show(struct device *dev, + struct device_attribute *attr, + char *p_i8_buf) +{ + unsigned char u8_rbuffer[4]; + unsigned short u16_len = 0; + int i32_ret = -1; + + if (g_raydium_ts->is_suspend) + LOGD(LOG_DEBUG, "[touch]RAD is_suspend at %s\n", __func__); + + mutex_lock(&g_raydium_ts->lock); + i32_ret = handle_i2c_pda_read(g_raydium_ts->client, + RAYDIUM_CHK_I2C_CMD, u8_rbuffer, 4); + if (i32_ret < 0) + goto exit_i2c_error; + + snprintf(p_i8_buf, PAGE_SIZE, "[touch]RAD Touch check i2c: %02X%02X\n", + u8_rbuffer[3], u8_rbuffer[2]); + u16_len = strlen(p_i8_buf); + i32_ret = u16_len + 1; + +exit_i2c_error: + mutex_unlock(&g_raydium_ts->lock); + return i32_ret; +} + +static ssize_t raydium_hw_reset_show(struct device *dev, + struct device_attribute *attr, + char *p_i8_buf) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + int i32_ret = SUCCESS; + + + LOGD(LOG_INFO, "[touch]HW reset\n"); + g_u8_resetflag = true; + g_u8_raydium_flag |= ENG_MODE; + + /*HW reset*/ + if (gpio_is_valid(g_raydium_ts->rst_gpio)) { + gpio_set_value(g_raydium_ts->rst_gpio, 1); + gpio_set_value(g_raydium_ts->rst_gpio, 0); + msleep(RAYDIUM_RESET_INTERVAL_MSEC); + gpio_set_value(g_raydium_ts->rst_gpio, 1); + } + + g_u8_i2c_mode = PDA2_MODE; + if (raydium_disable_i2c_deglitch() == ERROR) + LOGD(LOG_ERR, "[touch]disable_i2c_deglitch_3x NG!\r\n"); + + i32_ret = wait_irq_state(client, 1000, 2000); + if (i32_ret != ERROR) + msleep(35); + + g_u8_raydium_flag &= ~ENG_MODE; + + snprintf(p_i8_buf, PAGE_SIZE, "Raydium HW Reset : %d\n", i32_ret); + LOGD(LOG_INFO, "[touch]%s\n", p_i8_buf); + return strlen(p_i8_buf) + 1; +} + +static ssize_t raydium_palm_status_show(struct device *dev, + struct device_attribute *attr, + char *p_i8_buf) +{ + unsigned short u16_len = 0; + + unsigned char u8_tp_status[MAX_TCH_STATUS_PACKET_SIZE]; + unsigned char u8_tp_buf[MAX_REPORT_PACKET_SIZE]; + + raydium_read_touchdata(u8_tp_status, u8_tp_buf); + snprintf(p_i8_buf, PAGE_SIZE, "[touch] palm_status : %d\n", + u8_tp_status[POS_GES_STATUS]); + + u16_len = strlen(p_i8_buf); + return u16_len + 1; +} + + +static int raydium_ts_gpio_config(bool on) +{ + int i32_err = 0; + + if (on) { + if (gpio_is_valid(g_raydium_ts->irq_gpio)) { + i32_err = gpio_request(g_raydium_ts->irq_gpio, + "raydium_irq_gpio"); + if (i32_err) { + LOGD(LOG_ERR, "[touch]irq gpio request failed"); + goto err_irq_gpio_req; + } + + i32_err = gpio_direction_input(g_raydium_ts->irq_gpio); + if (i32_err) { + LOGD(LOG_ERR, "[touch]set_direction for irq gpio failed\n"); + goto err_irq_gpio_dir; + } + } + if (gpio_is_valid(g_raydium_ts->rst_gpio)) { + i32_err = gpio_request(g_raydium_ts->rst_gpio, + "raydium_rst_gpio"); + if (i32_err) { + LOGD(LOG_ERR, "[touch]rst gpio request failed"); + goto err_irq_gpio_req; + } + + i32_err = gpio_direction_output(g_raydium_ts->rst_gpio, 0); + msleep(RAYDIUM_RESET_INTERVAL_10MSEC); + if (i32_err) { + LOGD(LOG_ERR, + "[touch]set_direction for rst gpio failed\n"); + goto err_rst_gpio_dir; + } + + i32_err = gpio_direction_output(g_raydium_ts->rst_gpio, 1); + if (i32_err) { + LOGD(LOG_ERR, + "[touch]set_direction for irq gpio failed\n"); + goto err_rst_gpio_dir; + } + } + } else { + if (gpio_is_valid(g_raydium_ts->irq_gpio)) + gpio_free(g_raydium_ts->irq_gpio); + } + return 0; +err_rst_gpio_dir: + if (gpio_is_valid(g_raydium_ts->rst_gpio)) + gpio_free(g_raydium_ts->rst_gpio); + return i32_err; +err_irq_gpio_dir: + if (gpio_is_valid(g_raydium_ts->irq_gpio)) + gpio_free(g_raydium_ts->irq_gpio); +err_irq_gpio_req: + return i32_err; +} + +static void raydium_ts_touch_entry(void) +{ + void *glink_send_msg; + unsigned char u8_i = 0; + + int glink_touch_enter_prep = TOUCH_ENTER_PREPARE; + int glink_touch_enter = TOUCH_ENTER; + int rc = 0; + + LOGD(LOG_INFO, "%s[touch] raydium_ts_touch_entry Start\n", __func__); + + /*glink touch enter prepare cmd */ + glink_send_msg = &glink_touch_enter_prep; + LOGD(LOG_INFO, "[touch] glink_send_msg = %0x\n", glink_send_msg); + glink_touch_tx_msg(glink_send_msg, TOUCH_MSG_SIZE); + + if (slate_ack_resp != 0) { + rc = -EINVAL; + goto err_ret; + } + /*glink touch enter cmd */ + glink_send_msg = &glink_touch_enter; + LOGD(LOG_INFO, "[touch]glink_send_msg = %0x\n", glink_send_msg); + glink_touch_tx_msg(glink_send_msg, TOUCH_MSG_SIZE); + + if(slate_ack_resp == 0) { + //Release the gpio's + if (gpio_is_valid(g_raydium_ts->rst_gpio)) + gpio_free(g_raydium_ts->rst_gpio); + + if (gpio_is_valid(g_raydium_ts->irq_gpio)) + gpio_free(g_raydium_ts->irq_gpio); + +#ifdef GESTURE_EN + if (device_may_wakeup(&g_raydium_ts->client->dev)) { + LOGD(LOG_INFO, "[touch]%s Device may wakeup\n", __func__); + if (g_raydium_ts->irq_wake) { + disable_irq_wake(g_raydium_ts->irq); + g_raydium_ts->irq_wake = false; + } + } else + LOGD(LOG_INFO, "[touch]%s Device not wakeup\n", __func__); +#endif + + raydium_irq_control(DISABLE); + + if (!cancel_work_sync(&g_raydium_ts->work)) + LOGD(LOG_DEBUG, "[touch]workqueue is empty!\n"); + + /* release all touches */ + for (u8_i = 0; u8_i < g_raydium_ts->u8_max_touchs; u8_i++) { + pr_err("[touch]%s 1111\n", __func__); + input_mt_slot(g_raydium_ts->input_dev, u8_i); + input_mt_report_slot_state(g_raydium_ts->input_dev, + MT_TOOL_FINGER, + false); + } + + input_mt_report_pointer_emulation(g_raydium_ts->input_dev, false); + input_sync(g_raydium_ts->input_dev); + } + + LOGD(LOG_INFO, "%s[touch] raydium_ts_touch_entry Start End\n", __func__); +err_ret: + return; +} + + +static void raydium_ts_touch_exit(void) +{ + + int ret = 0, rc = 0; + void *glink_send_msg; + int glink_touch_exit_prep = TOUCH_EXIT_PREPARE; + int glink_touch_exit = TOUCH_EXIT; + + + LOGD(LOG_INFO, "%s[touch]raydium_ts_touch_exit Start\n", __func__); + + /*glink touch exit prepare cmd */ + glink_send_msg = &glink_touch_exit_prep; + LOGD(LOG_INFO, "[touch]glink_send_msg = %0x\n", glink_send_msg); + glink_touch_tx_msg(glink_send_msg, TOUCH_MSG_SIZE); + + if (slate_ack_resp != 0) { + rc = -EINVAL; + goto err_ret; + } + + else if(slate_ack_resp == 0) { + //Configure the gpio's + ret = raydium_ts_gpio_config(true); + if (ret < 0) { + LOGD(LOG_ERR, "[touch]failed to configure the gpios\n"); + goto err_ret; + } + } + + /*glink touch exit cmd */ + glink_send_msg = &glink_touch_exit; + LOGD(LOG_INFO, "[touch]glink_send_msg = %d\n", glink_send_msg); + glink_touch_tx_msg(glink_send_msg, TOUCH_MSG_SIZE); + + LOGD(LOG_INFO, "%s[touch] raydium_ts_touch_exit End\n", __func__); +err_ret: + return; + +} + +static ssize_t raydium_touch_offload_store(struct device *dev, + struct device_attribute *attr, + const char *p_i8_buf, size_t count) +{ + int i32_ret = 0; + unsigned char u8_mode; + + /* receive command line arguments string */ + if (count > 2) + return -EINVAL; + + i32_ret = kstrtou8(p_i8_buf, 16, &u8_mode); + if (i32_ret < 0) + return i32_ret; + + switch (u8_mode) { + case 0: /* Disable Touch offload */ + + LOGD(LOG_INFO, "[touch]RAD %s disable touch offload!!\n", __func__); + raydium_ts_touch_entry(); + g_raydium_ts->touch_offload = true; + break; + + case 1: /* Enable Touch offload */ + + LOGD(LOG_INFO, "[touch]RAD %s enable touch offload!!\n", __func__); + raydium_ts_touch_exit(); + g_raydium_ts->touch_offload = false; + break; + } + + return count; +} + +static ssize_t raydium_touch_offload_show(struct device *dev, + struct device_attribute *attr, + char *p_i8_buf) +{ + return snprintf(p_i8_buf, PAGE_SIZE, + "Touch offload : %s\n", + (g_raydium_ts->touch_offload)? "Enabled" : "Disabled"); + +} +static ssize_t raydium_touch_lock_store(struct device *dev, + struct device_attribute *attr, + const char *p_i8_buf, size_t count) +{ + int i32_ret = 0; + unsigned char u8_mode; + unsigned char u8_wbuffer[1]; + + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + + if (g_raydium_ts->is_suspend) + LOGD(LOG_DEBUG, "[touch]RAD is_suspend at %s\n", __func__); + + /* receive command line arguments string */ + if (count > 2) + return -EINVAL; + + i32_ret = kstrtou8(p_i8_buf, 16, &u8_mode); + if (i32_ret < 0) + return i32_ret; + g_u8_raydium_flag |= ENG_MODE; + mutex_lock(&g_raydium_ts->lock); + + switch (u8_mode) { + case 0: /* Disable Touch lock */ + + if (g_raydium_ts->is_sleep != 1) + break; + g_u8_resetflag = true; + if (gpio_is_valid(g_raydium_ts->rst_gpio)) { + gpio_set_value(g_raydium_ts->rst_gpio, 1); + gpio_set_value(g_raydium_ts->rst_gpio, 0); + msleep(RAYDIUM_RESET_INTERVAL_MSEC);/*5ms*/ + gpio_set_value(g_raydium_ts->rst_gpio, 1); + msleep(RAYDIUM_RESET_DELAY_MSEC);/*100ms*/ + } + LOGD(LOG_INFO, "[touch]RAD %s disable touch lock!!\n", __func__); + + g_raydium_ts->is_sleep = 0; + break; + + case 1: /* Enable Touch lock */ + + if (g_raydium_ts->is_sleep == 1) + break; + i32_ret = raydium_i2c_pda2_set_page(client, + g_raydium_ts->is_suspend, + RAYDIUM_PDA2_PAGE_0); + if (i32_ret < 0) + goto exit_i2c_error; + /*fw enter sleep mode*/ + u8_wbuffer[0] = RAYDIUM_HOST_CMD_PWR_SLEEP; + i32_ret = raydium_i2c_pda2_write(client, + RAYDIUM_PDA2_HOST_CMD_ADDR, + u8_wbuffer, + 1); + if (i32_ret < 0) + goto exit_i2c_error; + + LOGD(LOG_INFO, "[touch]RAD %s enable touch lock!!\n", __func__); + g_raydium_ts->is_sleep = 1; + break; + } + +exit_i2c_error: + mutex_unlock(&g_raydium_ts->lock); + g_u8_raydium_flag &= ~ENG_MODE; + return count; +} + +static ssize_t raydium_check_driver_version_show(struct device *dev, + struct device_attribute *attr, + char *p_i8_buf) +{ + /*unsigned char rbuffer[4];*/ + unsigned short u16_len = 0; + int i32_ret = -1; + + snprintf(p_i8_buf, PAGE_SIZE, "RAD Driver Ver: 0x%X\n", + g_u32_driver_version); + + u16_len = strlen(p_i8_buf); + i32_ret = u16_len + 1; + return i32_ret; +} + +static ssize_t raydium_check_fw_version_show(struct device *dev, + struct device_attribute *attr, + char *p_i8_buf) +{ + unsigned char u8_rbuffer[4]; + unsigned short u16_len = 0; + int i32_ret = -1; + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + + unsigned int fw_version, image_version; + + if (g_raydium_ts->is_suspend) + LOGD(LOG_DEBUG, "[touch]RAD is_suspend at %s\n", __func__); + + g_u8_raydium_flag |= ENG_MODE; + mutex_lock(&g_raydium_ts->lock); + + i32_ret = raydium_i2c_pda2_set_page(client, + g_raydium_ts->is_suspend, + RAYDIUM_PDA2_PAGE_0); + if (i32_ret < 0) + goto exit_i2c_error; + i32_ret = raydium_i2c_pda2_read(client, RAYDIUM_PDA2_FW_VERSION_ADDR, + u8_rbuffer, 4); + if (i32_ret < 0) + goto exit_i2c_error; + snprintf(p_i8_buf, PAGE_SIZE, "RAD Touch FW Ver : %02X%02X%02X%02X\n", + u8_rbuffer[0], u8_rbuffer[1], u8_rbuffer[2], u8_rbuffer[3]); + + fw_version = (u8_rbuffer[0] << 24) + | (u8_rbuffer[1] << 16) + | (u8_rbuffer[2] << 8) + | u8_rbuffer[3]; + LOGD(LOG_INFO, "[touch]RAD FW ver : 0x%x\n", fw_version); + + image_version = (g_rad_para_image[PARA_FW_VERSION_OFFSET] << 24) | + (g_rad_para_image[PARA_FW_VERSION_OFFSET + 1] << 16) | + (g_rad_para_image[PARA_FW_VERSION_OFFSET + 2] << 8) | + g_rad_para_image[PARA_FW_VERSION_OFFSET + 3]; + + LOGD(LOG_INFO, "[touch]RAD Image FW ver : 0x%x\n", image_version); + + mutex_unlock(&g_raydium_ts->lock); + g_u8_raydium_flag &= ~ENG_MODE; + + if (fw_version != image_version) + LOGD(LOG_INFO, "[touch]%s, FW need upgrade.\n", __func__); + + u16_len = strlen(p_i8_buf); + i32_ret = u16_len + 1; + goto exit_upgrade; + +exit_i2c_error: + mutex_unlock(&g_raydium_ts->lock); + g_u8_raydium_flag &= ~ENG_MODE; + +exit_upgrade: + return i32_ret; +} + +static ssize_t raydium_check_panel_version_show(struct device *dev, + struct device_attribute *attr, + char *p_i8_buf) +{ + unsigned char u8_rbuffer[8]; + unsigned short u16_len = 0; + int i32_ret = -1; + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + + if (g_raydium_ts->is_suspend) + LOGD(LOG_DEBUG, "[touch]RAD is_suspend at %s\n", __func__); + g_u8_raydium_flag |= ENG_MODE; + mutex_lock(&g_raydium_ts->lock); + + i32_ret = raydium_i2c_pda2_set_page(client, + g_raydium_ts->is_suspend, + RAYDIUM_PDA2_PAGE_0); + if (i32_ret < 0) + goto exit_i2c_error; + i32_ret = raydium_i2c_pda2_read(client, + RAYDIUM_PDA2_PANEL_VERSION_ADDR, + u8_rbuffer, 8); + if (i32_ret < 0) + goto exit_i2c_error; + snprintf(p_i8_buf, PAGE_SIZE, + "RAD Touch Panel Version : %02X%02X%02X%02X%02X%02X\n", + u8_rbuffer[0], u8_rbuffer[1], u8_rbuffer[2], + u8_rbuffer[3], u8_rbuffer[4], u8_rbuffer[5]); + mutex_unlock(&g_raydium_ts->lock); + g_u8_raydium_flag &= ~ENG_MODE; + + u16_len = strlen(p_i8_buf); + i32_ret = u16_len + 1; + goto exit_upgrade; + +exit_i2c_error: + mutex_unlock(&g_raydium_ts->lock); + g_u8_raydium_flag &= ~ENG_MODE; +exit_upgrade: + return i32_ret; +} + +static ssize_t raydium_fw_upgrade_store(struct device *dev, + struct device_attribute *attr, + const char *p_i8_buf, size_t count) +{ + int i32_ret = 0; + + /* receive command line arguments string */ + if (count > 2) + return -EINVAL; + + i32_ret = kstrtou8(p_i8_buf, 16, &g_u8_upgrade_type); + if (i32_ret < 0) + return i32_ret; + + return count; +} + +static ssize_t raydium_fw_upgrade_show(struct device *dev, + struct device_attribute *attr, + char *p_i8_buf) +{ + int i32_ret = 0, i32_result = FAIL; + unsigned short u16_len = 0; + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + + LOGD(LOG_INFO, "[touch]RAD burn type is %d\n", g_u8_upgrade_type); + + g_u8_raydium_flag |= ENG_MODE; + + if ((g_u8_table_setting == 1) && (g_u8_table_init == 1)) + raydium_mem_table_setting(); + + if (g_u8_upgrade_type == 1) { + i32_ret = raydium_burn_fw(client); + if (i32_ret < 0) + goto exit_upgrade; + + i32_result = SUCCESS; + } else if (g_u8_upgrade_type == 2) { + i32_ret = raydium_burn_comp(client); + if (i32_ret < 0) + goto exit_upgrade; + + i32_result = SUCCESS; + } +#ifdef RAD_SELFTEST + else if (g_u8_upgrade_type == 4) { + i32_ret = raydium_load_test_fw(client); + if (i32_ret < 0) + goto exit_upgrade; + + i32_result = SUCCESS; + } +#endif + +exit_upgrade: + LOGD(LOG_DEBUG, "[touch]g_u8_raydium_flag : %d", g_u8_raydium_flag); + g_u8_raydium_flag &= ~ENG_MODE; + g_u8_upgrade_type = 0; + + snprintf(p_i8_buf, PAGE_SIZE, "FW Upgrade result : %d\n", i32_result); + u16_len = strlen(p_i8_buf); + return u16_len + 1; +} +static ssize_t raydium_i2c_pda2_page_store(struct device *dev, + struct device_attribute *attr, + const char *p_i8_buf, size_t count) +{ + int i32_ret = 0; + unsigned char u8_page = 0; + char *temp_buf, *token, *free_temp_buf, *free_token; + const char *delim = " ,"; + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + + /* receive command line arguments string */ + if (count < 2) + return -EINVAL; + temp_buf = kzalloc(count + 1, GFP_KERNEL); + if (temp_buf == NULL) + return -ENOMEM; + + token = kzalloc(count + 1, GFP_KERNEL); + if (token == NULL) { + kfree(temp_buf); + return -ENOMEM; + } + + free_temp_buf = temp_buf; + free_token = token; + + strlcpy(temp_buf, p_i8_buf, count); + + token = strsep(&temp_buf, delim); + + if (temp_buf) { + LOGD(LOG_ERR, "[touch]input error, extra auguments!n"); + i32_ret = -EINVAL; + goto exit_error; + } + i32_ret = kstrtou8(token, 16, &u8_page); + + if (i32_ret < 0) + goto exit_error; + + mutex_lock(&g_raydium_ts->lock); + + i32_ret = raydium_i2c_pda2_set_page(client, g_raydium_ts->is_suspend, u8_page); + if (i32_ret < 0) + goto exit_set_error; + + /* TODO: Page check, Due to ISR will change page back to Page_0. + * Or disable IRQ during PDA2 access period + */ + +exit_set_error: + mutex_unlock(&g_raydium_ts->lock); + +exit_error: + kfree(free_token); + kfree(free_temp_buf); + return count; +} + +static ssize_t raydium_mem_store(struct device *dev, + struct device_attribute *attr, + const char *p_i8_buf, size_t count) +{ +#if !ENABLE_FW_LOADER + int i32_ret = 0; + unsigned char u8_type = 0; + unsigned int u32_image_version; + + /* receive command line arguments string */ + if (count > 2) + return -EINVAL; + + LOGD(LOG_INFO, "[touch]%s\n", __func__); + + i32_ret = kstrtou8(p_i8_buf, 16, &u8_type); + if (i32_ret < 0) + return i32_ret; + + if (u8_type > 3) { + LOGD(LOG_ERR, "[touch]Input invalid value!!\n"); + return ERROR; + } + + if (g_rad_boot_image) { + kfree(g_rad_boot_image); + g_rad_boot_image = NULL; + } + if (g_rad_init_image) { + kfree(g_rad_init_image); + g_rad_init_image = NULL; + } + if (g_rad_fw_image) { + kfree(g_rad_fw_image); + g_rad_fw_image = NULL; + } + + kfree(g_rad_para_image); + g_rad_para_image = NULL; + + if (g_rad_testfw_image) { + kfree(g_rad_testfw_image); + g_rad_testfw_image = NULL; + } + + kfree(g_rad_testpara_image); + g_rad_testpara_image = NULL; + + if (!raydium_id_init(u8_type)) { + LOGD(LOG_ERR, "[touch]Set Raydium id failed!\n"); + return count; + } + + raydium_mem_table_init(g_raydium_ts->id); + if (raydium_mem_table_setting()) { + u32_image_version = (g_rad_para_image[PARA_FW_VERSION_OFFSET] << 24) | + (g_rad_para_image[PARA_FW_VERSION_OFFSET + 1] << 16) | + (g_rad_para_image[PARA_FW_VERSION_OFFSET + 2] << 8) | + g_rad_para_image[PARA_FW_VERSION_OFFSET + 3]; + + LOGD(LOG_INFO, "[touch]RAD Image FW ver : 0x%x\n", u32_image_version); + } else + LOGD(LOG_ERR, "[touch]Mem init failed!\n"); + if (g_rad_para_image) { + kfree(g_rad_para_image); + g_rad_para_image = NULL; + } +#endif + return count; +} + +static ssize_t raydium_i2c_raw_data_store(struct device *dev, + struct device_attribute *attr, + const char *p_i8_buf, size_t count) +{ + int i32_ret = 0; + char *temp_buf, *token, *free_temp_buf, *free_token; + const char *delim = " ,"; + unsigned char u8_w_data[RAD_FT_CMD_LENGTH]; + + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + + /* receive command line arguments string */ + if (count < 2) + return -EINVAL; + temp_buf = kzalloc(count + 1, GFP_KERNEL); + if (temp_buf == NULL) { + LOGD(LOG_ERR, "[touch]kzalloc temp_buf failed\n"); + return -ENOMEM; + } + + token = kzalloc(count + 1, GFP_KERNEL); + if (token == NULL) { + kfree(temp_buf); + return -ENOMEM; + } + + free_temp_buf = temp_buf; + free_token = token; + + strlcpy(temp_buf, p_i8_buf, count); + + token = strsep(&temp_buf, delim); + + i32_ret = kstrtou8(token, 16, &g_u8_raw_data_type); + + token = strsep(&temp_buf, delim); + if (token) { + i32_ret = kstrtouint(token, 16, &g_u32_raw_data_len); + if (i32_ret < 0) + goto exit_error; + + + } else { /* without length info*/ + i32_ret = -EINVAL; + goto exit_error; + } + + if (temp_buf) { /* too much arguments*/ + i32_ret = -E2BIG; + goto exit_error; + } + + memset(u8_w_data, 0x00, RAD_FT_CMD_LENGTH); + + mutex_lock(&g_raydium_ts->lock); + i32_ret = raydium_i2c_pda2_set_page(client, + g_raydium_ts->is_suspend, + RAYDIUM_PDA2_PAGE_0); + if (i32_ret < 0) { + mutex_unlock(&g_raydium_ts->lock); + goto exit_error; + } + + g_u8_resetflag = true; + + u8_w_data[RAD_HOST_CMD_POS] = RAYDIUM_HOST_CMD_NO_OP; + u8_w_data[RAD_FT_CMD_POS] = g_u8_raw_data_type; + + i32_ret = raydium_i2c_pda2_write(client, RAYDIUM_PDA2_HOST_CMD_ADDR, + u8_w_data, RAD_FT_CMD_LENGTH); + mutex_unlock(&g_raydium_ts->lock); + if (i32_ret < 0) + goto exit_error; + + if (g_u8_raw_data_type == 0) { + msleep(20); + g_u8_resetflag = false; + } +exit_error: + kfree(free_token); + kfree(free_temp_buf); + + return count; +} + +static ssize_t raydium_i2c_raw_data_show(struct device *dev, + struct device_attribute *attr, + char *p_i8_buf) +{ + unsigned char u8_rbuffer[MAX_READ_PACKET_SIZE]; + unsigned int u32_target_addr; + unsigned int u32_offset; + unsigned short u16_read_length; + + int i32_ret = -1; + int i32_retry = 0; + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct raydium_ts_data *ts = + (struct raydium_ts_data *)i2c_get_clientdata(client); + unsigned char u8_retry_limit = (ts->is_suspend) ? 100 : 30; + + memset(u8_rbuffer, 0x00, MAX_READ_PACKET_SIZE); + + /* make sure update flag was set*/ + for (i32_retry = 0; i32_retry < u8_retry_limit; i32_retry++) { + mutex_lock(&ts->lock); + i32_ret = raydium_i2c_pda2_set_page(client, + ts->is_suspend, + RAYDIUM_PDA2_PAGE_0); + if (i32_ret < 0) { + mutex_unlock(&ts->lock); + goto exit_i2c_error; + } + + i32_ret = raydium_i2c_pda2_read(client, + RAYDIUM_PDA2_HOST_CMD_ADDR, + u8_rbuffer, + RAD_FT_CMD_LENGTH); + mutex_unlock(&ts->lock); + if (i32_ret < 0) + goto exit_flag_error; + + if ((u8_rbuffer[RAD_FT_CMD_POS] & RAYDIUM_FT_UPDATE) == + RAYDIUM_FT_UPDATE) + break; + + usleep_range(500, 1500); + } + + if (i32_retry == u8_retry_limit) { + i32_ret = -EAGAIN; + goto exit_flag_error; + } + + u32_offset = 0; + u16_read_length = 0; + while (u32_offset < g_u32_raw_data_len) { + if ((u32_offset + MAX_READ_PACKET_SIZE) < + g_u32_raw_data_len) + u16_read_length = MAX_READ_PACKET_SIZE; + else + u16_read_length = + (unsigned short)(g_u32_raw_data_len - u32_offset); + + u32_target_addr = RAD_READ_FT_DATA_CMD + u32_offset; + + mutex_lock(&(ts->lock)); + + /*using byte mode to read 4 bytes*/ + i32_ret = handle_i2c_pda_read(client, + u32_target_addr, + u8_rbuffer, + u16_read_length); + + mutex_unlock(&(ts->lock)); + if (i32_ret < 0) + goto exit_flag_error; + + memcpy((p_i8_buf + u32_offset), u8_rbuffer, u16_read_length); + + u32_offset += u16_read_length; + } + + /* clear update flag to get next one*/ + u8_rbuffer[RAD_HOST_CMD_POS] = RAYDIUM_HOST_CMD_NO_OP; + u8_rbuffer[RAD_FT_CMD_POS] = g_u8_raw_data_type; + mutex_lock(&ts->lock); + i32_ret = raydium_i2c_pda2_write(client, RAYDIUM_PDA2_HOST_CMD_ADDR, + u8_rbuffer, RAD_FT_CMD_LENGTH); + mutex_unlock(&ts->lock); + if (i32_ret < 0) + goto exit_flag_error; + + return g_u32_raw_data_len; +exit_i2c_error: + mutex_unlock(&(ts->lock)); +exit_flag_error: + return i32_ret; +} + +static ssize_t raydium_i2c_pda_access_store(struct device *dev, + struct device_attribute *attr, + const char *p_i8_buf, size_t count) +{ + int i32_ret = 0; + char *temp_buf, *token, *free_temp_buf, *free_token; + const char *delim = " ,"; + unsigned char u8_w_data[MAX_WRITE_PACKET_SIZE]; + unsigned int u32_data_count = 0; + unsigned int u32_data_index = 0; + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + + /* receive command line arguments string */ + if (count < 2) + return -EINVAL; + + temp_buf = kzalloc(count + 1, GFP_KERNEL); + if (temp_buf == NULL) + return -ENOMEM; + + token = kzalloc(count + 1, GFP_KERNEL); + if (token == NULL) { + kfree(temp_buf); + return -ENOMEM; + } + + free_temp_buf = temp_buf; + free_token = token; + + strlcpy(temp_buf, p_i8_buf, count); + + token = strsep(&temp_buf, delim); + + i32_ret = kstrtoul(token, 16, &g_u32_addr); + + token = strsep(&temp_buf, delim); + if (token) + i32_ret = kstrtouint(token, 16, &u32_data_count); + else + goto exit_error; + if (g_u32_length > MAX_WRITE_PACKET_SIZE) + return -EINVAL; + g_u32_length = u32_data_count; + + memset(u8_w_data, 0x00, MAX_WRITE_PACKET_SIZE); + + if (temp_buf && u32_data_count) { + u32_data_index = 0; + while (u32_data_count) { + token = strsep(&temp_buf, delim); + i32_ret = kstrtou8(token, 16, + &u8_w_data[u32_data_index++]); + if (i32_ret < 0) + goto exit_error; + u32_data_count--; + } + mutex_lock(&g_raydium_ts->lock); + i32_ret = raydium_i2c_pda_write(client, g_u32_addr, + u8_w_data, g_u32_length); + mutex_unlock(&g_raydium_ts->lock); + } + +exit_error: + kfree(free_token); + kfree(free_temp_buf); + return count; +} +static ssize_t raydium_i2c_pda_access_via_pda2_store(struct device *dev, + struct device_attribute *attr, + const char *p_i8_buf, size_t count) +{ + int i32_ret = 0; + char *temp_buf, *token, *free_temp_buf, *free_token; + const char *delim = " ,"; + unsigned char u8_w_data[MAX_WRITE_PACKET_SIZE]; + unsigned int u32_data_count = 0; + unsigned int u32_data_index = 0; + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + + /* receive command line arguments string */ + if (count < 2) + return -EINVAL; + + temp_buf = kzalloc(count + 1, GFP_KERNEL); + if (temp_buf == NULL) + return -ENOMEM; + + token = kzalloc(count + 1, GFP_KERNEL); + if (token == NULL) { + kfree(temp_buf); + return -ENOMEM; + } + + free_temp_buf = temp_buf; + free_token = token; + + strlcpy(temp_buf, p_i8_buf, count); + + token = strsep(&temp_buf, delim); + + i32_ret = kstrtoul(token, 16, &g_u32_addr); + + token = strsep(&temp_buf, delim); + if (token) + i32_ret = kstrtouint(token, 16, &u32_data_count); + else + goto exit_error; + if (g_u32_length > MAX_WRITE_PACKET_SIZE) + return -EINVAL; + g_u32_length = u32_data_count; + + memset(u8_w_data, 0x00, MAX_WRITE_PACKET_SIZE); + + if (temp_buf && u32_data_count) { + u32_data_index = 0; + while (u32_data_count) { + token = strsep(&temp_buf, delim); + i32_ret = kstrtou8(token, 16, + &u8_w_data[u32_data_index++]); + if (i32_ret < 0) + goto exit_error; + u32_data_count--; + } + mutex_lock(&g_raydium_ts->lock); + i32_ret = handle_i2c_pda_write(client, g_u32_addr, + u8_w_data, g_u32_length); + mutex_unlock(&g_raydium_ts->lock); + } + +exit_error: + kfree(free_token); + kfree(free_temp_buf); + return count; +} + +static ssize_t raydium_i2c_pda2_mode_store(struct device *dev, + struct device_attribute *attr, + const char *p_i8_buf, size_t count) +{ + int i32_ret = 0; + unsigned char u8_mode; + + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + + if (g_raydium_ts->is_suspend) + LOGD(LOG_DEBUG, "[touch]RAD is_suspend at %s\n", __func__); + + /* receive command line arguments string */ + if (count > 2) + return -EINVAL; + + i32_ret = kstrtou8(p_i8_buf, 16, &u8_mode); + if (i32_ret < 0) + return i32_ret; + i32_ret = raydium_i2c_mode_control(client, u8_mode); + if (i32_ret < 0) + return i32_ret; + + return count; +} + +static ssize_t raydium_i2c_pda2_access_show(struct device *dev, + struct device_attribute *attr, + char *p_i8_buf) +{ + unsigned char u8_rbuffer[4]; + unsigned short u16_len = 0; + int i32_ret = -1; + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + + if (g_u32_length > 4) + return -EINVAL; + memset(u8_rbuffer, 0x00, 4); + + mutex_lock(&g_raydium_ts->lock); + i32_ret = raydium_i2c_pda2_read(client, g_u8_addr, + u8_rbuffer, g_u32_length); + mutex_unlock(&g_raydium_ts->lock); + if (i32_ret < 0) + return i32_ret; + + snprintf(p_i8_buf, PAGE_SIZE, "0x%04X : 0x%02X%02X%02X%02X\n", + g_u8_addr, u8_rbuffer[3], u8_rbuffer[2], + u8_rbuffer[1], u8_rbuffer[0]); + u16_len = strlen(p_i8_buf); + + return u16_len + 1; +} + +static ssize_t raydium_i2c_pda2_access_store(struct device *dev, + struct device_attribute *attr, + const char *p_i8_buf, size_t count) +{ + int i32_ret = 0; + char *temp_buf, *token, *free_temp_buf, *free_token; + const char *delim = " ,"; + unsigned char u8_w_data[MAX_WRITE_PACKET_SIZE]; + unsigned int u32_data_count = 0; + unsigned int u32_data_index = 0; + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + + /* receive command line arguments string */ + if (count < 2) + return -EINVAL; + temp_buf = kzalloc(count + 1, GFP_KERNEL); + if (temp_buf == NULL) { + LOGD(LOG_ERR, "[touch]kzalloc temp_buf failed\n"); + return -ENOMEM; + } + + token = kzalloc(count + 1, GFP_KERNEL); + if (token == NULL) { + kfree(temp_buf); + return -ENOMEM; + } + + free_temp_buf = temp_buf; + free_token = token; + + strlcpy(temp_buf, p_i8_buf, count); + + token = strsep(&temp_buf, delim); + + i32_ret = kstrtou8(token, 16, &g_u8_addr); + + token = strsep(&temp_buf, delim); + if (token) + i32_ret = kstrtouint(token, 16, &u32_data_count); + else { + i32_ret = -EINVAL; + goto exit_error; + } + + if (u32_data_count > MAX_WRITE_PACKET_SIZE) { + i32_ret = -EINVAL; + goto exit_error; + } + + memset(u8_w_data, 0x00, MAX_WRITE_PACKET_SIZE); + + g_u32_length = u32_data_count; + + if (temp_buf && u32_data_count) { + u32_data_index = 0; + while (u32_data_count) { + token = strsep(&temp_buf, delim); + i32_ret = kstrtou8(token, 16, + &u8_w_data[u32_data_index++]); + if (i32_ret < 0) + goto exit_error; + u32_data_count--; + } + + mutex_lock(&g_raydium_ts->lock); + i32_ret = raydium_i2c_pda2_write(client, g_u8_addr, + u8_w_data, g_u32_length); + mutex_unlock(&g_raydium_ts->lock); + if (i32_ret < 0) + goto exit_error; + } + +exit_error: + kfree(free_token); + kfree(free_temp_buf); + return count; +} + +static ssize_t raydium_receive_fw_store(struct device *dev, + struct device_attribute *attr, + const char *p_i8_buf, size_t count) +{ + int i32_ret = 0; + const char *delim = " ,"; + char *token, *temp_buf, *free_token = NULL, *free_temp_buf = NULL; + static unsigned char *p_u8_firmware_data; + + unsigned char u8_cmd; + unsigned long u32_len; + static unsigned char u8_type; + static unsigned int u32_index; + + if (count == 20) { /*check FW type*/ + temp_buf = kzalloc(32, GFP_KERNEL); + if (temp_buf == NULL) { + LOGD(LOG_ERR, "[touch]kzalloc temp_buf failed\n"); + return -ENOMEM; + } + + token = kzalloc(32, GFP_KERNEL); + if (token == NULL) { + kfree(temp_buf); + return -ENOMEM; + } + + free_token = token; + free_temp_buf = temp_buf; + + snprintf(temp_buf, 32, "%s", p_i8_buf); + token = strsep(&temp_buf, delim); + i32_ret = kstrtou8(token, 16, &u8_cmd); + if (i32_ret < 0) { + LOGD(LOG_ERR, "[touch]kstrtou8 failed\n"); + kfree(free_token); + free_token = NULL; + kfree(free_temp_buf); + free_temp_buf = NULL; + } + + token = strsep(&temp_buf, delim); + i32_ret = kstrtou8(token, 16, &u8_type); + if (i32_ret < 0) { + LOGD(LOG_ERR, "[touch]kstrtou8 failed\n"); + kfree(temp_buf); + kfree(token); + } + + token = strsep(&temp_buf, delim); + i32_ret = kstrtoul(token, 16, &u32_len); + if (i32_ret < 0) { + LOGD(LOG_ERR, "[touch]kstrtou8 failed\n"); + kfree(temp_buf); + kfree(token); + } + + LOGD(LOG_INFO, "[touch]uc_cmd=0x%x, uc_type=0x%x, u16_len=0x%x\n", + u8_cmd, u8_type, (unsigned int)u32_len); + + if (u8_cmd == RAD_CMD_UPDATE_BIN) { /*check FW length*/ + u32_index = 0; + if (u8_type == RAYDIUM_BOOTLOADER) { + memset(g_rad_boot_image, 0, u32_len); + p_u8_firmware_data = g_rad_boot_image; + } else if (u8_type == RAYDIUM_INIT) { + memset(g_rad_init_image, 0, u32_len); + p_u8_firmware_data = g_rad_init_image; + } else if (u8_type == RAYDIUM_PARA) { + memset(g_rad_para_image, 0, u32_len); + p_u8_firmware_data = g_rad_para_image; + } else if (u8_type == RAYDIUM_FIRMWARE) { + memset(g_rad_fw_image, 0, u32_len); + p_u8_firmware_data = g_rad_fw_image; + } else if (u8_type == RAYDIUM_TEST_PARA) { + memset(g_rad_testpara_image, 0, u32_len); + p_u8_firmware_data = g_rad_testpara_image; + } else if (u8_type == RAYDIUM_TEST_FW) { + memset(g_rad_testfw_image, 0, u32_len); + p_u8_firmware_data = g_rad_testfw_image; + } + + } else if (u8_cmd == RAD_CMD_UPDATE_END) { /*set buffer finish*/ + if (u8_type == RAYDIUM_TEST_FW) { + memcpy((g_rad_testfw_image + RAD_FW_3X_SIZE), + g_rad_testpara_image, RAD_PARA_3X_SIZE + 4); + } + + u32_index = 0; + g_u8_table_setting = 0; + + } else if (u8_cmd == RAD_CMD_BURN_FINISH) { /*free buffer*/ + u8_type = 0; + u32_index = 0; + g_u8_table_setting = 1; + } + + if (free_temp_buf) { + kfree(free_temp_buf); + free_temp_buf = NULL; + } + if (free_token) { + kfree(free_token); + free_token = NULL; + } + } else if (count > 10) { /*start copy FW to array*/ + memcpy((p_u8_firmware_data + u32_index), p_i8_buf, count); + u32_index += count; + } else + LOGD(LOG_ERR, "[touch]other case, count=%d\n", count); + + return count; +} + +static ssize_t raydium_log_level_store(struct device *dev, + struct device_attribute *attr, + const char *p_i8_buf, size_t count) +{ + int i32_ret = 0; + unsigned char u8_level = 0; + + /* receive command line arguments string */ + if (count > 2) + return -EINVAL; + i32_ret = kstrtou8(p_i8_buf, 16, &u8_level); + if (i32_ret < 0) + return i32_ret; + g_u8_log_level = u8_level; + LOGD(LOG_ERR, "[touch]g_u8_log_level = %d\r\n", g_u8_log_level); + return count; +} + + +#ifdef RAD_SELFTEST +static ssize_t raydium_reset_control_store(struct device *dev, + struct device_attribute *attr, + const char *p_i8_buf, size_t count) +{ + int i32_ret = 0; + unsigned char u8_high; + + if (g_raydium_ts->is_suspend) + LOGD(LOG_DEBUG, "[touch]RAD is_suspend at %s\n", __func__); + + /* receive command line arguments string */ + if (count > 2) + return -EINVAL; + + i32_ret = kstrtou8(p_i8_buf, 16, &u8_high); + if (i32_ret < 0) + return i32_ret; + + g_u8_i2c_mode = PDA2_MODE; + g_u8_resetflag = true; + + if (u8_high) { + LOGD(LOG_INFO, "[touch]RAD %s set reset gpio to high!!\n", + __func__); + gpio_set_value(g_raydium_ts->rst_gpio, 1); + } else { + LOGD(LOG_INFO, "[touch]RAD %s set reset gpio to low!!\n", + __func__); + gpio_set_value(g_raydium_ts->rst_gpio, 0); + } + return count; +} + + +static ssize_t raydium_irq_state_show(struct device *dev, + struct device_attribute *attr, + char *p_i8_buf) +{ + unsigned int u32_irq_value; + + u32_irq_value = gpio_get_value(g_raydium_ts->irq_gpio); + + snprintf(p_i8_buf, PAGE_SIZE, "%d", u32_irq_value); + LOGD(LOG_DEBUG, "%s\n", p_i8_buf); + return strlen(p_i8_buf) + 1; +} + + +static ssize_t raydium_flag_show(struct device *dev, + struct device_attribute *attr, + char *p_i8_buf) +{ + unsigned short u16_len = 0; + + snprintf(p_i8_buf, PAGE_SIZE, "%d", g_u8_raydium_flag); + LOGD(LOG_DEBUG, "[touch]RAD flag : %d\n", g_u8_raydium_flag); + u16_len = strlen(p_i8_buf); + + return u16_len + 1; +} + +static ssize_t raydium_flag_store(struct device *dev, + struct device_attribute *attr, + const char *p_i8_buf, size_t count) +{ + int i32_ret = 0; + unsigned char u8_flag = 0; + + /* receive command line arguments string */ + if (count > 2) + return -EINVAL; + i32_ret = kstrtou8(p_i8_buf, 16, &u8_flag); + if (i32_ret < 0) + return i32_ret; + g_u8_raydium_flag = u8_flag; + return count; +} + +static ssize_t raydium_selftest_show(struct device *dev, + struct device_attribute *attr, + char *p_i8_buf) +{ + int i32_ret = SUCCESS; + + LOGD(LOG_INFO, "[touch]do selftest\n"); + + i32_ret = raydium_do_selftest(g_raydium_ts); + + + snprintf(p_i8_buf, PAGE_SIZE, "Raydium do selftest : %d\n", i32_ret); + + return strlen(p_i8_buf) + 1; +} + +#endif +/* panel calibration cmd (R) + * example:cat raydium_ic_verion + */ +static DEVICE_ATTR(raydium_touch_calibration, 0644, + raydium_touch_calibration_show, + NULL); + +/* check the i2c (R) + * example:cat raydium_check_i2c + */ +static DEVICE_ATTR(raydium_check_i2c, 0644, + raydium_check_i2c_show, + NULL); + +/* upgrade configurate and algo firmware from app.bin (W) + * example:echo "offset num_of_bin length *_app.bin [length *_app.bin]" + * > raydium_fw_upgrade_mode + */ +static DEVICE_ATTR(raydium_fw_upgrade, 0644, + raydium_fw_upgrade_show, + raydium_fw_upgrade_store); + +/* change I2C communication mode (W) + * example:echo 1 > raydium_i2c_pda2_mode ==> enable pda2 mode + * echo 0 > raydium_i2c_pda2_mode ==> disable pda2 mode + */ +static DEVICE_ATTR(raydium_i2c_pda2_mode, 0644, + NULL, + raydium_i2c_pda2_mode_store); + +/* I2C pda mode (R/W) + * example: cat raydium_i2c_pda_access ==> read pda address provided by the + * following cmd + * echo ADDRinHEX [DATAinHEX] > raydium_i2c_pda_access ==> write + * pda address [data] + */ +static DEVICE_ATTR(raydium_i2c_pda_access, 0644, + raydium_i2c_pda_access_show, + raydium_i2c_pda_access_store); +/* I2C pda mode via pda2(R/W) + * example: cat raydium_i2c_pda_access ==> read pda address provided by the + * following cmd + * echo ADDRinHEX [DATAinHEX] > raydium_i2c_pda_access ==> write + * pda address [data] + */ +static DEVICE_ATTR(raydium_i2c_pda_access_via_pda2, 0644, + raydium_i2c_pda_access_via_pda2_show, + raydium_i2c_pda_access_via_pda2_store); + +/* I2C pda2 mode (R/W) + * example: cat raydium_i2c_pda2_access ==> read pda2 address provided by + * the following cmd + * echo ADDRinHEX [DATAinHEX] > raydium_i2c_pda2_access ==> + * write pda2 address [data] + */ +static DEVICE_ATTR(raydium_i2c_pda2_access, 0644, + raydium_i2c_pda2_access_show, + raydium_i2c_pda2_access_store); + +/* I2C pda2 mode page (W) + * example: echo PAGEinHEX > raydium_i2c_pda2_page ==> write pda2 page + */ +static DEVICE_ATTR(raydium_i2c_pda2_page, 0644, + NULL, + raydium_i2c_pda2_page_store); + +/* I2C read/set FT raw data (R/W) + * example: cat raydium_i2c_raw_data ==> read raw data with specific length + * of corresponding type provided by the following cmd + * echo DataTypeinHEX RawDataLengthinHEX > raydium_i2c_raw_data + * ==> set raw data type and its length + */ +static DEVICE_ATTR(raydium_i2c_raw_data, 0644, + raydium_i2c_raw_data_show, + raydium_i2c_raw_data_store); + +/* Touch lock (W) + * example: echo 1 > raydium_i2c_touch_lock ==> enable touch lock + * echo 0 > raydium_i2c_touch_lock ==> disable touch lock + */ +static DEVICE_ATTR(raydium_i2c_touch_lock, 0644, + NULL, + raydium_touch_lock_store); + +/* Touch Offload (W) + * example: echo 1 > raydium_touch_offload ==> enable touch offload + * echo 0 > raydium_touch_offload ==> disable touch offload + */ +static DEVICE_ATTR(raydium_touch_offload, 0644, + raydium_touch_offload_show, + raydium_touch_offload_store); + +/* Log level (W) + * example: echo 1 > raydium_log_level ==> modify log level + */ +static DEVICE_ATTR(raydium_log_level, 0644, + NULL, + raydium_log_level_store); +/* show the fw version (R) + * example:cat raydium_fw_version + */ +static DEVICE_ATTR(raydium_check_fw_version, 0644, + raydium_check_fw_version_show, + NULL); + +/* show the driver version (R) + * example:cat raydium_check_driver_version + */ +static DEVICE_ATTR(raydium_check_driver_version, 0644, + raydium_check_driver_version_show, + NULL); +/* show the panel version (R) + * example:cat raydium_panel_version + */ +static DEVICE_ATTR(raydium_check_panel_version, 0644, + raydium_check_panel_version_show, + NULL); + +static DEVICE_ATTR(raydium_hw_reset, 0644, + raydium_hw_reset_show, + NULL); + +static DEVICE_ATTR(raydium_palm_status, 0644, + raydium_palm_status_show, + NULL); + +static DEVICE_ATTR(raydium_receive_fw_control, 0644, + NULL, + raydium_receive_fw_store); + +static DEVICE_ATTR(raydium_mem_setting, 0644, + NULL, + raydium_mem_store); + +#ifdef RAD_SELFTEST + +/* Read interrupt flag cmd (R) + * example:cat raydium_int_flag + */ +static DEVICE_ATTR(raydium_int_flag, 0644, + raydium_flag_show, + raydium_flag_store); + +static DEVICE_ATTR(raydium_reset_control, 0644, + NULL, + raydium_reset_control_store); + +static DEVICE_ATTR(raydium_irq_state, 0644, + raydium_irq_state_show, + NULL); +static DEVICE_ATTR(raydium_do_selftest, 0644, + raydium_selftest_show, + NULL); +#endif +/*add your attr in here*/ +struct attribute *raydium_attributes[] = { + &dev_attr_raydium_touch_calibration.attr, + &dev_attr_raydium_check_i2c.attr, + &dev_attr_raydium_i2c_pda2_mode.attr, + &dev_attr_raydium_i2c_pda_access.attr, + &dev_attr_raydium_i2c_pda_access_via_pda2.attr, + &dev_attr_raydium_i2c_pda2_access.attr, + &dev_attr_raydium_i2c_pda2_page.attr, + &dev_attr_raydium_i2c_raw_data.attr, + &dev_attr_raydium_i2c_touch_lock.attr, + &dev_attr_raydium_touch_offload.attr, + &dev_attr_raydium_fw_upgrade.attr, + &dev_attr_raydium_check_fw_version.attr, + &dev_attr_raydium_check_panel_version.attr, + &dev_attr_raydium_hw_reset.attr, + &dev_attr_raydium_palm_status.attr, + &dev_attr_raydium_check_driver_version.attr, + &dev_attr_raydium_receive_fw_control.attr, + &dev_attr_raydium_mem_setting.attr, + &dev_attr_raydium_log_level.attr, +#ifdef RAD_SELFTEST + &dev_attr_raydium_irq_state.attr, + &dev_attr_raydium_int_flag.attr, + &dev_attr_raydium_reset_control.attr, + &dev_attr_raydium_do_selftest.attr, +#endif + NULL +}; diff --git a/qcom/opensource/touch-drivers/raydium/tpselftest_30.h b/qcom/opensource/touch-drivers/raydium/tpselftest_30.h new file mode 100644 index 0000000000..2f5ad79a03 --- /dev/null +++ b/qcom/opensource/touch-drivers/raydium/tpselftest_30.h @@ -0,0 +1,54 @@ +/* tpselftest_30.h + * + * Raydium TouchScreen driver. + * + * Copyright (c) 2021 Raydium tech Ltd. + * + * 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. + * + */ + +#define RAD_SELFTEST_30 0x3202 +unsigned char u8_test_info_30[16] = { +0x03, 0x02, 0x02, 0x06, 0x5D, 0x00, 0x00, 0xAC, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; +signed char i8_ft_test_thd_30[36] = { +0x00, 0x00, 0x7F, 0xFE, 0x22, 0x07, 0x00, 0x00, 0x00, 0x00, 0x85, 0xFF, +0x00, 0x00, 0x00, 0x00, 0x84, 0x00, 0x7C, 0xFF, 0x17, 0x00, 0x23, 0x00, +0x2C, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; +unsigned char u8_test_para_30[48] = { +0x14, 0x05, 0x64, 0x0A, 0x5A, 0x14, 0x50, 0x15, 0x12, 0x00, 0x03, 0x1E, +0x00, 0x03, 0x64, 0x15, 0x12, 0x02, 0x00, 0x00, 0x10, 0xA1, 0x00, 0x02, +0x08, 0x20, 0x02, 0x28, 0x46, 0x00, 0x00, 0x00, 0x40, 0x08, 0x02, 0x00, +0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x01, 0x02, 0x02, 0x40, 0x01 +}; +unsigned char u8_raw_data_3_cc_30[96] = { +0xAB, 0x02, 0xCF, 0x02, 0x97, 0x02, 0x00, 0x00, 0xBD, 0x02, 0x8D, 0x02, +0xAF, 0x02, 0x00, 0x00, 0x82, 0x02, 0xA5, 0x02, 0x61, 0x02, 0x00, 0x00, +0x61, 0x02, 0xFC, 0x02, 0xBC, 0x02, 0x00, 0x00, 0x6B, 0x02, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0xA5, 0x02, 0xD1, 0x02, 0x90, 0x02, 0x00, 0x00, 0xBD, 0x02, 0x87, 0x02, +0xAE, 0x02, 0x00, 0x00, 0x75, 0x02, 0xA1, 0x02, 0x51, 0x02, 0x00, 0x00, +0x5F, 0x02, 0xBF, 0x02, 0x87, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; +unsigned char u8_raw_uc_cc_30[96] = { +0x7F, 0x02, 0x93, 0x02, 0x5D, 0x02, 0x00, 0x00, 0x83, 0x02, 0x55, 0x02, +0x79, 0x02, 0x00, 0x00, 0x51, 0x02, 0x79, 0x02, 0x39, 0x02, 0x00, 0x00, +0x3D, 0x02, 0xCB, 0x02, 0x8F, 0x02, 0x00, 0x00, 0x4D, 0x02, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x79, 0x02, 0x95, 0x02, 0x55, 0x02, 0x00, 0x00, 0x83, 0x02, 0x51, 0x02, +0x7B, 0x02, 0x00, 0x00, 0x45, 0x02, 0x75, 0x02, 0x2B, 0x02, 0x00, 0x00, +0x3B, 0x02, 0x95, 0x02, 0x65, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; diff --git a/qcom/opensource/touch-drivers/st/fts.c b/qcom/opensource/touch-drivers/st/fts.c new file mode 100644 index 0000000000..f6d8323345 --- /dev/null +++ b/qcom/opensource/touch-drivers/st/fts.c @@ -0,0 +1,6078 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2016-2019, STMicroelectronics Limited. + * Authors: AMG(Analog Mems Group) + * + * marco.cali@st.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +/*#include */ +#include +#include +#include +#include +#include +#include + +#if defined(CONFIG_FB_MSM) +#include +#include +#else +#include +#include +#endif + +#ifdef KERNEL_ABOVE_2_6_38 +#include +#endif + +#include "fts.h" +#include "fts_lib/ftsCompensation.h" +#include "fts_lib/ftsIO.h" +#include "fts_lib/ftsError.h" +#include "fts_lib/ftsFlash.h" +#include "fts_lib/ftsFrame.h" +#include "fts_lib/ftsGesture.h" +#include "fts_lib/ftsTest.h" +#include "fts_lib/ftsTime.h" +#include "fts_lib/ftsTool.h" +#include "linux/moduleparam.h" + +#if defined(CONFIG_ST_TRUSTED_TOUCH) + +#include +#include +#include + +#include +#include +#include +#include +#include +#include "linux/gunyah/gh_irq_lend.h" +#include "linux/gunyah/gh_msgq.h" +#include "linux/gunyah/gh_mem_notifier.h" +#include "linux/gunyah/gh_rm_drv.h" +#include +#endif + +#define LINK_KOBJ_NAME "tp" + +#define FTS_DVDD_VOL_MIN 1800000 +#define FTS_DVDD_VOL_MAX 1800000 +#define FTS_DVDD_LOAD 20000 +#define FTS_AVDD_VOL_MIN 3000000 +#define FTS_AVDD_VOL_MAX 3300000 +#define FTS_AVDD_LOAD 20000 + +/* + * Uncomment to use polling mode instead of interrupt mode. + * + */ +// #define FTS_USE_POLLING_MODE + +/* + * Event installer helpers + */ +#define event_id(_e) EVENTID_##_e +#define handler_name(_h) fts_##_h##_event_handler + +#define install_handler(_i, _evt, _hnd) \ + (_i->event_dispatch_table[event_id(_evt)].handler = handler_name(_hnd)) + +/* + * Asyncronouns command helper + */ +#define WAIT_WITH_TIMEOUT(_info, _timeout, _command) \ +do { \ + if (wait_for_completion_timeout(&_info->cmd_done, _timeout) == 0) { \ + dev_warn(_info->dev, "Waiting for %s command: timeout\n", \ + #_command); \ + } \ +} while (0) + +#ifdef KERNEL_ABOVE_2_6_38 +#define TYPE_B_PROTOCOL +#endif + +#if defined(SCRIPTLESS) || defined(DRIVER_TEST) +static struct class *fts_cmd_class; +#endif + +static void fts_interrupt_disable(struct fts_ts_info *info); +static void fts_interrupt_enable(struct fts_ts_info *info); +static irqreturn_t fts_interrupt_handler(int irq, void *handle); +static int fts_probe_delayed(struct fts_ts_info *info); + +#ifdef CONFIG_ST_TRUSTED_TOUCH + +static struct gh_acl_desc *fts_vm_get_acl(enum gh_vm_names vm_name) +{ + struct gh_acl_desc *acl_desc; + gh_vmid_t vmid; + + gh_rm_get_vmid(vm_name, &vmid); + + acl_desc = kzalloc(offsetof(struct gh_acl_desc, acl_entries[1]), + GFP_KERNEL); + if (!acl_desc) + return ERR_PTR(ENOMEM); + + acl_desc->n_acl_entries = 1; + acl_desc->acl_entries[0].vmid = vmid; + acl_desc->acl_entries[0].perms = GH_RM_ACL_R | GH_RM_ACL_W; + + return acl_desc; +} + +static struct gh_sgl_desc *fts_vm_get_sgl(struct trusted_touch_vm_info *vm_info) +{ + struct gh_sgl_desc *sgl_desc; + int i; + + sgl_desc = kzalloc(offsetof(struct gh_sgl_desc, + sgl_entries[vm_info->iomem_list_size]), GFP_KERNEL); + if (!sgl_desc) + return ERR_PTR(ENOMEM); + + sgl_desc->n_sgl_entries = vm_info->iomem_list_size; + + for (i = 0; i < vm_info->iomem_list_size; i++) { + sgl_desc->sgl_entries[i].ipa_base = vm_info->iomem_bases[i]; + sgl_desc->sgl_entries[i].size = vm_info->iomem_sizes[i]; + } + + return sgl_desc; +} + +static int fts_populate_vm_info(struct fts_ts_info *info) +{ + int rc = 0; + struct trusted_touch_vm_info *vm_info; + struct device_node *np = info->client->dev.of_node; + int num_regs, num_sizes = 0; + + vm_info = kzalloc(sizeof(struct trusted_touch_vm_info), GFP_KERNEL); + if (!vm_info) { + rc = -ENOMEM; + goto error; + } + + info->vm_info = vm_info; + vm_info->irq_label = GH_IRQ_LABEL_TRUSTED_TOUCH; + vm_info->vm_name = GH_TRUSTED_VM; + rc = of_property_read_u32(np, "st,trusted-touch-spi-irq", + &vm_info->hw_irq); + if (rc) { + pr_err("Failed to read trusted touch SPI irq:%d\n", rc); + goto vm_error; + } + num_regs = of_property_count_u32_elems(np, + "st,trusted-touch-io-bases"); + if (num_regs < 0) { + pr_err("Invalid number of IO regions specified\n"); + rc = -EINVAL; + goto vm_error; + } + + num_sizes = of_property_count_u32_elems(np, + "st,trusted-touch-io-sizes"); + if (num_sizes < 0) { + pr_err("Invalid number of IO regions specified\n"); + rc = -EINVAL; + goto vm_error; + } + + if (num_regs != num_sizes) { + pr_err("IO bases and sizes doe not match\n"); + rc = -EINVAL; + goto vm_error; + } + + vm_info->iomem_list_size = num_regs; + + vm_info->iomem_bases = kcalloc(num_regs, sizeof(*vm_info->iomem_bases), + GFP_KERNEL); + if (!vm_info->iomem_bases) { + rc = -ENOMEM; + goto vm_error; + } + + rc = of_property_read_u32_array(np, "st,trusted-touch-io-bases", + vm_info->iomem_bases, vm_info->iomem_list_size); + if (rc) { + pr_err("Failed to read trusted touch io bases:%d\n", rc); + goto io_bases_error; + } + + vm_info->iomem_sizes = kzalloc( + sizeof(*vm_info->iomem_sizes) * num_sizes, GFP_KERNEL); + if (!vm_info->iomem_sizes) { + rc = -ENOMEM; + goto io_bases_error; + } + + rc = of_property_read_u32_array(np, "st,trusted-touch-io-sizes", + vm_info->iomem_sizes, vm_info->iomem_list_size); + if (rc) { + pr_err("Failed to read trusted touch io sizes:%d\n", rc); + goto io_sizes_error; + } + return rc; + +io_sizes_error: + kfree(vm_info->iomem_sizes); +io_bases_error: + kfree(vm_info->iomem_bases); +vm_error: + kfree(vm_info); +error: + return rc; +} + +static void fts_destroy_vm_info(struct fts_ts_info *info) +{ + kfree(info->vm_info->iomem_sizes); + kfree(info->vm_info->iomem_bases); + kfree(info->vm_info); +} + +static void fts_vm_deinit(struct fts_ts_info *info) +{ + if (info->vm_info->mem_cookie) + gh_mem_notifier_unregister(info->vm_info->mem_cookie); + fts_destroy_vm_info(info); +} + +#ifdef CONFIG_ARCH_QTI_VM +static int fts_vm_mem_release(struct fts_ts_info *info); +static void fts_trusted_touch_vm_mode_disable(struct fts_ts_info *info); + +static int fts_sgl_cmp(const void *a, const void *b) +{ + struct gh_sgl_entry *left = (struct gh_sgl_entry *)a; + struct gh_sgl_entry *right = (struct gh_sgl_entry *)b; + + return (left->ipa_base - right->ipa_base); +} + +static int fts_vm_compare_sgl_desc(struct gh_sgl_desc *expected, + struct gh_sgl_desc *received) +{ + int idx; + + if (expected->n_sgl_entries != received->n_sgl_entries) + return -E2BIG; + sort(received->sgl_entries, received->n_sgl_entries, + sizeof(received->sgl_entries[0]), fts_sgl_cmp, NULL); + sort(expected->sgl_entries, expected->n_sgl_entries, + sizeof(expected->sgl_entries[0]), fts_sgl_cmp, NULL); + + for (idx = 0; idx < expected->n_sgl_entries; idx++) { + struct gh_sgl_entry *left = &expected->sgl_entries[idx]; + struct gh_sgl_entry *right = &received->sgl_entries[idx]; + + if ((left->ipa_base != right->ipa_base) || + (left->size != right->size)) { + pr_err("sgl mismatch: left_base:%d right base:%d left size:%d right size:%d\n", + left->ipa_base, right->ipa_base, + left->size, right->size); + return -EINVAL; + } + } + return 0; +} + +static int fts_vm_handle_vm_hardware(struct fts_ts_info *info) +{ + int rc = 0; + + if (atomic_read(&info->delayed_vm_probe_pending)) { + rc = fts_probe_delayed(info); + if (rc) { + pr_err(" Delayed probe failure on VM!\n"); + return rc; + } + atomic_set(&info->delayed_vm_probe_pending, 0); + return rc; + } + + queue_delayed_work(info->fwu_workqueue, &info->fwu_work, + msecs_to_jiffies(EXP_FN_WORK_DELAY_MS)); + fts_interrupt_enable(info); + return rc; +} + +static void fts_vm_irq_on_lend_callback(void *data, + unsigned long notif_type, + enum gh_irq_label label) +{ + struct fts_ts_info *info = data; + struct irq_data *irq_data; + int irq = 0; + int const resource_timeout = msecs_to_jiffies(2000); + int rc = 0; + + irq = gh_irq_accept(info->vm_info->irq_label, -1, IRQ_TYPE_LEVEL_HIGH); + if (irq < 0) { + pr_err("failed to accept irq\n"); + goto irq_fail; + } + atomic_set(&info->vm_info->tvm_owns_irq, 1); + irq_data = irq_get_irq_data(irq); + if (!irq_data) { + pr_err("Invalid irq data for trusted touch\n"); + goto irq_fail; + } + if (!irq_data->hwirq) { + pr_err("Invalid irq in irq data\n"); + goto irq_fail; + } + if (irq_data->hwirq != info->vm_info->hw_irq) { + pr_err("Invalid irq lent\n"); + goto irq_fail; + } + + pr_debug("irq:returned from accept:%d\n", irq); + info->client->irq = irq; + if (!wait_for_completion_timeout(&info->resource_checkpoint, + resource_timeout)) { + pr_err("Resources not acquired in TVM\n"); + goto irq_fail; + } + + rc = fts_vm_handle_vm_hardware(info); + if (rc) { + pr_err(" Delayed probe failure on VM!\n"); + goto irq_fail; + } + + atomic_set(&info->trusted_touch_enabled, 1); + return; +irq_fail: + fts_trusted_touch_vm_mode_disable(info); +} + +static void fts_vm_mem_on_lend_handler(enum gh_mem_notifier_tag tag, + unsigned long notif_type, void *entry_data, void *notif_msg) +{ + struct gh_rm_notif_mem_shared_payload *payload; + struct gh_sgl_desc *sgl_desc, *expected_sgl_desc; + struct gh_acl_desc *acl_desc; + struct trusted_touch_vm_info *vm_info; + struct fts_ts_info *info; + int rc = 0; + + if (notif_type != GH_RM_NOTIF_MEM_SHARED || + tag != GH_MEM_NOTIFIER_TAG_TOUCH) { + pr_err("Invalid command passed from rm\n"); + return; + } + + if (!entry_data || !notif_msg) { + pr_err("Invalid entry data passed from rm\n"); + return; + } + + info = (struct fts_ts_info *)entry_data; + vm_info = info->vm_info; + if (!vm_info) { + pr_err("Invalid vm_info\n"); + return; + } + + payload = (struct gh_rm_notif_mem_shared_payload *)notif_msg; + if (payload->trans_type != GH_RM_TRANS_TYPE_LEND || + payload->label != TRUSTED_TOUCH_MEM_LABEL) { + pr_err("Invalid label or transaction type\n"); + goto onlend_fail; + } + + acl_desc = fts_vm_get_acl(GH_TRUSTED_VM); + if (IS_ERR(acl_desc)) { + pr_err("failed to populated acl data:rc=%d\n", + PTR_ERR(acl_desc)); + goto onlend_fail; + } + + sgl_desc = gh_rm_mem_accept(payload->mem_handle, GH_RM_MEM_TYPE_IO, + GH_RM_TRANS_TYPE_LEND, + GH_RM_MEM_ACCEPT_VALIDATE_ACL_ATTRS | + GH_RM_MEM_ACCEPT_VALIDATE_LABEL | + GH_RM_MEM_ACCEPT_DONE, payload->label, acl_desc, + NULL, NULL, 0); + if (IS_ERR_OR_NULL(sgl_desc)) { + pr_err("failed to do mem accept :rc=%d\n", + PTR_ERR(sgl_desc)); + goto acl_fail; + } + atomic_set(&vm_info->tvm_owns_iomem, 1); + + /* Initiate i2c session on tvm */ + rc = pm_runtime_get_sync(info->client->adapter->dev.parent); + if (rc < 0) { + pr_err("failed to get sync rc:%d\n", rc); + (void)fts_vm_mem_release(info); + atomic_set(&info->vm_info->tvm_owns_iomem, 0); + goto acl_fail; + } + complete(&info->resource_checkpoint); + + expected_sgl_desc = fts_vm_get_sgl(vm_info); + if (fts_vm_compare_sgl_desc(expected_sgl_desc, sgl_desc)) { + pr_err("IO sg list does not match\n"); + goto sgl_cmp_fail; + } + + vm_info->vm_mem_handle = payload->mem_handle; + kfree(expected_sgl_desc); + kfree(acl_desc); + return; + +sgl_cmp_fail: + kfree(expected_sgl_desc); +acl_fail: + kfree(acl_desc); +onlend_fail: + fts_trusted_touch_vm_mode_disable(info); +} + +static int fts_vm_mem_release(struct fts_ts_info *info) +{ + int rc = 0; + + rc = gh_rm_mem_release(info->vm_info->vm_mem_handle, 0); + if (rc) + pr_err("VM mem release failed: rc=%d\n", rc); + + rc = gh_rm_mem_notify(info->vm_info->vm_mem_handle, + GH_RM_MEM_NOTIFY_OWNER_RELEASED, + GH_MEM_NOTIFIER_TAG_TOUCH, 0); + if (rc) + pr_err("Failed to notify mem release to PVM: rc=%d\n"); + + info->vm_info->vm_mem_handle = 0; + return rc; +} + +static void fts_trusted_touch_vm_mode_disable(struct fts_ts_info *info) +{ + int rc = 0; + + + if (atomic_read(&info->vm_info->tvm_owns_iomem) && + atomic_read(&info->vm_info->tvm_owns_irq)) + fts_interrupt_disable(info); + + if (atomic_read(&info->vm_info->tvm_owns_iomem)) { + flushFIFO(); + release_all_touches(info); + rc = fts_vm_mem_release(info); + if (rc) + pr_err("Failed to release mem rc:%d\n", rc); + else + atomic_set(&info->vm_info->tvm_owns_iomem, 0); + pm_runtime_put_sync(info->client->adapter->dev.parent); + } + + if (atomic_read(&info->vm_info->tvm_owns_irq)) { + rc = gh_irq_release(info->vm_info->irq_label); + if (rc) + pr_err("Failed to release irq rc:%d\n", rc); + else + atomic_set(&info->vm_info->tvm_owns_irq, 0); + + rc = gh_irq_release_notify(info->vm_info->irq_label); + if (rc) + pr_err("Failed to notify release irq rc:%d\n", rc); + } + atomic_set(&info->trusted_touch_enabled, 0); + reinit_completion(&info->resource_checkpoint); + pr_debug("trusted touch disabled\n"); +} + +static int fts_handle_trusted_touch_tvm(struct fts_ts_info *info, int value) +{ + int err = 0; + + switch (value) { + case 0: + if (atomic_read(&info->trusted_touch_enabled) == 0) { + pr_err("Trusted touch is already disabled\n"); + break; + } + if (atomic_read(&info->trusted_touch_mode) == + TRUSTED_TOUCH_VM_MODE) { + fts_trusted_touch_vm_mode_disable(info); + } else { + pr_err("Unsupported trusted touch mode\n"); + } + break; + + case 1: + if (atomic_read(&info->trusted_touch_enabled)) { + pr_err("Trusted touch usecase underway\n"); + err = -EBUSY; + break; + } + if (atomic_read(&info->trusted_touch_mode) == + TRUSTED_TOUCH_VM_MODE) { + pr_err("Cannot turnon trusted touch(vm mode) in VM\n"); + } else { + pr_err("Unsupported trusted touch mode\n"); + } + break; + + default: + dev_err(&info->client->dev, "unsupported value: %lu\n", value); + err = -EINVAL; + break; + } + + return err; +} + +#else + +static int fts_clk_prepare_enable(struct fts_ts_info *info) +{ + int ret; + + ret = clk_prepare_enable(info->iface_clk); + if (ret) { + dev_err(&info->client->dev, + "error on clk_prepare_enable(iface_clk):%d\n", ret); + return ret; + } + + ret = clk_prepare_enable(info->core_clk); + if (ret) { + clk_disable_unprepare(info->iface_clk); + dev_err(&info->client->dev, + "error clk_prepare_enable(core_clk):%d\n", ret); + } + return ret; +} + +static void fts_clk_disable_unprepare(struct fts_ts_info *info) +{ + clk_disable_unprepare(info->core_clk); + clk_disable_unprepare(info->iface_clk); +} + +static int fts_bus_get(struct fts_ts_info *info) +{ + int rc = 0; + + mutex_lock(&info->fts_clk_io_ctrl_mutex); + rc = pm_runtime_get_sync(info->client->adapter->dev.parent); + if (rc >= 0 && info->core_clk != NULL && info->iface_clk != NULL) { + rc = fts_clk_prepare_enable(info); + if (rc) + pm_runtime_put_sync(info->client->adapter->dev.parent); + } + mutex_unlock(&info->fts_clk_io_ctrl_mutex); + return rc; +} + +static void fts_bus_put(struct fts_ts_info *info) +{ + mutex_lock(&info->fts_clk_io_ctrl_mutex); + if (info->core_clk != NULL && info->iface_clk != NULL) + fts_clk_disable_unprepare(info); + pm_runtime_put_sync(info->client->adapter->dev.parent); + mutex_unlock(&info->fts_clk_io_ctrl_mutex); +} + +static struct gh_notify_vmid_desc *fts_vm_get_vmid(gh_vmid_t vmid) +{ + struct gh_notify_vmid_desc *vmid_desc; + + vmid_desc = kzalloc(offsetof(struct gh_notify_vmid_desc, + vmid_entries[1]), GFP_KERNEL); + if (!vmid_desc) + return ERR_PTR(ENOMEM); + + vmid_desc->n_vmid_entries = 1; + vmid_desc->vmid_entries[0].vmid = vmid; + return vmid_desc; +} + +static void fts_trusted_touch_complete(struct fts_ts_info *info) +{ + if (atomic_read(&info->vm_info->pvm_owns_iomem) && + atomic_read(&info->vm_info->pvm_owns_irq)) { + fts_interrupt_enable(info); + fts_bus_put(info); + complete(&info->trusted_touch_powerdown); + atomic_set(&info->trusted_touch_enabled, 0); + pr_debug("reenabled interrupts on PVM\n"); + } else { + pr_err("PVM does not own irq and IOMEM\n"); + } +} + +static void fts_vm_irq_on_release_callback(void *data, + unsigned long notif_type, + enum gh_irq_label label) +{ + struct fts_ts_info *info = data; + int rc = 0; + + rc = gh_irq_reclaim(info->vm_info->irq_label); + if (rc) + pr_err("failed to reclaim irq on pvm rc:%d\n", rc); + else + atomic_set(&info->vm_info->pvm_owns_irq, 1); +} + +static void fts_vm_mem_on_release_handler(enum gh_mem_notifier_tag tag, + unsigned long notif_type, void *entry_data, void *notif_msg) +{ + struct gh_rm_notif_mem_released_payload *payload; + struct trusted_touch_vm_info *vm_info; + struct fts_ts_info *info; + int rc = 0; + + if (notif_type != GH_RM_NOTIF_MEM_RELEASED || + tag != GH_MEM_NOTIFIER_TAG_TOUCH) { + pr_err(" Invalid tag or command passed\n"); + return; + } + + if (!entry_data || !notif_msg) { + pr_err(" Invalid data or notification message\n"); + return; + } + + payload = (struct gh_rm_notif_mem_released_payload *)notif_msg; + info = (struct fts_ts_info *)entry_data; + vm_info = info->vm_info; + if (!vm_info) { + pr_err(" Invalid vm_info\n"); + return; + } + + if (payload->mem_handle != vm_info->vm_mem_handle) { + pr_err("Invalid mem handle detected\n"); + return; + } + + rc = gh_rm_mem_reclaim(payload->mem_handle, 0); + if (rc) { + pr_err("Trusted touch VM mem release failed rc:%d\n", rc); + return; + } + atomic_set(&vm_info->pvm_owns_iomem, 1); + vm_info->vm_mem_handle = 0; +} + +static int fts_vm_mem_lend(struct fts_ts_info *info) +{ + struct gh_acl_desc *acl_desc; + struct gh_sgl_desc *sgl_desc; + struct gh_notify_vmid_desc *vmid_desc; + gh_memparcel_handle_t mem_handle; + gh_vmid_t trusted_vmid; + int rc = 0; + + acl_desc = fts_vm_get_acl(GH_TRUSTED_VM); + if (IS_ERR(acl_desc)) { + pr_err("Failed to get acl of IO memories for Trusted touch\n"); + PTR_ERR(acl_desc); + return -EINVAL; + } + + sgl_desc = fts_vm_get_sgl(info->vm_info); + if (IS_ERR(sgl_desc)) { + pr_err("Failed to get sgl of IO memories for Trusted touch\n"); + PTR_ERR(sgl_desc); + rc = -EINVAL; + goto sgl_error; + } + + rc = gh_rm_mem_lend(GH_RM_MEM_TYPE_IO, 0, TRUSTED_TOUCH_MEM_LABEL, + acl_desc, sgl_desc, NULL, &mem_handle); + if (rc) { + pr_err("Failed to lend IO memories for Trusted touch rc:%d\n", + rc); + goto error; + } + + gh_rm_get_vmid(GH_TRUSTED_VM, &trusted_vmid); + + vmid_desc = fts_vm_get_vmid(trusted_vmid); + + rc = gh_rm_mem_notify(mem_handle, GH_RM_MEM_NOTIFY_RECIPIENT_SHARED, + GH_MEM_NOTIFIER_TAG_TOUCH, vmid_desc); + if (rc) { + pr_err("Failed to notify mem lend to hypervisor rc:%d\n", rc); + goto vmid_error; + } + + info->vm_info->vm_mem_handle = mem_handle; +vmid_error: + kfree(vmid_desc); +error: + kfree(sgl_desc); +sgl_error: + kfree(acl_desc); + + return rc; +} + +static int fts_trusted_touch_vm_mode_enable(struct fts_ts_info *info) +{ + int rc = 0; + struct trusted_touch_vm_info *vm_info = info->vm_info; + + /* i2c session start and resource acquire */ + if (fts_bus_get(info) < 0) { + dev_err(&info->client->dev, "fts_bus_get failed\n"); + rc = -EIO; + return rc; + } + + /* flush pending interurpts from FIFO */ + fts_interrupt_disable(info); + flushFIFO(); + release_all_touches(info); + + rc = fts_vm_mem_lend(info); + if (rc) { + pr_err("Failed to lend memory\n"); + return -EINVAL; + } + atomic_set(&vm_info->pvm_owns_iomem, 0); + + rc = gh_irq_lend_v2(vm_info->irq_label, vm_info->vm_name, + info->client->irq, &fts_vm_irq_on_release_callback, info); + if (rc) { + pr_err("Failed to lend irq\n"); + return -EINVAL; + } + atomic_set(&vm_info->pvm_owns_irq, 0); + + rc = gh_irq_lend_notify(vm_info->irq_label); + if (rc) { + pr_err("Failed to notify irq\n"); + return -EINVAL; + } + + reinit_completion(&info->trusted_touch_powerdown); + atomic_set(&info->trusted_touch_enabled, 1); + pr_debug("trusted touch enabled\n"); + return rc; +} + +static int fts_handle_trusted_touch_pvm(struct fts_ts_info *info, int value) +{ + int err = 0; + + switch (value) { + case 0: + if (atomic_read(&info->trusted_touch_enabled) == 0) { + pr_err("Trusted touch is already disabled\n"); + break; + } + if (atomic_read(&info->trusted_touch_mode) == + TRUSTED_TOUCH_VM_MODE) { + fts_trusted_touch_complete(info); + } else { + pr_err("Unsupported trusted touch mode\n"); + } + break; + + case 1: + if (atomic_read(&info->trusted_touch_enabled)) { + pr_err("Trusted touch usecase underway\n"); + err = -EBUSY; + break; + } + if (atomic_read(&info->trusted_touch_mode) == + TRUSTED_TOUCH_VM_MODE) { + err = fts_trusted_touch_vm_mode_enable(info); + } else { + pr_err("Unsupported trusted touch mode\n"); + } + break; + + default: + dev_err(&info->client->dev, "unsupported value: %lu\n", value); + err = -EINVAL; + break; + } + return err; +} +#endif + +static int fts_vm_init(struct fts_ts_info *info) +{ + int rc = 0; + struct trusted_touch_vm_info *vm_info; + void *mem_cookie; + + rc = fts_populate_vm_info(info); + if (rc) { + pr_err("Cannot setup vm pipeline\n"); + rc = -EINVAL; + goto fail; + } + + vm_info = info->vm_info; +#ifdef CONFIG_ARCH_QTI_VM + mem_cookie = gh_mem_notifier_register(GH_MEM_NOTIFIER_TAG_TOUCH, + fts_vm_mem_on_lend_handler, info); + if (!mem_cookie) { + pr_err("Failed to register on lend mem notifier\n"); + rc = -EINVAL; + goto init_fail; + } + vm_info->mem_cookie = mem_cookie; + rc = gh_irq_wait_for_lend_v2(vm_info->irq_label, GH_PRIMARY_VM, + &fts_vm_irq_on_lend_callback, info); + atomic_set(&vm_info->tvm_owns_irq, 0); + atomic_set(&vm_info->tvm_owns_iomem, 0); + init_completion(&info->resource_checkpoint); +#else + mem_cookie = gh_mem_notifier_register(GH_MEM_NOTIFIER_TAG_TOUCH, + fts_vm_mem_on_release_handler, info); + if (!mem_cookie) { + pr_err("Failed to register on release mem notifier\n"); + rc = -EINVAL; + goto init_fail; + } + vm_info->mem_cookie = mem_cookie; + atomic_set(&vm_info->pvm_owns_irq, 1); + atomic_set(&vm_info->pvm_owns_iomem, 1); +#endif + return rc; +init_fail: + fts_vm_deinit(info); +fail: + return rc; +} + +static void fts_dt_parse_trusted_touch_info(struct fts_ts_info *info) +{ + struct device_node *np = info->client->dev.of_node; + int rc = 0; + const char *selection; + const char *environment; + + rc = of_property_read_string(np, "st,trusted-touch-mode", + &selection); + if (rc) { + dev_warn(&info->client->dev, + "%s: No trusted touch mode selection made\n", __func__); + } + + if (!strcmp(selection, "vm_mode")) { + atomic_set(&info->trusted_touch_mode, TRUSTED_TOUCH_VM_MODE); + pr_err("Selected trusted touch mode to VM mode\n"); + } else { + atomic_set(&info->trusted_touch_mode, TRUSTED_TOUCH_MODE_NONE); + pr_err("Invalid trusted_touch mode\n"); + } + + rc = of_property_read_string(np, "st,touch-environment", + &environment); + if (rc) { + dev_warn(&info->client->dev, + "%s: No trusted touch mode environment\n", __func__); + } + info->touch_environment = environment; + pr_err("Trusted touch environment:%s\n", + info->touch_environment); +} + +static void fts_trusted_touch_init(struct fts_ts_info *info) +{ + int rc = 0; + + atomic_set(&info->trusted_touch_initialized, 0); + init_completion(&info->trusted_touch_powerdown); + fts_dt_parse_trusted_touch_info(info); + + /* Get clocks */ + info->core_clk = devm_clk_get(info->client->dev.parent, + "m-ahb"); + if (IS_ERR(info->core_clk)) { + info->core_clk = NULL; + dev_warn(&info->client->dev, + "%s: core_clk is not defined\n", __func__); + } + + info->iface_clk = devm_clk_get(info->client->dev.parent, + "se-clk"); + if (IS_ERR(info->iface_clk)) { + info->iface_clk = NULL; + dev_warn(&info->client->dev, + "%s: iface_clk is not defined\n", __func__); + } + if (atomic_read(&info->trusted_touch_mode) == TRUSTED_TOUCH_VM_MODE) { + rc = fts_vm_init(info); + if (rc) + pr_err("Failed to init VM\n"); + } + atomic_set(&info->trusted_touch_initialized, 1); +} + +static ssize_t fts_trusted_touch_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info; + + if (!client) + return scnprintf(buf, PAGE_SIZE, "client is null\n"); + + info = i2c_get_clientdata(client); + if (!info) { + logError(0, "info is null\n"); + return scnprintf(buf, PAGE_SIZE, "info is null\n"); + } + + return scnprintf(buf, PAGE_SIZE, "%d", + atomic_read(&info->trusted_touch_enabled)); +} + +static ssize_t fts_trusted_touch_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info; + unsigned long value; + int err = 0; + + if (!client) + return -EIO; + info = i2c_get_clientdata(client); + if (!info) { + logError(0, "info is null\n"); + return -EIO; + } + if (count > 2) + return -EINVAL; + err = kstrtoul(buf, 10, &value); + if (err != 0) + return err; + + if (!atomic_read(&info->trusted_touch_initialized)) + return -EIO; + +#ifdef CONFIG_ARCH_QTI_VM + err = fts_handle_trusted_touch_tvm(info, value); + if (err) { + pr_err("Failed to handle trusted touch in tvm\n"); + return -EINVAL; + } +#else + err = fts_handle_trusted_touch_pvm(info, value); + if (err) { + pr_err("Failed to handle trusted touch in pvm\n"); + return -EINVAL; + } +#endif + err = count; + return err; +} + +#endif + +//struct chipInfo ftsInfo; + +/** + * #ifdef PHONE_GESTURE + * extern struct mutex gestureMask_mutex; + * #endif + */ + +static char tag[8] = "[ FTS ]\0"; + +static char fts_ts_phys[64]; +static u32 typeOfComand[CMD_STR_LEN] = {0}; +static int numberParameters; +#ifdef USE_ONE_FILE_NODE +static int feature_feasibility = ERROR_OP_NOT_ALLOW; +#endif +#ifdef PHONE_GESTURE +static u8 mask[GESTURE_MASK_SIZE + 2]; +//extern u16 gesture_coordinates_x[GESTURE_COORDS_REPORT_MAX]; +//extern u16 gesture_coordinates_y[GESTURE_COORDS_REPORT_MAX]; +//extern int gesture_coords_reported; +//extern struct mutex gestureMask_mutex; +#ifdef USE_CUSTOM_GESTURES +static int custom_gesture_res; +#endif +#endif +#ifdef USE_NOISE_PARAM +static u8 noise_params[NOISE_PARAMETERS_SIZE] = {0}; +#endif +static void fts_interrupt_enable(struct fts_ts_info *info); +static int fts_init_afterProbe(struct fts_ts_info *info); +static int fts_mode_handler(struct fts_ts_info *info, int force); +static int fts_command(struct fts_ts_info *info, unsigned char cmd); +static int fts_chip_initialization(struct fts_ts_info *info); +static int fts_enable_reg(struct fts_ts_info *info, bool enable); + +static struct drm_panel *active_panel; +#if defined(CONFIG_DRM) +static void st_ts_panel_notifier_callback(enum panel_event_notifier_tag tag, + struct panel_event_notification *notification, void *client_data) +{ + struct fts_ts_info *info = client_data; + + if (!notification) { + pr_err("Invalid notification\n"); + return; + } + + logError(0, "%s %s Notification type:%d, early_trigger:%d, sensor_sleep:%d", tag, __func__, + notification->notif_type, + notification->notif_data.early_trigger, + info->sensor_sleep); + + switch (notification->notif_type) { + case DRM_PANEL_EVENT_UNBLANK: + if (!notification->notif_data.early_trigger) { + logError(0, "%s %s: DRM_PANEL_EVENT_UNBLANK\n", tag, __func__); + queue_work(info->event_wq, &info->resume_work); + } + break; + + case DRM_PANEL_EVENT_BLANK: + if (!notification->notif_data.early_trigger) { + logError(0, "%s %s: DRM_PANEL_EVENT_BLANK\n", tag, __func__); + queue_work(info->event_wq, &info->suspend_work); + } + break; + + case DRM_PANEL_EVENT_BLANK_LP: + logError(0, "%s %s:received lp event\n", tag, __func__); + break; + + case DRM_PANEL_EVENT_FPS_CHANGE: + logError(0, "%s %s: Received fps change old fps:%d new fps:%d\n", + tag, __func__, + notification->notif_data.old_fps, + notification->notif_data.new_fps); + break; + + default: + logError(0, "%s %s:notification serviced :%d\n", + tag, __func__, notification->notif_type); + break; + } +} + +static int st_register_for_panel_events(struct device_node *dp, + struct fts_ts_info *info) +{ + void *cookie; + + cookie = panel_event_notifier_register(PANEL_EVENT_NOTIFICATION_PRIMARY, + PANEL_EVENT_NOTIFIER_CLIENT_PRIMARY_TOUCH, active_panel, + &st_ts_panel_notifier_callback, info); + if (!cookie) { + pr_err("Failed to register for panel events\n"); + return -1; + } + + logError(0, "%s %s registered for panel notifications panel: 0x%x\n", + tag, __func__, active_panel); + + info->notifier_cookie = cookie; + + return 0; +} +#endif + +void touch_callback(unsigned int status) +{ + /* Empty */ +} + +unsigned int le_to_uint(const unsigned char *ptr) +{ + return (unsigned int) ptr[0] + (unsigned int) ptr[1] * 0x100; +} + +unsigned int be_to_uint(const unsigned char *ptr) +{ + return (unsigned int) ptr[1] + (unsigned int) ptr[0] * 0x100; +} + +void release_all_touches(struct fts_ts_info *info) +{ + unsigned int type = MT_TOOL_FINGER; + int i; + + for (i = 0; i < TOUCH_ID_MAX; i++) { +#ifdef STYLUS_MODE + if (test_bit(i, &info->stylus_id)) + type = MT_TOOL_PEN; +#endif + input_mt_slot(info->input_dev, i); + input_mt_report_slot_state(info->input_dev, type, 0); + input_report_abs(info->input_dev, ABS_MT_TRACKING_ID, -1); + } + input_sync(info->input_dev); + info->touch_id = 0; +#ifdef STYLUS_MODE + info->stylus_id = 0; +#endif +} + +/************************* FW UPGGRADE *********************************/ +/* update firmware*/ +/** + * echo 01/00 > fwupdate perform a fw update taking the FW to burn always + * from a bin file stored in /system/etc/firmware, 01= force the FW update + * whicheve fw_version and config_id; 00=perform a fw update only if the fw + * in the file has a greater fw_version or config_id + */ + +/** + * cat fwupdate to show the result of the burning procedure + * (example output in the terminal = "AA00000001BB" if the switch is enabled) + */ + +/** + * echo 01/00 > fwupdate; cat fwupdate to perform both operation stated before + * in just one call + */ +static ssize_t fts_fwupdate_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int ret, mode; + /*const struct firmware *fw = NULL;*/ + /*char *firmware_name = "st_fts.bin";*/ + struct Firmware fwD; + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + int orig_size; + u8 *orig_data; + + /* reading out firmware upgrade mode */ + ret = kstrtoint(buf, 10, &mode); + if (ret != 0) { + pr_err("%s: ret = %d\n", __func__, ret); + return -EINVAL; + } + + fwD.data = NULL; + ret = getFWdata_nocheck(PATH_FILE_FW, &orig_data, &orig_size, 0); + if (ret < OK) { + logError(1, "%s %s: impossible retrieve FW... ERROR %08X\n", + tag, __func__, ERROR_MEMH_READ); + ret = (ret | ERROR_MEMH_READ); + goto END; + } + + ret = parseBinFile(orig_data, orig_size, &fwD, !mode); + if (ret < OK) { + logError(1, "%s %s: impossible parse ERROR %08X\n", + tag, __func__, ERROR_MEMH_READ); + ret = (ret | ERROR_MEMH_READ); + goto END; + } + + logError(0, "%s Starting flashing procedure...\n", tag); + ret = flash_burn(&fwD, mode, !mode); + + if (ret < OK && ret != (ERROR_FW_NO_UPDATE | ERROR_FLASH_BURN_FAILED)) + logError(0, "%s flashProcedure: ERROR %02X\n", + tag, ERROR_FLASH_PROCEDURE); + logError(0, "%s flashing procedure Finished!\n", tag); + +END: + kfree(fwD.data); + info->fwupdate_stat = ret; + + if (ret < OK) + logError(1, "%s %s Unable to upgrade firmware! ERROR %08X\n", + tag, __func__, ret); + + return count; +} + +static ssize_t fts_fwupdate_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + + //fwupdate_stat: ERROR code Returned by flashProcedure. + return snprintf(buf, PAGE_SIZE, "AA%08XBB\n", info->fwupdate_stat); +} + +/****UTILITIES (current fw_ver/conf_id, active mode, file fw_ver/conf_id)****/ +/** + * cat appid show on the terminal fw_version.config_id of + * the FW running in the IC + */ +static ssize_t fts_appid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int error; + + error = snprintf(buf, PAGE_SIZE, "%x.%x\n", ftsInfo.u16_fwVer, + ftsInfo.u16_cfgId); + return error; +} + +/** + * cat mode_active to show the bitmask of which indicate the modes/features + * which are running on the IC in a specific istant oftime (example output in + * the terminal = "AA10000000BB" only senseOn performed) + */ +static ssize_t fts_mode_active_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + + logError(1, "%s Current mode active = %08X\n", tag, info->mode); + //return sprintf(buf, "AA%08XBB\n", info->mode); + return snprintf(buf, PAGE_SIZE, "AA%08XBB\n", info->mode); +} + +/** + * cat fw_file_test show on the terminal fw_version and config_id of the FW + * stored in the fw file/header file + */ +static ssize_t fts_fw_test_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct Firmware fw; + int ret; + + fw.data = NULL; + ret = readFwFile(PATH_FILE_FW, &fw, 0); + + if (ret < OK) + logError(1, "%s Error during reading FW file! ERROR %08X\n", + tag, ret); + else { + logError(1, "%s fw_version = %04X, config_version = %04X, ", + tag, fw.fw_ver, fw.config_id); + logError(1, "size = %dbytes\n", fw.data_size); + } + + kfree(fw.data); + return 0; +} + +/** + * cat lockdown_info to show the lockdown info on the terminal + * (example output in the terminal = "AA00000000X1X2..X10BB" ) + * where first 4 bytes correspond t + */ +static ssize_t fts_lockdown_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + u8 data[LOCKDOWN_CODE_SIZE] = {0}; + int ret, size = 100; + char buff[CMD_STR_LEN] = {0}; + char all_strbuff[100] = {0}; + + ret = fts_disableInterrupt(); + if (ret < OK) + goto END; + + ret = lockDownInfo((u8 *)data, LOCKDOWN_CODE_SIZE); + if (ret < OK) + goto END; + +END: + ret |= fts_enableInterrupt(); + + snprintf(buff, sizeof(buff), "%02X", 0xAA); + strlcat(all_strbuff, buff, size); + snprintf(buff, sizeof(buff), "%08X", ret); + strlcat(all_strbuff, buff, size); + if (ret >= OK) { + for (ret = 0; ret < LOCKDOWN_CODE_SIZE; ret++) { + snprintf(buff, sizeof(buff), "%02X", data[ret]); + strlcat(all_strbuff, buff, size); + } + } else { + logError(1, "%s Error while reading lockdown info = %08X\n", + tag, ret); + } + snprintf(buff, sizeof(buff), "%02X", 0xBB); + strlcat(all_strbuff, buff, size); + return snprintf(buf, TSP_BUF_SIZE, "%s\n", all_strbuff); +} + +/** + * cat strength_frame to obtain strength data + * the string returned in the shell is made up as follow: + * AA = start byte + * X1X2X3X4 = 4 bytes in HEX format which represent an + * error code (00000000 no error) + * + * if error code is all 0s + * FF = 1 byte in HEX format number of rows + * SS = 1 byte in HEX format number of columns + * N1, ... = the decimal value of each node separated by a coma + * + * BB = end byte + */ +static ssize_t fts_strength_frame_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + char *p = (char *)buf; + /*unsigned int temp;*/ + /*int res;*/ + /*struct i2c_client *client = to_i2c_client(dev); */ + /*struct fts_ts_info *info = i2c_get_clientdata(client); */ + + if (sscanf(p, "%x ", &typeOfComand[0]) != 1) + return -EINVAL; + + logError(1, "%s %s: Type of Strength Frame selected: %d\n", tag, + __func__, typeOfComand[0]); + return count; +} + +static ssize_t fts_strength_frame_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct MutualSenseFrame frame; + int res = ERROR_OP_NOT_ALLOW, j, size = 6*2; + int count = 0; + u16 type = 0; + char *all_strbuff = NULL; + char buff[CMD_STR_LEN] = {0}; + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + + frame.node_data = NULL; + + res = fts_disableInterrupt(); + if (res < OK) + goto END; + + res = senseOn(); +#ifdef PHONE_KEY + res = keyOn(); +#endif + if (res < OK) { + logError(1, "%s %s: could not start scanning! ERROR %08X\n", + tag, __func__, res); + goto END; + } + msleep(WAIT_FOR_FRESH_FRAMES); + + res = senseOff(); +#ifdef PHONE_KEY + res = keyOff(); +#endif + if (res < OK) { + logError(1, "%s %s: could not finish scanning! ERROR %08X\n", + tag, __func__, res); + goto END; + } + /* mdelay(WAIT_AFTER_SENSEOFF); */ + msleep(WAIT_AFTER_SENSEOFF); + flushFIFO(); + + switch (typeOfComand[0]) { + case 1: + type = ADDR_NORM_TOUCH; + break; +#ifdef PHONE_KEY + case 2: + type = ADDR_NORM_MS_KEY; + break; +#endif + default: + logError(1, "%s %s: Strength type %d not valid! ERROR %08X\n", + tag, __func__, typeOfComand[0], ERROR_OP_NOT_ALLOW); + res = ERROR_OP_NOT_ALLOW; + goto END; + } + + res = getMSFrame(type, &frame, 0); + if (res < OK) { + logError(1, "%s %s: could not get the frame! ERROR %08X\n", + tag, __func__, res); + goto END; + } else { + size += (res * 6); + logError(0, "%s The frame size is %d words\n", tag, res); + res = OK; + print_frame_short("MS Strength frame =", + array1dTo2d_short(frame.node_data, + frame.node_data_size, + frame.header.sense_node), + frame.header.force_node, + frame.header.sense_node); + } + +END: + flushFIFO(); + release_all_touches(info); + fts_mode_handler(info, 1); + + all_strbuff = kmalloc_array(size, sizeof(char), GFP_KERNEL); + + if (all_strbuff != NULL) { + memset(all_strbuff, 0, size); + snprintf(buff, sizeof(buff), "%02X", 0xAA); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%08X", res); + strlcat(all_strbuff, buff, size); + + if (res >= OK) { + snprintf(buff, sizeof(buff), "%02X", + (u8) frame.header.force_node); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", + (u8) frame.header.sense_node); + strlcat(all_strbuff, buff, size); + + for (j = 0; j < frame.node_data_size; j++) { + snprintf(buff, sizeof(buff), "%d,", + frame.node_data[j]); + strlcat(all_strbuff, buff, size); + } + + kfree(frame.node_data); + } + + snprintf(buff, sizeof(buff), "%02X", 0xBB); + strlcat(all_strbuff, buff, size); + + count = snprintf(buf, TSP_BUF_SIZE, "%s\n", all_strbuff); + kfree(all_strbuff); + } else { + logError(1, "%s %s: Unable toallocate all_strbuff!ERROR %08X\n", + tag, ERROR_ALLOC); + } + + fts_enableInterrupt(); + return count; +} + +/********** FEATURES *********************/ + +/** + * TODO: edit this function according to the features policy to + * allow during the screen on/off, following is shown an example + * but check always with ST for more details + */ +int check_feature_feasibility(struct fts_ts_info *info, unsigned int feature) +{ + int res = OK; + + /** + * Example based on the status of the screen and + * on the feature that is trying to enable + */ + + /*Example based only on the feature that is going to be activated*/ + switch (feature) { + case FEAT_GESTURE: + if (info->cover_enabled == 1) { + res = ERROR_OP_NOT_ALLOW; + + logError(1, "%s %s:Feature not allowed when in Cover ", + tag, __func__); + logError(1, "mode %08X\n", res); + /** + * for example here can be place a code for + * disabling the cover mode when gesture is + * activated + */ + } + break; + + case FEAT_COVER: + if (info->gesture_enabled == 1) { + res = ERROR_OP_NOT_ALLOW; + /*logError(1,"Feature not allowed*/ + /*when Gestures enabled!");*/ + logError(1, "s %s: Feature not allowed when Gestures ", + tag, __func__); + logError(1, "enabled%08X\n", res); + /** + * for example here can be place a code for + * disabling the gesture mode when cover is + * activated (that means that cover mode has + * an higher priority on gesture mode) + */ + } + break; + + default: + logError(1, "%s %s: Feature Allowed!\n", tag, __func__); + } + return res; +} + +#ifdef USE_ONE_FILE_NODE +/** + * echo XXXX 00/01 > feature_enable + * set the feature to disable/enable. + * XXXX = 4 bytes which identify the feature + * + * cat feature_enable + * set the enabled mode/features in the IC + * and return an error code + * + * echo XXXX 00/01 > feature_enable; + * cat feature_enable to perform both action stated + * before in just one call + */ +static ssize_t fts_feature_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + char *p = (char *)buf; + unsigned int temp; + int res = OK; + + if ((count - 8 + 1) / 3 != 1) { + logError(1, "%s fts_feature_enable: ", tag); + logError(1, "Number of parameter wrong! %d > %d\n", + (count - 8 + 1) / 3, 1); + return -EINVAL; + } + + if (sscanf(p, "%08X ", &temp) != 1) + return -EINVAL; + p += 9; + res = check_feature_feasibility(info, temp); + if (res < OK) + return -EINVAL; + + switch (temp) { +#ifdef PHONE_GESTURE + case FEAT_GESTURE: + if (sscanf(p, "%02X ", &info->gesture_enabled) != 1) + return -EINVAL; + + logError(1, "%s fts_feature_enable: Gesture Enabled = %d\n", + tag, info->gesture_enabled); + + break; +#endif + +#ifdef GLOVE_MODE + case FEAT_GLOVE: + if (sscanf(p, "%02X ", &info->glove_enabled) != 1) + return -EINVAL; + + logError(1, "%s fts_feature_enable: Glove Enabled = %d\n", + tag, info->glove_enabled); + + break; +#endif + +#ifdef STYLUS_MODE + case FEAT_STYLUS: + if (sscanf(p, "%02X ", &info->stylus_enabled) != 1) + return -EINVAL; + + logError(1, "%s fts_feature_enable: Stylus Enabled = %d\n", + tag, info->stylus_enabled); + + break; +#endif + +#ifdef COVER_MODE + case FEAT_COVER: + if (sscanf(p, "%02X ", &info->cover_enabled) != 1) + return -EINVAL; + + logError(1, "%s fts_feature_enable: Cover Enabled = %d\n", + tag, info->cover_enabled); + + break; +#endif + +#ifdef CHARGER_MODE + case FEAT_CHARGER: + if (sscanf(p, "%02X ", &info->charger_enabled) != 1) + return -EINVAL; + + logError(1, "%s fts_feature_enable: Charger Enabled= %d\n", + tag, info->charger_enabled); + + break; +#endif + +#ifdef VR_MODE + case FEAT_VR: + if (sscanf(p, "%02X ", &info->vr_enabled) != 1) + return -EINVAL; + + logError(1, "%s fts_feature_enable: VR Enabled = %d\n", + tag, info->vr_enabled); + + break; +#endif + +#ifdef EDGE_REJ + case FEAT_EDGE_REJECTION: + if (sscanf(p, "%02X ", &info->edge_rej_enabled) != 1) + return -EINVAL; + logError(1, "%s %s: Edge Rejection Enabled= %d\n", + tag, __func__, info->edge_rej_enabled); + + break; +#endif + +#ifdef CORNER_REJ + case FEAT_CORNER_REJECTION: + if (sscanf(p, "%02X ", &info->corner_rej_enabled) != 1) + return -EINVAL; + + logError(1, "%s %s: Corner Rejection Enabled= %d\n", + tag, __func__, info->corner_rej_enabled); + + break; +#endif + +#ifdef EDGE_PALM_REJ + case FEAT_EDGE_PALM_REJECTION: + if (sscanf(p, "%02X", &info->edge_palm_rej_enabled) != 1) + return -EINVAL; + + logError(1, "%s %s:Edge Palm RejectionEnabled= %d\n", + tag, __func__, info->edge_palm_rej_enabled); + + break; +#endif + default: + logError(1, "%s %s: Feature %08X not valid! ERROR %08X\n", + tag, __func__, temp, ERROR_OP_NOT_ALLOW); + res = ERROR_OP_NOT_ALLOW; + } + feature_feasibility = res; + + if (feature_feasibility >= OK) + feature_feasibility = fts_mode_handler(info, 1); + else { + logError(1, "%s %s: Call echo XXXX 00/01 > feature_enable ", + tag, __func__); + logError(1, "with a correct feature! ERROR %08X\n", res); + } + return count; +} + +static ssize_t fts_feature_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + char buff[CMD_STR_LEN] = {0}; + int size = 6 * 2; + u8 *all_strbuff = NULL; + int count = 0; + + if (feature_feasibility < OK) { + logError(1, + "%s %s:Call before echo 00/01 > feature_enable %08X\n", + tag, __func__, feature_feasibility); + } + + all_strbuff = kmalloc(size, GFP_KERNEL); + if (all_strbuff != NULL) { + memset(all_strbuff, 0, size); + + snprintf(buff, sizeof(buff), "%02X", 0xAA); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%08X", feature_feasibility); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", 0xBB); + strlcat(all_strbuff, buff, size); + + count = snprintf(buf, TSP_BUF_SIZE, "%s\n", all_strbuff); + kfree(all_strbuff); + } else { + logError(1, + "%s %s: Unable to allocate all_strbuff! ERROR %08X\n", + tag, __func__, ERROR_ALLOC); + } + + feature_feasibility = ERROR_OP_NOT_ALLOW; + return count; +} + +#else + +#ifdef EDGE_REJ +/** + * echo 01/00 > edge_rej to enable/disable edge rejection + * cat edge_rej to show the status of the edge_rej_enabled + * switch (example output in the terminal = "AA00000001BB" + * if the switch is enabled) + * + * echo 01/00 > edge_rej; cat edge_rej to enable/disable + * edge rejection and see the switch status in just one call + */ +static ssize_t fts_edge_rej_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + char buff[CMD_STR_LEN] = {0}; + int size = 6 * 2; + u8 *all_strbuff = NULL; + int count = 0; + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + + logError(0, "%s %s: edge_rej_enabled = %d\n", + tag, __func__, info->edge_rej_enabled); + + all_strbuff = kmalloc(size, GFP_KERNEL); + if (all_strbuff != NULL) { + memset(all_strbuff, 0, size); + + snprintf(buff, sizeof(buff), "%02X", 0xAA); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%08X", info->edge_rej_enabled); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", 0xBB); + strlcat(all_strbuff, buff, size); + + count = snprintf(buf, TSP_BUF_SIZE, "%s\n", all_strbuff); + kfree(all_strbuff); + } else + logError(1, + "%s %s: Unable to allocate all_strbuff! ERROR %08X\n", + tag, __func__, ERROR_ALLOC); + + return count; +} + +static ssize_t fts_edge_rej_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + char *p = (char *)buf; + unsigned int temp; + int res; + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + + /** + * in case of a different elaboration of the input, + * just modify this initial part of the code + */ + if ((count + 1) / 3 != 1) { + logError(1, + "%s %s:Number bytes of parameter wrong!%d != %d byte\n", + tag, __func__, (count + 1) / 3, 1); + return -EINVAL; + } + + if (sscanf(p, "%02X ", &temp) != 1) + return -EINVAL; + + p += 3; + + /** + * this is a standard code that should be always + * used when a feature is enabled! + * + * first step : check if the wanted feature can be enabled + * + * second step: call fts_mode_handler to actually enable it + * NOTE: Disabling a feature is always allowed by default + */ + res = check_feature_feasibility(info, FEAT_EDGE_REJECTION); + if (res < OK && temp != FEAT_DISABLE) + return -EINVAL; + + info->edge_rej_enabled = temp; + res = fts_mode_handler(info, 1); + if (res < OK) { + logError(1, + "%s %s: Error during fts_mode_handler! ERROR %08X\n", + tag, __func__, res); + } + } + + return count; +} +#endif + +#ifdef CORNER_REJ +/** + * echo 01/00 > corner_rej to enable/disable corner rejection + * cat corner_rej to show the status of the + * corner_rej_enabled switch (example output in the terminal + * = "AA00000001BB" if the switch is enabled) + * + * echo 01/00 > corner_rej; cat corner_rej to enable/disable + * corner rejection and see the switch status in just one call + */ +static ssize_t fts_corner_rej_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + char buff[CMD_STR_LEN] = {0}; + int size = 6 * 2; + u8 *all_strbuff = NULL; + int count = 0; + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + + logError(0, "%s %s: corner_rej_enabled = %d\n", + tag, __func__, info->corner_rej_enabled); + + all_strbuff = kmalloc(size, GFP_KERNEL); + if (all_strbuff != NULL) { + memset(all_strbuff, 0, size); + + snprintf(buff, sizeof(buff), "%02X", 0xAA); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%08X", info->corner_rej_enabled); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", 0xBB); + strlcat(all_strbuff, buff, size); + + count = snprintf(buf, TSP_BUF_SIZE, "%s\n", all_strbuff); + kfree(all_strbuff); + } else { + logError(1, "%s%s:Unable to allocate all_strbuff! ERROR %08X\n", + tag, __func__, ERROR_ALLOC); + } + + return count; +} + +static ssize_t fts_corner_rej_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + char *p = (char *)buf; + unsigned int temp; + int res; + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + + /** + * in case of a different elaboration of the input, + * just modify this initial part of the code according + * to customer needs + */ + if ((count + 1) / 3 != 1) { + logError(1, + "%s %s:Number bytes of parameter wrong!%d != %d byte\n", + tag, __func__, (count + 1) / 3, 1); + return -EINVAL; + } + + /*sscanf(p, "%02X ", &temp);*/ + if (sscanf(p, "%02X ", &temp) != 1) + return -EINVAL; + + p += 3; + + /** + * this is a standard code that should be always + * used when a feature is enabled! + * + * first step : check if the wanted feature + * can be enabled + * + * second step: call fts_mode_handler to + * actually enable it + * + * NOTE: Disabling a feature is always + * allowed by default + */ + res = check_feature_feasibility(info, FEAT_CORNER_REJECTION); + if (res >= OK || temp == FEAT_DISABLE) { + info->corner_rej_enabled = temp; + res = fts_mode_handler(info, 1); + if (res < OK) { + logError(1, + "%s %s: During fts_mode_handler!ERROR %08X\n", + tag, __func__, res); + } + } + + return count; +} +#endif + +#ifdef EDGE_PALM_REJ +/** + * echo 01/00 > edge_palm_rej + * to enable/disable edge palm rejection + * + * cat edge_palm_rej to show the status of the + * edge_palm_rej_enabled switch (example output + * in the terminal = "AA00000001BB" if the switch is enabled) + * + * echo 01/00 > edge_palm_rej; cat edge_palm_rej + * to enable/disable edge palm rejection and see + * the switch status in just one call + */ +static ssize_t fts_edge_palm_rej_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + char buff[CMD_STR_LEN] = {0}; + int size = 6 * 2; + u8 *all_strbuff = NULL; + int count = 0; + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + + logError(0, "%s %s: edge_palm_rej_enabled = %d\n", + tag, __func__, info->edge_palm_rej_enabled); + + all_strbuff = kmalloc(size, GFP_KERNEL); + if (all_strbuff != NULL) { + memset(all_strbuff, 0, size); + + snprintf(buff, sizeof(buff), "%02X", 0xAA); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%08X", + info->edge_palm_rej_enabled); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", 0xBB); + strlcat(all_strbuff, buff, size); + + count = snprintf(buf, TSP_BUF_SIZE, "%s\n", + all_strbuff); + kfree(all_strbuff); + } else { + logError(1, "%s%s:Unable to allocate all_strbuff! %08X\n", + tag, __func__, ERROR_ALLOC); + } + + return count; +} + +static ssize_t fts_edge_palm_rej_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + char *p = (char *)buf; + unsigned int temp; + int res; + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + + /** + * in case of a different elaboration of the input, + * just modify this initial part of the code according + * to customer needs + */ + if ((count + 1) / 3 != 1) { + logError(1, + "%s%s:Number bytes of parameter wrong! %d != %d byte\n", + tag, __func__, (count + 1) / 3, 1); + return -EINVAL; + } + /*sscanf(p, "%02X ", &temp);*/ + if (sscanf(p, "%02X ", &temp) != 1) + return -EINVAL; + + p += 3; + + /** + * this is a standard code that should be + * always used when a feature is enabled! + * + * first step : check if the wanted feature can be enabled + * + * second step: call fts_mode_handler to actually enable it + * + * NOTE: Disabling a feature is always allowed by default + */ + res = check_feature_feasibility(info, FEAT_EDGE_PALM_REJECTION); + if (res >= OK || temp == FEAT_DISABLE) { + info->edge_palm_rej_enabled = temp; + res = fts_mode_handler(info, 1); + if (res < OK) { + logError(1, "%s%s:Error in fts_mode_handler!%08X\n", + tag, __func__, res); + } + } + + return count; +} +#endif + +#ifdef CHARGER_MODE +/** + * echo 01/00 > charger_mode to enable/disable charger mode + * + * cat charger_mode to show the status of + * the charger_enabled switch (example output in the terminal + * = "AA00000001BB" if the switch is enabled) + * + * echo 01/00 > charger_mode; cat charger_mode + * to enable/disable charger mode and see the + * switch status in just one call + */ +static ssize_t fts_charger_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + char buff[CMD_STR_LEN] = {0}; + int size = 6 * 2; + u8 *all_strbuff = NULL; + int count = 0; + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + + logError(0, "%s %s:charger_enabled = %d\n", + tag, __func__, info->charger_enabled); + + all_strbuff = kmalloc(size, GFP_KERNEL); + if (all_strbuff != NULL) { + memset(all_strbuff, 0, size); + + snprintf(buff, sizeof(buff), "%02X", 0xAA); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%08X", info->charger_enabled); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", 0xBB); + strlcat(all_strbuff, buff, size); + + count = snprintf(buf, TSP_BUF_SIZE, "%s\n", all_strbuff); + kfree(all_strbuff); + } else { + logError(1, "%s %s:Unable to allocate all_strbuff! %08X\n", + tag, __func__, ERROR_ALLOC); + } + + return count; +} + +static ssize_t fts_charger_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + char *p = (char *)buf; + unsigned int temp; + int res; + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + + /** + * in case of a different elaboration of the input, + * just modify this initial part of the code + * according to customer needs + */ + if ((count + 1) / 3 != 1) { + logError(1, "%s %s:Size of parameter wrong! %d != %d byte\n", + tag, __func__, (count + 1) / 3, 1); + return -EINVAL; + } + /*sscanf(p, "%02X ", &temp);*/ + if (sscanf(p, "%02X ", &temp) != 1) + return -EINVAL; + + p += 3; + + /** + * this is a standard code that should be always + * used when a feature is enabled! + * + * first step : check if the wanted feature + * can be enabled + * second step: call fts_mode_handler to + * actually enable it + * + * NOTE: Disabling a feature is always + * allowed by default + */ + res = check_feature_feasibility(info, FEAT_CHARGER); + if (res >= OK || temp == FEAT_DISABLE) { + info->charger_enabled = temp; + res = fts_mode_handler(info, 1); + if (res < OK) { + logError(1, "%s %s: Error during fts_mode_handler! ", + tag, __func__); + logError(1, "ERROR %08X\n", res); + } + } + + return count; +} +#endif + +#ifdef GLOVE_MODE +/** + * echo 01/00 > glove_mode + * to enable/disable glove mode + * + * cat glove_mode to show the status of + * the glove_enabled switch (example output in the + * terminal = "AA00000001BB" if the switch is enabled) + * + * echo 01/00 > glove_mode; cat glove_mode + * to enable/disable glove mode and see the + * switch status in just one call + */ +static ssize_t fts_glove_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + char buff[CMD_STR_LEN] = {0}; + int size = 6 * 2; + u8 *all_strbuff = NULL; + int count = 0; + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + + logError(0, "%s %s:glove_enabled = %d\n", + tag, __func__, info->glove_enabled); + + all_strbuff = kmalloc(size, GFP_KERNEL); + if (all_strbuff != NULL) { + memset(all_strbuff, 0, size); + + snprintf(buff, sizeof(buff), "%02X", 0xAA); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%08X", info->glove_enabled); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", 0xBB); + strlcat(all_strbuff, buff, size); + + count = snprintf(buf, TSP_BUF_SIZE, "%s\n", all_strbuff); + kfree(all_strbuff); + } else { + logError(1, "%s %s:Unable to allocate all_strbuff! %08X\n", + tag, __func__, ERROR_ALLOC); + } + + return count; +} + +static ssize_t fts_glove_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + char *p = (char *)buf; + unsigned int temp; + int res; + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + + /** + * in case of a different elaboration of the input, + * just modify this initial part of the code + * according to customer needs + */ + if ((count + 1) / 3 != 1) { + logError(1, "%s %s:Size of parameter wrong! %d != %d byte\n", + tag, __func__, (count + 1) / 3, 1); + return -EINVAL; + } + + if (sscanf(p, "%02X ", &temp) != 1) + return -EINVAL; + + p += 3; + + /** + * this is a standard code that should be + * always used when a feature is enabled! + * + * first step : check if the wanted feature can be enabled + * + * second step: call fts_mode_handler to actually enable it + * + * NOTE: Disabling a feature is always allowed by default + */ + res = check_feature_feasibility(info, FEAT_GLOVE); + if (res >= OK || temp == FEAT_DISABLE) { + info->glove_enabled = temp; + res = fts_mode_handler(info, 1); + if (res < OK) { + logError(1, "%s %s: Error during fts_mode_handler! ", + tag, __func__); + logError(1, "ERROR %08X\n", res); + } + } + + return count; +} +#endif + +#ifdef VR_MODE +/** + * echo 01/00 > vr_mode to enable/disable vr mode + * + * cat vr_mode to show the status of + * the vr_enabled switch (example output in the + * terminal = "AA00000001BB" if the switch is enabled) + * + * echo 01/00 > vr_mode; cat vr_mode to enable/disable + * vr mode and see the switch status in just one call + */ +static ssize_t fts_vr_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + char buff[CMD_STR_LEN] = {0}; + int size = 6 * 2; + u8 *all_strbuff = NULL; + int count = 0; + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + + logError(0, "%s %s: vr_enabled = %d\n", + tag, __func__, info->vr_enabled); + + all_strbuff = kmalloc(size, GFP_KERNEL); + if (all_strbuff != NULL) { + memset(all_strbuff, 0, size); + + snprintf(buff, sizeof(buff), "%02X", 0xAA); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%08X", info->vr_enabled); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", 0xBB); + strlcat(all_strbuff, buff, size); + + count = snprintf(buf, TSP_BUF_SIZE, "%s\n", all_strbuff); + kfree(all_strbuff); + } else { + logError(1, + "%s %s: Unable to allocate all_strbuff! ERROR %08X\n", + tag, __func__, ERROR_ALLOC); + } + + return count; +} + +static ssize_t fts_vr_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + char *p = (char *)buf; + unsigned int temp; + int res; + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + + /** + * in case of a different elaboration of the input, + * just modify this initial part of the code + * according to customer needs + */ + if ((count + 1) / 3 != 1) { + logError(1, + "%s %s:Number bytes of parameter wrong!%d != %d byte\n", + tag, __func__, (count + 1) / 3, 1); + return -EINVAL; + } + + if (sscanf(p, "%02X ", &temp) != 1) + return -EINVAL; + + p += 3; + + /** + * this is a standard code that should be always + * used when a feature is enabled! + * + * first step : check if the wanted feature can be enabled + * second step: call fts_mode_handler to actually enable it + * + * NOTE: Disabling a feature is always allowed by default + */ + res = check_feature_feasibility(info, FEAT_VR); + if (res >= OK || temp == FEAT_DISABLE) { + info->vr_enabled = temp; + res = fts_mode_handler(info, 1); + if (res < OK) { + logError(1, "%s %s: Error in fts_mode_handler!%08X\n", + tag, __func__, res); + } + } + + return count; +} +#endif + +#ifdef COVER_MODE +/** + * echo 01/00 > cover_mode to enable/disable cover mode + * cat cover_mode to show the status of the + * cover_enabled switch (example output in the + * terminal = "AA00000001BB" if the switch is enabled) + * + * echo 01/00 > cover_mode; cat cover_mode to + * enable/disable cover mode and see the switch + * status in just one call + * + * NOTE: the cover can be handled also using a notifier, + * in this case the body of these functions + * should be copied in the notifier callback + */ +static ssize_t fts_cover_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + char buff[CMD_STR_LEN] = {0}; + int size = 6 * 2; + u8 *all_strbuff = NULL; + int count = 0; + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + + logError(0, "%s %s: cover_enabled = %d\n", + tag, __func__, info->cover_enabled); + + all_strbuff = kmalloc(size, GFP_KERNEL); + if (all_strbuff != NULL) { + memset(all_strbuff, 0, size); + + snprintf(buff, sizeof(buff), "%02X", 0xAA); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%08X", info->cover_enabled); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", 0xBB); + strlcat(all_strbuff, buff, size); + + count = snprintf(buf, TSP_BUF_SIZE, "%s\n", all_strbuff); + kfree(all_strbuff); + } else { + logError(1, + "%s %s:Unable to allocate all_strbuff! ERROR %08X\n", + tag, __func__, ERROR_ALLOC); + } + + return count; +} + +static ssize_t fts_cover_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + char *p = (char *)buf; + unsigned int temp; + int res; + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + + + /** + * in case of a different elaboration of the input, + * just modify this initial part of the code according + * to customer needs + */ + if ((count + 1) / 3 != 1) { + logError(1, + "%s %s:Number bytes of parameter wrong!%d != %d byte\n", + tag, __func__, (count + 1) / 3, 1); + return -EINVAL; + + } + + if (sscanf(p, "%02X ", &temp) != 1) + return -EINVAL; + p += 3; + + /** + * this is a standard code that should be + * always used when a feature is enabled! + * + * first step : check if the wanted feature can be enabled + * second step: call fts_mode_handler to actually enable it + * NOTE: Disabling a feature is always allowed by default + */ + res = check_feature_feasibility(info, FEAT_COVER); + if (res >= OK || temp == FEAT_DISABLE) { + info->cover_enabled = temp; + res = fts_mode_handler(info, 1); + if (res < OK) { + logError(1, "%s%s:Error in fts_mode_handler!%08X\n", + tag, __func__, res); + } + } + + + return count; +} +#endif + +#ifdef STYLUS_MODE +/** + * echo 01/00 > stylus_mode to enable/disable stylus mode + * cat stylus_mode to show the status of + * the stylus_enabled switch (example output in the + * terminal = "AA00000001BB" if the switch is enabled) + * + * echo 01/00 > stylus_mode; cat stylus_mode to + * enable/disable stylus mode and see the + * switch status in just one call + */ +static ssize_t fts_stylus_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + char buff[CMD_STR_LEN] = {0}; + int size = 6 * 2; + u8 *all_strbuff = NULL; + int count = 0; + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + + logError(0, "%s %s: stylus_enabled = %d\n", + tag, __func__, info->stylus_enabled); + + all_strbuff = kmalloc(size, GFP_KERNEL); + if (all_strbuff != NULL) { + memset(all_strbuff, 0, size); + + snprintf(buff, sizeof(buff), "%02X", 0xAA); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%08X", info->stylus_enabled); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", 0xBB); + strlcat(all_strbuff, buff, size); + + count = snprintf(buf, TSP_BUF_SIZE, "%s\n", all_strbuff); + kfree(all_strbuff); + } else { + logError(1, + "%s %s: Unable to allocate all_strbuff! ERROR %08X\n", + tag, __func__, ERROR_ALLOC); + } + + return count; +} + +static ssize_t fts_stylus_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + char *p = (char *)buf; + unsigned int temp; + int res; + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + + /** + * in case of a different elaboration of the input, + * just modify this initial part of the code + * according to customer needs + */ + if ((count + 1) / 3 != 1) { + logError(1, "%s %s:Size of parameter wrong! %d != %d byte\n", + tag, __func__, (count + 1) / 3, 1); + return -EINVAL; + } + + if (sscanf(p, "%02X ", &temp) != 1) + return -EINVAL; + p += 3; + + /** + * this is a standard code that should be + * always used when a feature is enabled! + * + * first step : check if the wanted feature can be enabled + * second step: call fts_mode_handler to actually enable it + * NOTE: Disabling a feature is always allowed by default + */ + res = check_feature_feasibility(info, FEAT_STYLUS); + if (res >= OK || temp == FEAT_DISABLE) { + info->stylus_enabled = temp; + res = fts_mode_handler(info, 1); + if (res < OK) { + logError(1, + "%s %s:Error during fts_mode_handler! %08X\n", + tag, __func__, res); + } + } + + return count; +} +#endif + +#endif + +/************** GESTURES *************/ +#ifdef PHONE_GESTURE +#ifdef USE_GESTURE_MASK + +/** + * if this define is used, a gesture bit mask + * is used as method to select the gestures + * to enable/disable + */ + +/** + * echo EE X1 X2 ... X8 > gesture_mask set + * the gesture mask to disable/enable; + * EE = 00(disable) or 01(enable); + * X1 ... X8 = gesture mask (example 06 00 ... 00 + * this gesture mask represent the gestures with ID = 1 and 2) + * can be specified from 1 to 8 bytes, if less than 8 bytes + * are specified the remaining bytes are kept as previous settings + * + * cat gesture_mask enable/disable the given mask, + * if one or more gestures is enabled the driver will + * automatically enable the gesture mode. + * If all the gestures are disabled the driver + * automatically will disable the gesture mode. + * At the end an error code will be printed + * (example output in the terminal = "AA00000000BB" + * if there are no errors) + * + * echo EE X1 X2 ... X8 > gesture_mask; + * cat gesture_mask perform in one + * command both actions stated before + */ +static ssize_t fts_gesture_mask_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + char buff[CMD_STR_LEN] = {0}; + int size = 6 * 2; + u8 *all_strbuff = NULL; + int count = 0, res, temp; + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + + if (mask[0] == 0) { + res = ERROR_OP_NOT_ALLOW; + logError(1, "%s %s:Call before echo enable/disable xx xx >", + tag, __func__); + logError(1, "%s %s: gesture_mask with a correct number of ", + tag, __func__); + logError(1, "parameters! ERROR %08X\n", res); + return -EINVAL; + } + + if (mask[1] == FEAT_ENABLE || mask[1] == FEAT_DISABLE) + res = updateGestureMask(&mask[2], mask[0], mask[1]); + else + res = ERROR_OP_NOT_ALLOW; + + if (res < OK) { + logError(1, "%s fts_gesture_mask_store: ERROR %08X\n", + tag, res); + } + + res |= check_feature_feasibility(info, FEAT_GESTURE); + temp = isAnyGestureActive(); + if (res >= OK || temp == FEAT_DISABLE) + info->gesture_enabled = temp; + + logError(1, "%s fts_gesture_mask_store:Gesture Enabled = %d\n", + tag, info->gesture_enabled); + + all_strbuff = kmalloc(size, GFP_KERNEL); + if (all_strbuff != NULL) { + memset(all_strbuff, 0, size); + + snprintf(buff, sizeof(buff), "%02X", 0xAA); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%08X", res); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", 0xBB); + strlcat(all_strbuff, buff, size); + + count = snprintf(buf, TSP_BUF_SIZE, "%s\n", all_strbuff); + kfree(all_strbuff); + } else { + logError(1, + "%s %s:Unable to allocate all_strbuff! ERROR %08X\n", + tag, __func__, ERROR_ALLOC); + } + + mask[0] = 0; + return count; +} + + +static ssize_t fts_gesture_mask_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + char *p = (char *)buf; + int n; + unsigned int temp; + + if ((count + 1) / 3 > GESTURE_MASK_SIZE + 1) { + logError(1, "%s %s: Number of bytes of parameter wrong! ", tag, + __func__); + logError(1, "%d > (enable/disable + %d )\n", (count + 1) / 3, + GESTURE_MASK_SIZE); + mask[0] = 0; + return -EINVAL; + } + mask[0] = ((count + 1) / 3) - 1; + for (n = 1; n <= (count + 1) / 3; n++) { + if (sscanf(p, "%02X ", &temp) != 1) + return -EINVAL; + p += 3; + mask[n] = (u8)temp; + logError(1, "%s mask[%d] = %02X\n", tag, n, mask[n]); + } + + return count; +} + +#else + +/** + * if this define is not used, + * to select the gestures to enable/disable + * are used the IDs of the gestures + * + * echo EE X1 X2 ... > gesture_mask set + * the gesture to disable/enable; EE = 00(disable) + * or 01(enable); X1 ... = gesture IDs + * (example 01 02 05... represent the gestures with + * ID = 1, 2 and 5) there is no limit of the parameters + * that can be passed, but of course the gesture IDs + * should be valid (all the valid IDs are listed + * in ftsGesture.h) + * + * cat gesture_mask enable/disable the + * given gestures, if one or more gestures is enabled + * the driver will automatically enable the gesture mode. + * If all the gestures are disabled the driver automatically + * will disable the gesture mode. At the end an error code + * will be printed (example output in the terminal = + * "AA00000000BB" if there are no errors) + * + * echo EE X1 X2 ... > gesture_mask; cat gesture_mask + * perform in one command both actions stated before + */ +static ssize_t fts_gesture_mask_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + char buff[CMD_STR_LEN] = {0}; + int size = 6 * 2; + u8 *all_strbuff = NULL; + int count = 0; + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + + logError(0, "%s %s: gesture_enabled = %d\n", tag, __func__, + info->gesture_enabled); + + all_strbuff = kmalloc(size, GFP_KERNEL); + if (all_strbuff != NULL) { + memset(all_strbuff, 0, size); + + snprintf(buff, sizeof(buff), "%02X", 0xAA); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%08X", info->gesture_enabled); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", 0xBB); + strlcat(all_strbuff, buff, size); + + count = snprintf(buf, TSP_BUF_SIZE, "%s\n", all_strbuff); + kfree(all_strbuff); + } else { + logError(1, + "%s %s: Unable to allocate all_strbuff! ERROR %08X\n", + tag, __func__, ERROR_ALLOC); + } + + return count; +} + +static ssize_t fts_gesture_mask_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + char *p = (char *)buf; + int n; + unsigned int temp; + int res; + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + + if ((count + 1) / 3 < 2 || (count + 1) / 3 > GESTURE_MASK_SIZE + 1) { + logError(1, + "%s %s:Number bytes of parameter wrong! %d %d bytes)\n", + tag, __func__, (count + 1) / 3, GESTURE_MASK_SIZE); + mask[0] = 0; + return -EINVAL; + } + + memset(mask, 0, GESTURE_MASK_SIZE + 2); + mask[0] = ((count + 1) / 3) - 1; + if (sscanf(p, "%02X ", &temp) != 1) + return -EINVAL; + + p += 3; + mask[1] = (u8)temp; + for (n = 1; n < (count + 1) / 3; n++) { + /*sscanf(p, "%02X ", &temp);*/ + if (sscanf(p, "%02X ", &temp) != 1) + return -EINVAL; + + p += 3; + gestureIDtoGestureMask((u8)temp, &mask[2]); + } + + for (n = 0; n < GESTURE_MASK_SIZE + 2; n++) + logError(1, "%s mask[%d] = %02X\n", tag, n, mask[n]); + + if (mask[0] == 0) { + res = ERROR_OP_NOT_ALLOW; + logError(1, "%s %s: Call before echo enable/disable xx xx ....", + tag, __func__); + logError(1, " > gesture_mask with parameters! ERROR %08X\n", + res); + + } else { + + if (mask[1] == FEAT_ENABLE || mask[1] == FEAT_DISABLE) + res = updateGestureMask(&mask[2], mask[0], mask[1]); + else + res = ERROR_OP_NOT_ALLOW; + + if (res < OK) + logError(1, "%s %s: ERROR %08X\n", tag, __func__, res); + + } + + res = check_feature_feasibility(info, FEAT_GESTURE); + temp = isAnyGestureActive(); + if (res >= OK || temp == FEAT_DISABLE) + info->gesture_enabled = temp; + res = fts_mode_handler(info, 0); + + return count; +} +#endif + +#ifdef USE_CUSTOM_GESTURES +/** + * allow to use user defined gestures + * + * echo ID X1 Y1 X2 Y2 ... X30 Y30 > + * add_custom_gesture add a custom gesture; + * ID = 1 byte that represent the gesture ID of + * the custom gesture (can be chosen only between + * the custom IDs defined in ftsGesture.h); + * X1 Y1 ... = a series of 30 points (x,y) which + * represent the gesture template. + * The loaded gesture is enabled automatically + * + * cat add_custom_gesture/remove_custom_gesture + * Print the error code of the last operation + * performed with the custom gestures + * (example output in the terminal = "AA00000000BB" + * if there are no errors) + * + * echo ID X1 Y1 X2 Y2 ... X30 Y30 > + * add_custom_gesture; cat add_custom_gesture + * perform in one command both actions stated before + */ +static ssize_t fts_add_custom_gesture_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + char buff[CMD_STR_LEN] = {0}; + int size = 6 * 2; + u8 *all_strbuff = NULL; + int count = 0; + + logError(0, "%s %s:Last Operation Result = %08X\n", + tag, __func__, custom_gesture_res); + + all_strbuff = kmalloc(size, GFP_KERNEL); + if (all_strbuff != NULL) { + memset(all_strbuff, 0, size); + + snprintf(buff, sizeof(buff), "%02X", 0xAA); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%08X", custom_gesture_res); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", 0xBB); + strlcat(all_strbuff, buff, size); + + count = snprintf(buf, TSP_BUF_SIZE, "%s\n", all_strbuff); + kfree(all_strbuff); + } else { + logError(1, + "%s %s:Unable to allocate all_strbuff! ERROR %08X\n", + tag, __func__, ERROR_ALLOC); + } + + return count; +} + +static ssize_t fts_add_custom_gesture_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + char *p = (char *)buf; + int n; + unsigned int temp; + u8 gestureID; + u8 gestMask[GESTURE_MASK_SIZE] = {0}; + u8 template[GESTURE_CUSTOM_POINTS]; + int res; + /*struct i2c_client *client = to_i2c_client(dev);*/ + /*struct fts_ts_info *info = i2c_get_clientdata(client);*/ + + if ((count + 1) / 3 != GESTURE_CUSTOM_POINTS + 1) { + logError(1, + "%s %s: Number bytes of parameter wrong! %d != %d\n", + tag, __func__, (count + 1) / 3, + GESTURE_CUSTOM_POINTS + 1); + res = ERROR_OP_NOT_ALLOW; + return -EINVAL; + } + if (sscanf(p, "%02X ", &temp) != 1) + return -EINVAL; + p += 3; + gestureID = (u8)temp; + + for (n = 1; n < (count + 1) / 3; n++) { + /*sscanf(p, "%02X ", &temp);*/ + if (sscanf(p, "%02X ", &temp) != 1) + return -EINVAL; + + p += 3; + template[n-1] = (u8)temp; + logError(1, "%s template[%d] = %02X\n", + tag, n-1, template[n-1]); + } + + res = fts_disableInterrupt(); + if (res >= OK) { + logError(1, "%s %s: Adding custom gesture ID = %02X\n", + tag, __func__, gestureID); + res = addCustomGesture(template, + GESTURE_CUSTOM_POINTS, gestureID); + if (res < OK) { + logError(1, + "%s %s:error during add custom gesture ", + tag, __func__); + logError(1, "ERROR %08X\n", res); + } else { + logError(1, + "%s %s:Enabling in the gesture mask...\n", + tag, __func__); + gestureIDtoGestureMask(gestureID, gestMask); + res = enableGesture(gestMask, GESTURE_MASK_SIZE); + if (res < OK) { + logError(1, "%s %s:error during enable gesture", + tag, __func__); + logError(1, " mask: ERROR %08X\n", res); + } else { + /*if (check_feature_feasibility(info,*/ + /*FEAT_GESTURE)==OK)*/ + /*info->gesture_enabled =*/ + /*isAnyGestureActive();*/ + /*uncomment if you want to activate*/ + /* automatically*/ + /*the gesture mode when a custom gesture*/ + /*is loaded*/ + logError(1, "%s %s:Custom Gesture enabled!\n", + tag, __func__, res); + } + } + } + res |= fts_enableInterrupt(); + + custom_gesture_res = res; + + return count; +} + + +/** + * echo ID > remove_custom_gesture + * remove a custom gesture; + * ID = 1 byte that represent the gesture ID + * of the custom gesture (can be chosen only + * between the custom IDs defined in ftsGesture.h); + * the same gesture is disabled automatically + */ +static ssize_t fts_remove_custom_gesture_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + char buff[CMD_STR_LEN] = {0}; + int size = 6 * 2; + u8 *all_strbuff = NULL; + int count = 0; + + logError(0, "%s %s:Last Operation Result = %08X\n", + tag, __func__, custom_gesture_res); + + all_strbuff = kmalloc(size, GFP_KERNEL); + if (all_strbuff != NULL) { + memset(all_strbuff, 0, size); + + snprintf(buff, sizeof(buff), "%02X", 0xAA); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%08X", custom_gesture_res); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", 0xBB); + strlcat(all_strbuff, buff, size); + + count = snprintf(buf, TSP_BUF_SIZE, "%s\n", all_strbuff); + kfree(all_strbuff); + } else { + logError(1, + "%s %s:Unable to allocate all_strbuff! ERROR %08X\n", + tag, __func__, ERROR_ALLOC); + } + + return count; +} + +static ssize_t fts_remove_custom_gesture_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + char *p = (char *)buf; + unsigned int temp; + int res; + u8 gestureID; + u8 gestMask[GESTURE_MASK_SIZE] = {0}; + /*struct i2c_client *client = to_i2c_client(dev);*/ + /*struct fts_ts_info *info = i2c_get_clientdata(client);*/ + + if ((count + 1) / 3 < 1) { + logError(1, + "%s %s:Number bytes of parameter wrong! %d != %d\n", + tag, __func__, (count + 1) / 3, 1); + res = ERROR_OP_NOT_ALLOW; + return -EINVAL; + } + + if (sscanf(p, "%02X ", &temp) != 1) + return -EINVAL; + p += 3; + gestureID = (u8)temp; + res = fts_disableInterrupt(); + if (res >= OK) { + logError(1, + "%s %s: Removing custom gesture ID = %02X\n", + tag, __func__, gestureID); + res = removeCustomGesture(gestureID); + if (res < OK) { + logError(1, + "%s %s:error in custom gesture:%08X\n", + tag, __func__, res); + } else { + logError(1, "%s %s: Enabling in the gesture mask...\n", + tag, __func__); + gestureIDtoGestureMask(gestureID, gestMask); + res = disableGesture(gestMask, GESTURE_MASK_SIZE); + if (res < OK) { + logError(1, + "%s %s:error in enable gesture mask:%08X\n", + tag, __func__, res); + } else { + /*if (check_feature_feasibility*/ + /*(info,FEAT_GESTURE)==OK)*/ + /*info->gesture_enabled = */ + /*isAnyGestureActive();*/ + /** + * uncomment if you want to disable + * automatically + * the gesture mode when a custom gesture is + * removed and no other gestures were enabled + */ + logError(1, "%s %s: Custom Gesture disabled!\n", + tag, __func__, res); + } + + } + } + + res |= fts_enableInterrupt(); + + custom_gesture_res = res; + return count; +} +#endif + + +/** + * cat gesture_coordinates to obtain the gesture coordinates + * the string returned in the shell follow this up as follow: + * AA = start byte + * X1X2X3X4 = 4 bytes in HEX format + * which represent an error code (00000000 no error) + */ + /**** if error code is all 0s ****/ +/** + * CC = 1 byte in HEX format number of coords + * (pair of x,y) returned + * + * X1X2 Y1Y2 ... = X1X2 2 bytes in HEX format for + * x[i] and Y1Y2 2 bytes in HEX format for y[i] (MSB first) + */ +/********************************/ +/* BB = end byte*/ +static ssize_t fts_gesture_coordinates_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + char buff[CMD_STR_LEN] = {0}; + int size = 6 * 2; + //u8 coords_num; + u8 *all_strbuff = NULL; + int count = 0, res, i = 0; + + logError(0, "%s %s: Getting gestures coordinates...\n", tag, __func__); + + if (gesture_coords_reported < OK) { + logError(1, "%s %s:invalid coordinates! ERROR %08X\n", + tag, __func__, gesture_coords_reported); + res = gesture_coords_reported; + } else { + /*coords are pairs of x,y (*2) where each coord*/ + /*is a short(2bytes=4char)(*4) + 1 byte(2char) num*/ + /*of coords (+2)*/ + size += gesture_coords_reported * 2 * 4 + 2; + /*coords_num = res;*/ + res = OK; + /*set error code to OK*/ + } + + all_strbuff = kmalloc(size, GFP_KERNEL); + if (all_strbuff != NULL) { + memset(all_strbuff, 0, size); + + snprintf(buff, sizeof(buff), "%02X", 0xAA); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%08X", res); + strlcat(all_strbuff, buff, size); + + if (res >= OK) { + snprintf(buff, sizeof(buff), "%02X", + gesture_coords_reported); + strlcat(all_strbuff, buff, size); + + for (i = 0; i < gesture_coords_reported; i++) { + snprintf(buff, sizeof(buff), "%04X", + gesture_coordinates_x[i]); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%04X", + gesture_coordinates_y[i]); + strlcat(all_strbuff, buff, size); + } + } + + snprintf(buff, sizeof(buff), "%02X", 0xBB); + strlcat(all_strbuff, buff, size); + + count = snprintf(buf, TSP_BUF_SIZE, "%s\n", all_strbuff); + kfree(all_strbuff); + } else { + logError(1, + "%s %s:Unable to allocate all_strbuff! ERROR %08X\n", + tag, ERROR_ALLOC); + } + + return count; +} + +static ssize_t fts_gesture_coordinates_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + return 0; +} +#endif + +/***************** PRODUCTION TEST ****************/ +static ssize_t fts_stm_cmd_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int n; + char *p = (char *) buf; + + memset(typeOfComand, 0, CMD_STR_LEN * sizeof(u32)); + + logError(1, "%s\n", tag); + for (n = 0; n < (count + 1) / 3; n++) { + + if (sscanf(p, "%02X ", &typeOfComand[n]) != 1) + return -EINVAL; + p += 3; + logError(1, "%s typeOfComand[%d] = %02X\n", + tag, n, typeOfComand[n]); + + } + + numberParameters = n; + logError(1, "%s Number of Parameters = %d\n", tag, numberParameters); + return count; +} + +static ssize_t fts_stm_cmd_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + char buff[CMD_STR_LEN] = {0}; + int res, j, doClean = 0, count; + + int size = 6 * 2; + u8 *all_strbuff = NULL; + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + + struct MutualSenseData compData = {0}; + struct SelfSenseData comData = {0}; + struct MutualSenseFrame frameMS = {0}; + struct SelfSenseFrame frameSS = {0}; + + /** + * struct used for defining which test + * perform during the production test + */ + struct TestToDo todoDefault; + + todoDefault.MutualRaw = 1; + todoDefault.MutualRawGap = 1; + todoDefault.MutualCx1 = 0; + todoDefault.MutualCx2 = 1; + todoDefault.MutualCx2Adj = 1; + todoDefault.MutualCxTotal = 0; + todoDefault.MutualCxTotalAdj = 0; + + todoDefault.MutualKeyRaw = 0; + todoDefault.MutualKeyCx1 = 0; + todoDefault.MutualKeyCx2 = 0; + todoDefault.MutualKeyCxTotal = 0; + + todoDefault.SelfForceRaw = 1; + todoDefault.SelfForceRawGap = 0; + todoDefault.SelfForceIx1 = 0; + todoDefault.SelfForceIx2 = 0; + todoDefault.SelfForceIx2Adj = 0; + todoDefault.SelfForceIxTotal = 1; + todoDefault.SelfForceIxTotalAdj = 0; + todoDefault.SelfForceCx1 = 0; + todoDefault.SelfForceCx2 = 0; + todoDefault.SelfForceCx2Adj = 0; + todoDefault.SelfForceCxTotal = 0; + todoDefault.SelfForceCxTotalAdj = 0; + + todoDefault.SelfSenseRaw = 1; + todoDefault.SelfSenseRawGap = 0; + todoDefault.SelfSenseIx1 = 0; + todoDefault.SelfSenseIx2 = 0; + todoDefault.SelfSenseIx2Adj = 0; + todoDefault.SelfSenseIxTotal = 1; + todoDefault.SelfSenseIxTotalAdj = 0; + todoDefault.SelfSenseCx1 = 0; + todoDefault.SelfSenseCx2 = 0; + todoDefault.SelfSenseCx2Adj = 0; + todoDefault.SelfSenseCxTotal = 0; + todoDefault.SelfSenseCxTotalAdj = 0; + + + if (numberParameters >= 1) { + res = fts_disableInterrupt(); + if (res < 0) { + logError(0, "%s fts_disableInterrupt: ERROR %08X\n", + tag, res); + res = (res | ERROR_DISABLE_INTER); + goto END; + } + +#if defined(CONFIG_FB_MSM) + res = fb_unregister_client(&info->notifier); +#else + if (active_panel && info->notifier_cookie) + panel_event_notifier_unregister(info->notifier_cookie); +#endif + if (res < 0) { + logError(1, "%s ERROR: unregister notifier failed!\n", + tag); + goto END; + } + + switch (typeOfComand[0]) { + /*ITO TEST*/ + case 0x01: + res = production_test_ito(); + break; + /*PRODUCTION TEST*/ + case 0x00: + if (ftsInfo.u32_mpPassFlag != INIT_MP) { + logError(0, "%s MP Flag not set!\n", tag, res); + res = production_test_main(LIMITS_FILE, 1, 1, + &todoDefault, INIT_MP); + } else { + logError(0, "%s MP Flag set!\n", tag, res); + res = production_test_main(LIMITS_FILE, 1, 0, + &todoDefault, INIT_MP); + } + break; + /*read mutual raw*/ + case 0x13: + logError(0, "%s Get 1 MS Frame\n", tag); + //res = getMSFrame(ADDR_RAW_TOUCH, &frame, 0); + res = getMSFrame2(MS_TOUCH_ACTIVE, &frameMS); + if (res < 0) { + logError(0, + "%s Error in taking MS frame.%02X\n", + tag, res); + + } else { + logError(0, "%s The frame size is %d words\n", + tag, res); + size = (res * sizeof(short) + 8) * 2; + /* set res to OK because if getMSFrame is*/ + /* successful res = number of words read*/ + res = OK; + print_frame_short("MS frame =", + array1dTo2d_short(frameMS.node_data, + frameMS.node_data_size, + frameMS.header.sense_node), + frameMS.header.force_node, + frameMS.header.sense_node); + } + break; + /*read self raw*/ + case 0x15: + logError(0, "%s Get 1 SS Frame\n", tag); + res = getSSFrame2(SS_TOUCH, &frameSS); + + if (res < OK) { + logError(0, + "%s Error while taking the SS frame%02X\n", + tag, res); + + } else { + logError(0, "%s The frame size is %d words\n", + tag, res); + size = (res * sizeof(short) + 8) * 2 + 1; + /** + * set res to OK because if getMSFrame is + * successful res = number of words read + */ + res = OK; + print_frame_short("SS force frame =", + array1dTo2d_short(frameSS.force_data, + frameSS.header.force_node, 1), + frameSS.header.force_node, 1); + print_frame_short("SS sense frame =", + array1dTo2d_short(frameSS.sense_data, + frameSS.header.sense_node, + frameSS.header.sense_node), + 1, + frameSS.header.sense_node); + } + break; + + /*read mutual comp data*/ + case 0x14: + logError(0, "%s Get MS Compensation Data\n", tag); + res = readMutualSenseCompensationData(MS_TOUCH_ACTIVE, + &compData); + + if (res < 0) { + logError(0, + "%s Error MS compensation data%02X\n", + tag, res); + } else { + logError(0, + "%s MS Data Reading Finished!\n", + tag); + size = ((compData.node_data_size + 9) * + sizeof(u8)) * 2; + print_frame_u8("MS Data (Cx2) =", + array1dTo2d_u8(compData.node_data, + compData.node_data_size, + compData.header.sense_node), + compData.header.force_node, + compData.header.sense_node); + } + break; + + /*read self comp data*/ + case 0x16: + logError(0, "%s Get SS Compensation Data...\n", tag); + res = readSelfSenseCompensationData(SS_TOUCH, &comData); + if (res < 0) { + logError(0, "%s Error reading SS data%02X\n", + tag, res); + } else { + logError(0, "%s SS Data Reading Finished!\n", + tag); + size = ((comData.header.force_node + + comData.header.sense_node) * 2 + 12); + size *= sizeof(u8) * 2; + print_frame_u8("SS Data Ix2_fm = ", + array1dTo2d_u8(comData.ix2_fm, + comData.header.force_node, 1), + comData.header.force_node, + 1); + print_frame_u8("SS Data Cx2_fm = ", + array1dTo2d_u8(comData.cx2_fm, + comData.header.force_node, 1), + comData.header.force_node, + 1); + print_frame_u8("SS Data Ix2_sn = ", + array1dTo2d_u8(comData.ix2_sn, + comData.header.sense_node, + comData.header.sense_node), + 1, + comData.header.sense_node); + print_frame_u8("SS Data Cx2_sn = ", + array1dTo2d_u8(comData.cx2_sn, + comData.header.sense_node, + comData.header.sense_node), + 1, + comData.header.sense_node); + } + break; + + /* MS Raw DATA TEST */ + case 0x03: + res = fts_system_reset(); + if (res >= OK) + res = production_test_ms_raw(LIMITS_FILE, + 1, &todoDefault); + break; + /* MS CX DATA TEST */ + case 0x04: + res = fts_system_reset(); + if (res >= OK) + res = production_test_ms_cx(LIMITS_FILE, + 1, &todoDefault); + break; + /* SS RAW DATA TEST */ + case 0x05: + res = fts_system_reset(); + if (res >= OK) + res = production_test_ss_raw(LIMITS_FILE, + 1, &todoDefault); + break; + /* SS IX CX DATA TEST */ + case 0x06: + res = fts_system_reset(); + if (res >= OK) + res = production_test_ss_ix_cx(LIMITS_FILE, + 1, &todoDefault); + break; + + case 0xF0: + /* TOUCH ENABLE/DISABLE */ + case 0xF1: + doClean = (int) (typeOfComand[0] & 0x01); + res = cleanUp(doClean); + break; + + default: + logError(1, + "%s COMMAND NOT VALID!! Insert a proper value\n", + tag); + res = ERROR_OP_NOT_ALLOW; + break; + } + + doClean = fts_enableInterrupt(); + if (doClean < 0) { + logError(0, "%s fts_enableInterrupt: ERROR %08X\n", + tag, (doClean|ERROR_ENABLE_INTER)); + } + } else { + logError(1, "%s NO COMMAND SPECIFIED!!!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + +#if defined(CONFIG_FB_MSM) + if (fb_register_client(&info->notifier) < 0) + logError(1, "%s ERROR: register notifier failed!\n", tag); +#else + if (active_panel) + st_register_for_panel_events(info->dev->of_node, info); +#endif + +END: + /*here start the reporting phase,*/ + /* assembling the data to send in the file node */ + all_strbuff = kmalloc(size, GFP_KERNEL); + if (!all_strbuff) + return 0; + memset(all_strbuff, 0, size); + + snprintf(buff, sizeof(buff), "%02X", 0xAA); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%08X", res); + strlcat(all_strbuff, buff, size); + + if (res >= OK) { + /*all the other cases are already fine printing only the res.*/ + switch (typeOfComand[0]) { + case 0x13: + snprintf(buff, sizeof(buff), "%02X", + (u8) frameMS.header.force_node); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", + (u8) frameMS.header.sense_node); + strlcat(all_strbuff, buff, size); + + for (j = 0; j < frameMS.node_data_size; j++) { + snprintf(buff, sizeof(buff), "%04X", + frameMS.node_data[j]); + strlcat(all_strbuff, buff, size); + } + + kfree(frameMS.node_data); + break; + + case 0x15: + snprintf(buff, sizeof(buff), "%02X", + (u8) frameSS.header.force_node); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", + (u8) frameSS.header.sense_node); + strlcat(all_strbuff, buff, size); + + /* Copying self raw data Force */ + for (j = 0; j < frameSS.header.force_node; j++) { + snprintf(buff, sizeof(buff), "%04X", + frameSS.force_data[j]); + strlcat(all_strbuff, buff, size); + } + + /* Copying self raw data Sense */ + for (j = 0; j < frameSS.header.sense_node; j++) { + snprintf(buff, sizeof(buff), "%04X", + frameSS.sense_data[j]); + strlcat(all_strbuff, buff, size); + } + + kfree(frameSS.force_data); + kfree(frameSS.sense_data); + break; + + case 0x14: + snprintf(buff, sizeof(buff), "%02X", + (u8) compData.header.force_node); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", + (u8) compData.header.sense_node); + strlcat(all_strbuff, buff, size); + + /* Cpying CX1 value */ + snprintf(buff, sizeof(buff), "%02X", compData.cx1); + strlcat(all_strbuff, buff, size); + + /* Copying CX2 values */ + for (j = 0; j < compData.node_data_size; j++) { + snprintf(buff, sizeof(buff), "%02X", + *(compData.node_data + j)); + strlcat(all_strbuff, buff, size); + } + + kfree(compData.node_data); + break; + + case 0x16: + snprintf(buff, sizeof(buff), "%02X", + comData.header.force_node); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", + comData.header.sense_node); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", comData.f_ix1); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", comData.s_ix1); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", comData.f_cx1); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", comData.s_cx1); + strlcat(all_strbuff, buff, size); + + /* Copying IX2 Force */ + for (j = 0; j < comData.header.force_node; j++) { + snprintf(buff, sizeof(buff), "%02X", + comData.ix2_fm[j]); + strlcat(all_strbuff, buff, size); + } + + /* Copying IX2 Sense */ + for (j = 0; j < comData.header.sense_node; j++) { + snprintf(buff, sizeof(buff), "%02X", + comData.ix2_sn[j]); + strlcat(all_strbuff, buff, size); + } + + /* Copying CX2 Force */ + for (j = 0; j < comData.header.force_node; j++) { + snprintf(buff, sizeof(buff), "%02X", + comData.cx2_fm[j]); + strlcat(all_strbuff, buff, size); + } + + /* Copying CX2 Sense */ + for (j = 0; j < comData.header.sense_node; j++) { + snprintf(buff, sizeof(buff), "%02X", + comData.cx2_sn[j]); + strlcat(all_strbuff, buff, size); + } + + kfree(comData.ix2_fm); + kfree(comData.ix2_sn); + kfree(comData.cx2_fm); + kfree(comData.cx2_sn); + break; + + default: + break; + } + } + + snprintf(buff, sizeof(buff), "%02X", 0xBB); + strlcat(all_strbuff, buff, size); + + count = snprintf(buf, TSP_BUF_SIZE, "%s\n", all_strbuff); + /** + * need to reset the number of parameters + * in order to wait the next command, + * comment if you want to repeat + * the last command sent just doing a cat + */ + numberParameters = 0; + /* logError(0,"%s numberParameters = %d\n",tag, numberParameters);*/ + kfree(all_strbuff); + + return count; +} + +static DEVICE_ATTR_RW(fts_fwupdate); +static DEVICE_ATTR_RO(fts_appid); +static DEVICE_ATTR_RO(fts_mode_active); +static DEVICE_ATTR_RO(fts_lockdown_info); +static DEVICE_ATTR_RW(fts_strength_frame); +static DEVICE_ATTR_RO(fts_fw_test); +static DEVICE_ATTR_RW(fts_stm_cmd); +#ifdef USE_ONE_FILE_NODE +static DEVICE_ATTR_RW(fts_feature_enable); +#else + +#ifdef EDGE_REJ +static DEVICE_ATTR_RW(fts_edge_rej); +#endif + +#ifdef CORNER_REJ +static DEVICE_ATTR_RW(fts_corner_rej); +#endif + +#ifdef EDGE_PALM_REJ +static DEVICE_ATTR_RW(fts_edge_palm_rej); +#endif + +#ifdef CHARGER_MODE +static DEVICE_ATTR_RW(fts_charger_mode); +#endif + +#ifdef GLOVE_MODE +static DEVICE_ATTR_RW(fts_glove_mode); +#endif + +#ifdef VR_MODE +static DEVICE_ATTR_RW(fts_vr_mode); +#endif + +#ifdef COVER_MODE +static DEVICE_ATTR_RW(fts_cover_mode); +#endif + +#ifdef STYLUS_MODE +static DEVICE_ATTR_RW(fts_stylus_mode); +#endif + +#endif + +#ifdef PHONE_GESTURE +static DEVICE_ATTR_RW(fts_gesture_mask); +static DEVICE_ATTR_RW(fts_gesture_coordinates); +#ifdef USE_CUSTOM_GESTURES +static DEVICE_ATTR_RW(fts_add_custom_gesture); +static DEVICE_ATTR_RW(fts_remove_custom_gesture); +#endif +#endif + +#ifdef CONFIG_ST_TRUSTED_TOUCH +static DEVICE_ATTR(trusted_touch_enable, + 0664, + fts_trusted_touch_enable_show, + fts_trusted_touch_enable_store); +#endif + +/* /sys/devices/soc.0/f9928000.i2c/i2c-6/6-0049 */ +static struct attribute *fts_attr_group[] = { + &dev_attr_fts_fwupdate.attr, + &dev_attr_fts_appid.attr, + &dev_attr_fts_mode_active.attr, + &dev_attr_fts_lockdown_info.attr, + &dev_attr_fts_strength_frame.attr, + &dev_attr_fts_fw_test.attr, + &dev_attr_fts_stm_cmd.attr, +#ifdef USE_ONE_FILE_NODE + &dev_attr_fts_feature_enable.attr, +#else + +#ifdef EDGE_REJ + &dev_attr_fts_edge_rej.attr, +#endif +#ifdef CORNER_REJ + &dev_attr_fts_corner_rej.attr, +#endif +#ifdef EDGE_PALM_REJ + &dev_attr_fts_edge_palm_rej.attr, +#endif +#ifdef CHARGER_MODE + &dev_attr_fts_charger_mode.attr, +#endif +#ifdef GLOVE_MODE + &dev_attr_fts_glove_mode.attr, +#endif +#ifdef VR_MODE + &dev_attr_fts_vr_mode.attr, +#endif +#ifdef COVER_MODE + &dev_attr_fts_cover_mode.attr, +#endif +#ifdef STYLUS_MODE + &dev_attr_fts_stylus_mode.attr, +#endif + +#endif + +#ifdef PHONE_GESTURE + &dev_attr_fts_gesture_mask.attr, + &dev_attr_fts_gesture_coordinates.attr, +#ifdef USE_CUSTOM_GESTURES + &dev_attr_fts_add_custom_gesture.attr, + &dev_attr_fts_remove_custom_gesture.attr, +#endif + +#endif +#ifdef CONFIG_ST_TRUSTED_TOUCH + &dev_attr_trusted_touch_enable.attr, +#endif + NULL, +}; + +static int fts_command(struct fts_ts_info *info, unsigned char cmd) +{ + unsigned char regAdd; + int ret; + + regAdd = cmd; + + ret = fts_writeCmd(®Add, sizeof(regAdd)); /* 0 = ok */ + + logError(0, "%s Issued command 0x%02x, return value %08X\n", cmd, ret); + + return ret; +} + +void fts_input_report_key(struct fts_ts_info *info, int key_code) +{ + mutex_lock(&info->input_report_mutex); + input_report_key(info->input_dev, key_code, 1); + input_sync(info->input_dev); + input_report_key(info->input_dev, key_code, 0); + input_sync(info->input_dev); + mutex_unlock(&info->input_report_mutex); +} + +/* + * New Interrupt handle implementation + */ +static inline unsigned char *fts_next_event(unsigned char *evt) +{ + /* Nothing to do with this event, moving to the next one */ + evt += FIFO_EVENT_SIZE; + + /* the previous one was the last event ? */ + return (evt[-1] & 0x1F) ? evt : NULL; +} + +/* EventId : 0x00 */ +static void fts_nop_event_handler(struct fts_ts_info *info, + unsigned char *event) +{ + /** + * logError(1, + * "%s %s Doing nothing for event = + * %02X %02X %02X %02X %02X %02X %02X %02X\n", + * tag, __func__, event[0], event[1], event[2], + * event[3], event[4], event[5], event[6], event[7]); + */ + /* return fts_next_event(event); */ +} + +/* EventId : 0x03 */ +static void fts_enter_pointer_event_handler(struct fts_ts_info *info, + unsigned char *event) +{ + unsigned char touchId, touchcount; + int x, y; + int minor; + int major, distance = 0; + u8 touchsize; + + if (!info->resume_bit && !info->aoi_notify_enabled) + return; + + touchId = event[1] & 0x0F; + touchcount = (event[1] & 0xF0) >> 4; + touchsize = (event[5] & 0xC0) >> 6; + major = (event[5] & 0x1F); // bit0-bit4: major + minor = event[6]; // event6:minor + + __set_bit(touchId, &info->touch_id); + + x = (event[2] << 4) | (event[4] & 0xF0) >> 4; + y = (event[3] << 4) | (event[4] & 0x0F); + + if (info->bdata->x_flip) + x = X_AXIS_MAX - x; + if (info->bdata->y_flip) + y = Y_AXIS_MAX - y; + + if (x == X_AXIS_MAX) + x--; + + if (y == Y_AXIS_MAX) + y--; + + if (info->sensor_sleep && info->aoi_notify_enabled) + if ((x < info->aoi_left || x > info->aoi_right) + || (y < info->aoi_top || y > info->aoi_bottom)) { + x = -x; + y = -y; + } + + input_mt_slot(info->input_dev, touchId); + input_mt_report_slot_state(info->input_dev, MT_TOOL_FINGER, 1); + + if (touchcount == 1) { + input_report_key(info->input_dev, BTN_TOUCH, 1); + input_report_key(info->input_dev, BTN_TOOL_FINGER, 1); + } + input_report_abs(info->input_dev, ABS_MT_POSITION_X, x); + input_report_abs(info->input_dev, ABS_MT_POSITION_Y, y); + input_report_abs(info->input_dev, ABS_MT_TOUCH_MAJOR, major); + input_report_abs(info->input_dev, ABS_MT_TOUCH_MINOR, minor); + input_report_abs(info->input_dev, ABS_MT_DISTANCE, distance); + + return; +} + +/* EventId : 0x04 */ +static void fts_leave_pointer_event_handler(struct fts_ts_info *info, + unsigned char *event) +{ + unsigned char touchId, touchcount; + u8 touchsize; + + touchId = event[1] & 0x0F; + touchcount = (event[1] & 0xF0) >> 4; + touchsize = (event[5] & 0xC0) >> 6; + + input_mt_slot(info->input_dev, touchId); + + __clear_bit(touchId, &info->touch_id); + input_mt_report_slot_state(info->input_dev, MT_TOOL_FINGER, 0); + + if (touchcount == 0) { + input_report_key(info->input_dev, BTN_TOUCH, 0); + input_report_key(info->input_dev, BTN_TOOL_FINGER, 0); + } + + input_report_abs(info->input_dev, ABS_MT_TRACKING_ID, -1); + +} + +/* EventId : 0x05 */ +#define fts_motion_pointer_event_handler fts_enter_pointer_event_handler + +#ifdef PHONE_KEY +/* EventId : 0x0E */ +static void fts_key_status_event_handler(struct fts_ts_info *info, + unsigned char *event) +{ + int value; + + logError(0, + "%s %sReceived event %02X %02X %02X %02X %02X %02X %02X %02X\n", + tag, __func__, event[0], event[1], event[2], event[3], + event[4], event[5], event[6], event[7]); + /* + * TODO: the customer should handle the events coming + * from the keys according his needs (this is an example + * that report only the single pressure of one key at time) + */ + /* event[2] contain the bitmask of the keys that are actually pressed */ + if (event[2] != 0) { + switch (event[2]) { + case KEY1: + value = KEY_HOMEPAGE; + logError(0, "%s %s: Button HOME!\n", tag, __func__); + break; + + case KEY2: + value = KEY_BACK; + logError(0, "%s %s: Button Back !\n", tag, __func__); + break; + + case KEY3: + value = KEY_MENU; + logError(0, "%s %s: Button Menu !\n", tag, __func__); + break; + + default: + logError(0, + "%s %s:No valid Button ID or more than one key pressed!\n", + tag, __func__); + return; + } + + fts_input_report_key(info, value); + } else { + logError(0, "%s %s: All buttons released!\n", tag, __func__); + } +} +#endif + +/* EventId : 0x0F */ +static void fts_error_event_handler(struct fts_ts_info *info, + unsigned char *event) +{ + int error = 0; + + logError(0, + "%s %sReceived event:%02X %02X %02X %02X %02X %02X %02X %02X\n", + tag, __func__, event[0], event[1], event[2], event[3], + event[4], event[5], event[6], event[7]); + + switch (event[1]) { + case EVENT_TYPE_ESD_ERROR: /* esd */ + /* before reset clear all slot */ + release_all_touches(info); + + fts_chip_powercycle(info); + + error = fts_system_reset(); + error |= fts_mode_handler(info, 0); + error |= fts_enableInterrupt(); + if (error < OK) { + logError(1, + "%s %s Cannot restore the device ERROR %08X\n", + tag, __func__, error); + } + break; + case EVENT_TYPE_WATCHDOG_ERROR: /* watch dog timer */ + /* if (event[2] == 0) { */ + dumpErrorInfo(); + /* before reset clear all slot */ + release_all_touches(info); + error = fts_system_reset(); + error |= fts_mode_handler(info, 0); + error |= fts_enableInterrupt(); + if (error < OK) { + logError(1, + "%s %s Cannot reset the device ERROR %08X\n", + tag, __func__, error); + } + /* } */ + break; +} + /* return fts_next_event(event); */ +} + +/* EventId : 0x10 */ +static void fts_controller_ready_event_handler(struct fts_ts_info *info, + unsigned char *event) +{ + int error; + + logError(0, "%s %s Received event 0x%02x\n", tag, __func__, event[0]); + release_all_touches(info); + setSystemResettedUp(1); + setSystemResettedDown(1); + error = fts_mode_handler(info, 0); + if (error < OK) { + logError(1, + "%s %s Cannot restore the device status ERROR %08X\n", + tag, __func__, error); + } + /* return fts_next_event(event); */ +} + +/* EventId : 0x16 */ +static void fts_status_event_handler(struct fts_ts_info *info, + unsigned char *event) +{ + /* logError(1, "%s Received event 0x%02x\n", tag, event[0]); */ + + switch (event[1]) { + case EVENT_TYPE_MS_TUNING_CMPL: + case EVENT_TYPE_SS_TUNING_CMPL: + case FTS_FORCE_CAL_SELF_MUTUAL: + case FTS_FLASH_WRITE_CONFIG: + case FTS_FLASH_WRITE_COMP_MEMORY: + case FTS_FORCE_CAL_SELF: + case FTS_WATER_MODE_ON: + case FTS_WATER_MODE_OFF: + default: + logError(0, "%s %s Received unhandled status event = ", + tag, __func__); + logError(0, "%02X %02X %02X %02X %02X %02X %02X %02X\n", + event[0], event[1], event[2], event[3], event[4], + event[5], event[6], event[7]); + break; + } + + /* return fts_next_event(event); */ +} + +#ifdef PHONE_GESTURE +/** + * TODO: Customer should implement their own action + * in respons of a gesture event. + * This is an example that simply print the gesture received + */ +static void fts_gesture_event_handler(struct fts_ts_info *info, + unsigned char *event) +{ + unsigned char touchId; + int value; + int needCoords = 0; + + logError(0, + "%s gesture event: %02X %02X %02X %02X %02X %02X %02X %02X\n", + tag, event[0], event[1], event[2], event[3], + event[4], event[5], event[6], event[7]); + + if (event[1] == 0x03) { + logError(1, "%s %s: Gesture ID %02X enable_status = %02X\n", + tag, __func__, event[2], event[3]); + } + + if (event[1] == EVENT_TYPE_ENB && event[2] == 0x00) { + switch (event[3]) { + case GESTURE_ENABLE: + logError(1, "%s %s: Gesture Enabled! res = %02X\n", + tag, __func__, event[4]); + break; + + case GESTURE_DISABLE: + logError(1, "%s %s: Gesture Disabled! res = %02X\n", + tag, __func__, event[4]); + break; + + default: + logError(1, "%s %s: Event not Valid!\n", tag, __func__); + } + } + + if (event[0] == EVENTID_GESTURE && (event[1] == EVENT_TYPE_GESTURE_DTC1 + || event[1] == EVENT_TYPE_GESTURE_DTC2)) { + /* always use touchId zero */ + touchId = 0; + __set_bit(touchId, &info->touch_id); + + /* by default read the coordinates*/ + /* for all gestures excluding double tap */ + needCoords = 1; + + switch (event[2]) { + case GES_ID_DBLTAP: + value = KEY_WAKEUP; + logError(0, "%s %s: double tap!\n", tag, __func__); + needCoords = 0; + break; + + case GES_ID_AT: + value = KEY_WWW; + logError(0, "%s %s: @!\n", tag, __func__); + break; + + case GES_ID_C: + value = KEY_C; + logError(0, "%s %s: C !\n", tag, __func__); + break; + + case GES_ID_E: + value = KEY_E; + logError(0, "%s %s: e !\n", tag, __func__); + break; + + case GES_ID_F: + value = KEY_F; + logError(0, "%s %s: F !\n", tag, __func__); + break; + + case GES_ID_L: + value = KEY_L; + logError(0, "%s %s: L !\n", tag, __func__); + break; + + case GES_ID_M: + value = KEY_M; + logError(0, "%s %s: M !\n", tag, __func__); + break; + + case GES_ID_O: + value = KEY_O; + logError(0, "%s %s: O !\n", tag, __func__); + break; + + case GES_ID_S: + value = KEY_S; + logError(0, "%s %s: S !\n", tag, __func__); + break; + + case GES_ID_V: + value = KEY_V; + logError(0, "%s %s: V !\n", tag, __func__); + break; + + case GES_ID_W: + value = KEY_W; + logError(0, "%s %s: W !\n", tag, __func__); + break; + + case GES_ID_Z: + value = KEY_Z; + logError(0, "%s %s: Z !\n", tag, __func__); + break; + + case GES_ID_HFLIP_L2R: + value = KEY_RIGHT; + logError(0, "%s %s: -> !\n", tag, __func__); + break; + + case GES_ID_HFLIP_R2L: + value = KEY_LEFT; + logError(0, "%s %s: <- !\n", tag, __func__); + break; + + case GES_ID_VFLIP_D2T: + value = KEY_UP; + logError(0, "%s %s: UP !\n", tag, __func__); + break; + + case GES_ID_VFLIP_T2D: + value = KEY_DOWN; + logError(0, "%s %s: DOWN !\n", tag, __func__); + break; + + case GES_ID_CUST1: + value = KEY_F1; + logError(0, "%s %s: F1 !\n", tag, __func__); + break; + + case GES_ID_CUST2: + value = KEY_F1; + logError(0, "%s %s: F2 !\n", tag, __func__); + break; + + case GES_ID_CUST3: + value = KEY_F3; + logError(0, "%s %s: F3 !\n", tag, __func__); + break; + + case GES_ID_CUST4: + value = KEY_F1; + logError(0, "%s %s: F4 !\n", tag, __func__); + break; + + case GES_ID_CUST5: + value = KEY_F1; + logError(0, "%s %s: F5 !\n", tag, __func__); + break; + + case GES_ID_LEFTBRACE: + value = KEY_LEFTBRACE; + logError(0, "%s %s: < !\n", tag, __func__); + break; + + case GES_ID_RIGHTBRACE: + value = KEY_RIGHTBRACE; + logError(0, "%s %s: > !\n", tag, __func__); + break; + default: + logError(0, "%s %s: No valid GestureID!\n", + tag, __func__); + goto gesture_done; + } + + /* no coordinates for gestures reported by FW */ + if (event[1] == EVENT_TYPE_GESTURE_DTC1) + needCoords = 0; + + if (needCoords == 1) + readGestureCoords(event); + + fts_input_report_key(info, value); + +gesture_done: + /* Done with gesture event, clear bit. */ + __clear_bit(touchId, &info->touch_id); + } + /* return fts_next_event(event); */ +} +#endif + +/* EventId : 0x05 */ +#define fts_motion_pointer_event_handler fts_enter_pointer_event_handler + +/* + * This handler is called each time there is at least + * one new event in the FIFO + */ +static void fts_event_handler(struct work_struct *work) +{ + struct fts_ts_info *info; + int error = 0, count = 0; + unsigned char regAdd; + unsigned char data[FIFO_EVENT_SIZE] = {0}; + unsigned char eventId; + + struct event_dispatch_handler_t event_handler; + + info = container_of(work, struct fts_ts_info, work); + /* + * read all the FIFO and parsing events + */ + + __pm_wakeup_event(info->wakeup_source, HZ); + regAdd = FIFO_CMD_READONE; + + for (count = 0; count < FIFO_DEPTH; count++) { + error = fts_readCmd(®Add, sizeof(regAdd), data, + FIFO_EVENT_SIZE); + if (error == OK && data[0] != EVENTID_NO_EVENT) + eventId = data[0]; + else + break; + + if (eventId < EVENTID_LAST) { + event_handler = info->event_dispatch_table[eventId]; + event_handler.handler(info, (data)); + } + } + input_sync(info->input_dev); + + fts_interrupt_enable(info); +} + +static void fts_fw_update_auto(struct work_struct *work) +{ + u8 cmd[4] = { FTS_CMD_HW_REG_W, 0x00, 0x00, SYSTEM_RESET_VALUE }; + int event_to_search[2] = {(int)EVENTID_ERROR_EVENT, + (int)EVENT_TYPE_CHECKSUM_ERROR}; + u8 readData[FIFO_EVENT_SIZE] = {0}; + int flag_init = 0; + int retval = 0; + int retval1 = 0; + int ret; + struct fts_ts_info *info; + struct delayed_work *fwu_work = container_of(work, + struct delayed_work, work); + int crc_status = 0; + int error = 0; + struct Firmware fwD; + int orig_size; + u8 *orig_data; + + info = container_of(fwu_work, struct fts_ts_info, fwu_work); + logError(0, "%s Fw Auto Update is starting...\n", tag); + + ret = getFWdata(PATH_FILE_FW, &orig_data, &orig_size, 0); + if (ret < OK) { + logError(0, "%s %s: impossible retrieve FW... ERROR %08X\n", + tag, __func__, ERROR_MEMH_READ); + ret = (ret | ERROR_MEMH_READ); + goto NO_FIRMWARE_UPDATE; + } + + ret = parseBinFile(orig_data, orig_size, &fwD, 1); + if (ret < OK) { + logError(1, "%s %s: impossible parse ERROR %08X\n", + tag, __func__, ERROR_MEMH_READ); + ret = (ret | ERROR_MEMH_READ); + kfree(fwD.data); + goto NO_FIRMWARE_UPDATE; + } + + fts_chip_powercycle(info); + retval = flash_burn(&fwD, crc_status, 1); + + if ((retval & 0xFF000000) == ERROR_FLASH_PROCEDURE) { + logError(1, "%s %s:firmware update retry! ERROR %08X\n", + tag, __func__, retval); + fts_chip_powercycle(info); + + retval1 = flash_burn(&fwD, crc_status, 1); + + if ((retval1 & 0xFF000000) == ERROR_FLASH_PROCEDURE) { + logError(1, "%s %s: update failed again! ERROR %08X\n", + tag, __func__, retval1); + logError(1, "%s Fw Auto Update Failed!\n", tag); + } + } + + kfree(fwD.data); + u16ToU8_be(SYSTEM_RESET_ADDRESS, &cmd[1]); + ret = fts_writeCmd(cmd, 4); + if (ret < OK) { + logError(1, "%s %s Can't send reset command! ERROR %08X\n", + tag, __func__, ret); + } else { + setSystemResettedDown(1); + setSystemResettedUp(1); + ret = pollForEvent(event_to_search, 2, readData, + GENERAL_TIMEOUT); + if (ret < OK) { + logError(0, "%s %s: No CX CRC Found!\n", tag, __func__); + } else { + if (readData[2] == CRC_CX_MEMORY) { + logError(1, "%s %s: CRC Error! ERROR:%02X\n\n", + tag, __func__, readData[2]); + + flag_init = 1; + } + } + } + + if (ftsInfo.u8_msScrConfigTuneVer != ftsInfo.u8_msScrCxmemTuneVer || + ftsInfo.u8_ssTchConfigTuneVer != ftsInfo.u8_ssTchCxmemTuneVer) + ret = ERROR_GET_INIT_STATUS; + else if (((ftsInfo.u32_mpPassFlag != INIT_MP) + && (ftsInfo.u32_mpPassFlag != INIT_FIELD)) || flag_init == 1) + ret = ERROR_GET_INIT_STATUS; + else + ret = OK; + + if (ret == ERROR_GET_INIT_STATUS) { + error = fts_chip_initialization(info); + if (error < OK) + logError(1, "%s %s Can't initialize chip! ERROR %08X", + tag, __func__, error); + } + +NO_FIRMWARE_UPDATE: + error = fts_init_afterProbe(info); + if (error < OK) + logError(1, "%s Can't initialize hardware device ERROR %08X\n", + tag, error); + + logError(0, "%s Fw Auto Update Finished!\n", tag); +} + +static int fts_chip_initialization(struct fts_ts_info *info) +{ + int ret2 = 0; + int retry; + int initretrycnt = 0; + struct TestToDo todoDefault; + + todoDefault.MutualRaw = 1; + todoDefault.MutualRawGap = 1; + todoDefault.MutualCx1 = 0; + todoDefault.MutualCx2 = 0; + todoDefault.MutualCx2Adj = 0; + todoDefault.MutualCxTotal = 0; + todoDefault.MutualCxTotalAdj = 0; + + todoDefault.MutualKeyRaw = 0; + todoDefault.MutualKeyCx1 = 0; + todoDefault.MutualKeyCx2 = 0; + todoDefault.MutualKeyCxTotal = 0; + + todoDefault.SelfForceRaw = 0; + todoDefault.SelfForceRawGap = 0; + todoDefault.SelfForceIx1 = 0; + todoDefault.SelfForceIx2 = 0; + todoDefault.SelfForceIx2Adj = 0; + todoDefault.SelfForceIxTotal = 0; + todoDefault.SelfForceIxTotalAdj = 0; + todoDefault.SelfForceCx1 = 0; + todoDefault.SelfForceCx2 = 0; + todoDefault.SelfForceCx2Adj = 0; + todoDefault.SelfForceCxTotal = 0; + todoDefault.SelfForceCxTotalAdj = 0; + + todoDefault.SelfSenseRaw = 1; + todoDefault.SelfSenseRawGap = 0; + todoDefault.SelfSenseIx1 = 0; + todoDefault.SelfSenseIx2 = 0; + todoDefault.SelfSenseIx2Adj = 0; + todoDefault.SelfSenseIxTotal = 0; + todoDefault.SelfSenseIxTotalAdj = 0; + todoDefault.SelfSenseCx1 = 0; + todoDefault.SelfSenseCx2 = 0; + todoDefault.SelfSenseCx2Adj = 0; + todoDefault.SelfSenseCxTotal = 0; + todoDefault.SelfSenseCxTotalAdj = 0; + + for (retry = 0; retry <= INIT_FLAG_CNT; retry++) { + ret2 = production_test_main(LIMITS_FILE, 1, 1, &todoDefault, + INIT_FIELD); + if (ret2 == OK) + break; + initretrycnt++; + logError(1, "%s %s: cycle count = %04d - ERROR %08X\n", + tag, __func__, initretrycnt, ret2); + fts_chip_powercycle(info); + } + + if (ret2 < OK) + logError(1, "%s failed to initializate 3 times\n", tag); + + return ret2; +} + +#ifdef FTS_USE_POLLING_MODE + +static enum hrtimer_restart fts_timer_func(struct hrtimer *timer) +{ + struct fts_ts_info *info = + container_of(timer, struct fts_ts_info, timer); + + queue_work(info->event_wq, &info->work); + return HRTIMER_NORESTART; +} +#else + +static irqreturn_t fts_interrupt_handler(int irq, void *handle) +{ + struct fts_ts_info *info = handle; + + if (!info) { + pr_err("%s: Invalid info\n", __func__); + return IRQ_HANDLED; + } +#ifdef CONFIG_ST_TRUSTED_TOUCH +#ifndef CONFIG_ARCH_QTI_VM + if (atomic_read(&info->vm_info->pvm_owns_iomem) && + atomic_read(&info->vm_info->pvm_owns_irq) && + atomic_read(&info->trusted_touch_enabled)) { + pr_err("%s: Cannot service interrupts in PVM while trusted touch is enabled\n", + __func__); + return IRQ_HANDLED; + } +#endif +#endif + disable_irq_nosync(info->client->irq); + + queue_work(info->event_wq, &info->work); + + return IRQ_HANDLED; +} +#endif + +static int fts_interrupt_install(struct fts_ts_info *info) +{ + int i, error = 0; + size_t len; + + len = sizeof(struct event_dispatch_handler_t) * EVENTID_LAST; + info->event_dispatch_table = kzalloc(len, GFP_KERNEL); + + if (!info->event_dispatch_table) { + logError(1, "%s OOM allocating event dispatch table\n", tag); + return -ENOMEM; + } + + for (i = 0; i < EVENTID_LAST; i++) + info->event_dispatch_table[i].handler = fts_nop_event_handler; + install_handler(info, ENTER_POINTER, enter_pointer); + install_handler(info, LEAVE_POINTER, leave_pointer); + install_handler(info, MOTION_POINTER, motion_pointer); + install_handler(info, ERROR_EVENT, error); + install_handler(info, CONTROL_READY, controller_ready); + install_handler(info, STATUS_UPDATE, status); +#ifdef PHONE_GESTURE + install_handler(info, GESTURE, gesture); +#endif +#ifdef PHONE_KEY + install_handler(info, KEY_STATUS, key_status); +#endif + /* disable interrupts in any case */ + error = fts_disableInterrupt(); + +#ifdef FTS_USE_POLLING_MODE + logError(0, "%s Polling Mode\n"); + hrtimer_init(&info->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + info->timer.function = fts_timer_func; + hrtimer_start(&info->timer, ktime_set(1, 0), HRTIMER_MODE_REL); +#else +#ifdef CONFIG_ARCH_QTI_VM + logError(0, "%s Interrupt Mode\n", tag); + if (request_threaded_irq(info->client->irq, NULL, fts_interrupt_handler, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, info->client->name, info)) { + logError(1, "%s Request irq failed\n", tag); + kfree(info->event_dispatch_table); + error = -EBUSY; + } +#else + logError(0, "%s Interrupt Mode\n", tag); + if (request_threaded_irq(info->client->irq, NULL, fts_interrupt_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, info->client->name, info)) { + logError(1, "%s Request irq failed\n", tag); + kfree(info->event_dispatch_table); + error = -EBUSY; + } +#endif +#endif + return error; +} + +static void fts_interrupt_uninstall(struct fts_ts_info *info) +{ + fts_disableInterrupt(); + + kfree(info->event_dispatch_table); +#ifdef FTS_USE_POLLING_MODE + hrtimer_cancel(&info->timer); +#else + free_irq(info->client->irq, info); +#endif +} + +static void fts_interrupt_enable(struct fts_ts_info *info) +{ +#ifdef FTS_USE_POLLING_MODE + hrtimer_start(&info->timer, ktime_set(0, 10000000), HRTIMER_MODE_REL); +#else + enable_irq(info->client->irq); +#endif + /* enable the touch IC irq */ + fts_enableInterrupt(); +} + +static void fts_interrupt_disable(struct fts_ts_info *info) +{ + /* disable the touch IC irq */ + fts_disableInterrupt(); + +#ifdef FTS_USE_POLLING_MODE + hrtimer_cancel(&info->timer); +#else + disable_irq(info->client->irq); +#endif + +} + +static int fts_init(struct fts_ts_info *info) +{ + int error; + + error = fts_system_reset(); + if (error < OK && error != (ERROR_TIMEOUT | ERROR_SYSTEM_RESET_FAIL)) { + logError(1, "%s Cannot reset the device! ERROR %08X\n", + tag, error); + return error; + } + if (error == (ERROR_TIMEOUT | ERROR_SYSTEM_RESET_FAIL)) { + logError(1, "%s Setting default Chip INFO!\n", tag); + defaultChipInfo(0); + } else { + error = readChipInfo(0); + if (error < OK) { + logError(1, "%s Cannot read Chip Info!ERROR:%08X\n", + tag, error); + } + } + + error = fts_interrupt_install(info); + + if (error != OK) + logError(1, "%s Init (1) error (ERROR = %08X)\n", tag, error); + + return error; +} + +int fts_chip_powercycle(struct fts_ts_info *info) +{ + int error = 0; + + logError(0, "%s %s: Power Cycle Starting...\n", tag, __func__); + + /* + * if IRQ pin is short with DVDD a call to + * the ISR will triggered when the regulator is turned off + */ + + logError(0, "%s %s: Disabling IRQ...\n", tag, __func__); + disable_irq_nosync(info->client->irq); + if (info->pwr_reg) { + error = regulator_disable(info->pwr_reg); + if (error < 0) { + logError(1, "%s %s: Failed to disable DVDD regulator\n", + tag, __func__); + } + } + + if (info->bus_reg) { + error = regulator_disable(info->bus_reg); + if (error < 0) { + logError(1, "%s %s: Failed to disable AVDD regulator\n", + tag, __func__); + } + } + + if (info->bdata->reset_gpio != GPIO_NOT_DEFINED) + gpio_set_value(info->bdata->reset_gpio, 0); + else + msleep(300); + + if (info->pwr_reg) { + error = regulator_enable(info->bus_reg); + if (error < 0) { + logError(1, "%s %s: Failed to enable AVDD regulator\n", + tag, __func__); + } + } + + if (info->bus_reg) { + error = regulator_enable(info->pwr_reg); + if (error < 0) { + logError(1, "%s %s: Failed to enable DVDD regulator\n", + tag, __func__); + } + } + /* time needed by the regulators for reaching the regime values */ + msleep(20); + + if (info->bdata->reset_gpio != GPIO_NOT_DEFINED) { + /* time to wait before bring up the reset */ + /* gpio after the power up of the regulators */ + msleep(20); + gpio_set_value(info->bdata->reset_gpio, 1); + /* mdelay(300); */ + } + + release_all_touches(info); + + logError(0, "%s %s: Enabling IRQ...\n", tag, __func__); + enable_irq(info->client->irq); + logError(0, "%s %s: Power Cycle Finished! ERROR CODE = %08x\n", + tag, __func__, error); + setSystemResettedUp(1); + setSystemResettedDown(1); + return error; +} + +int fts_chip_powercycle2(struct fts_ts_info *info, unsigned long sleep) +{ + int error = 0; + + logError(0, "%s %s: Power Cycle Starting...\n", tag, __func__); + + if (info->pwr_reg) { + error = regulator_disable(info->pwr_reg); + if (error < 0) { + logError(1, "%s %s: Failed to disable DVDD regulator\n", + tag, __func__); + } + } + + if (info->bus_reg) { + error = regulator_disable(info->bus_reg); + if (error < 0) { + logError(1, "%s %s: Failed to disable AVDD regulator\n", + tag, __func__); + } + } + + if (info->bdata->reset_gpio != GPIO_NOT_DEFINED) + gpio_set_value(info->bdata->reset_gpio, 0); + + msleep(sleep); + if (info->pwr_reg) { + error = regulator_enable(info->bus_reg); + if (error < 0) { + logError(1, "%s %s: Failed to enable AVDD regulator\n", + tag, __func__); + } + } + + if (info->bus_reg) { + error = regulator_enable(info->pwr_reg); + if (error < 0) { + logError(1, "%s %s: Failed to enable DVDD regulator\n", + tag, __func__); + } + } + /* time needed by the regulators for reaching the regime values */ + msleep(500); + + if (info->bdata->reset_gpio != GPIO_NOT_DEFINED) { + /* + * time to wait before bring up the reset + * gpio after the power up of the regulators + */ + msleep(20); + gpio_set_value(info->bdata->reset_gpio, 1); + /* msleep(300); */ + } + + /* before reset clear all slot */ + release_all_touches(info); + + logError(0, "%s %s: Power Cycle Finished! ERROR CODE = %08x\n", + tag, __func__, error); + setSystemResettedUp(1); + setSystemResettedDown(1); + return error; +} + +static int fts_init_afterProbe(struct fts_ts_info *info) +{ + int error = 0; + + /* system reset */ + error = cleanUp(0); + + /* enable the features and the sensing */ + error |= fts_mode_handler(info, 0); + + /* enable the interrupt */ + error |= fts_enableInterrupt(); + +#if defined(CONFIG_FB_MSM) + error |= fb_register_client(&info->notifier); +#else + if (active_panel) + st_register_for_panel_events(info->dev->of_node, info); +#endif + + if (error < OK) + logError(1, "%s %s Init after Probe error (ERROR = %08X)\n", + tag, __func__, error); + + return error; +} + +/* + * TODO: change this function according with the needs + * of customer in terms of feature to enable/disable + */ +static int fts_mode_handler(struct fts_ts_info *info, int force) +{ + int res = OK; + int ret = OK; + + /* initialize the mode to Nothing in order */ + /* to be updated depending on the features enabled */ + info->mode = MODE_NOTHING; + + logError(0, "%s %s: Mode Handler starting...\n", tag, __func__); + switch (info->resume_bit) { + case 0: + /* screen down */ + logError(0, "%s %s: Screen OFF...\n", tag, __func__); + /* + * do sense off in order to avoid the flooding + * of the fifo with touch events if someone is + * touching the panel during suspend + */ + logError(0, "%s %s: Sense OFF!\n", tag, __func__); + /* + *we need to use fts_command for speed reason + * (no need to check echo in this case and interrupt + * can be enabled) + */ + res |= fts_command(info, FTS_CMD_MS_MT_SENSE_OFF); +#ifdef PHONE_KEY + logError(0, "%s %s: Key OFF!\n", tag, __func__); + res |= fts_command(info, FTS_CMD_MS_KEY_OFF); +#endif + +#ifdef PHONE_GESTURE + if (info->gesture_enabled == 1) { + logError(0, "%s %s: enter in gesture mode!\n", + tag, __func__); + ret = enterGestureMode(isSystemResettedDown()); + if (ret >= OK) { + info->mode |= FEAT_GESTURE; + } else { + logError(1, + "%s %s:enterGestureMode failed!%08X recovery in senseOff\n", + tag, __func__, ret); + } + res |= ret; + } +#endif + if (info->mode != (FEAT_GESTURE|MODE_NOTHING) + || info->gesture_enabled == 0) + info->mode |= MODE_SENSEOFF; + setSystemResettedDown(0); + break; + + case 1: + /* screen up */ + logError(0, "%s %s: Screen ON...\n", tag, __func__); + +#ifdef FEAT_GLOVE + if ((info->glove_enabled == FEAT_ENABLE && + isSystemResettedUp()) || force == 1) { + logError(0, "%s %s: Glove Mode setting...\n", + tag, __func__); + ret = featureEnableDisable(info->glove_enabled, + FEAT_GLOVE); + if (ret < OK) { + logError(1, + "%s %s:error in setting GLOVE_MODE!%08X\n", + tag, __func__, ret); + } + res |= ret; + + if (ret >= OK && info->glove_enabled == FEAT_ENABLE) { + info->mode |= FEAT_GLOVE; + logError(1, "%s %s: GLOVE_MODE Enabled!\n", + tag, __func__); + } else { + logError(1, "%s %s: GLOVE_MODE Disabled!\n", + tag, __func__); + } + } +#endif +#ifdef FEAT_STYLUS + if ((info->stylus_enabled == FEAT_ENABLE && + isSystemResettedUp()) || force == 1) { + logError(0, "%s %s: Stylus Mode setting...\n", + tag, __func__); + ret = featureEnableDisable(info->stylus_enabled, + FEAT_STYLUS); + if (ret < OK) { + logError(1, + "%s %s:error in set STYLUS_MODE!%08X\n", + tag, __func__, ret); + } + res |= ret; + + if (ret >= OK && info->stylus_enabled == FEAT_ENABLE) { + info->mode |= FEAT_STYLUS; + logError(1, "%s %s: STYLUS_MODE Enabled!\n", + tag, __func__); + } else { + logError(1, "%s %s: STYLUS_MODE Disabled!\n", + tag, __func__); + } + } +#endif +#ifdef FEAT_COVER + if ((info->cover_enabled == FEAT_ENABLE && + isSystemResettedUp()) || force == 1) { + logError(0, "%s %s: Cover Mode setting...\n", + tag, __func__); + ret = featureEnableDisable(info->cover_enabled, + FEAT_COVER); + if (ret < OK) { + logError(1, + "%s %s:error setting COVER_MODE!%08X\n", + tag, __func__, ret); + } + res |= ret; + + if (ret >= OK && info->cover_enabled == FEAT_ENABLE) { + info->mode |= FEAT_COVER; + logError(1, "%s %s: COVER_MODE Enabled!\n", + tag, __func__); + } else { + logError(1, "%s %s: COVER_MODE Disabled!\n", + tag, __func__); + } + } +#endif +#ifdef FEAT_CHARGER + if ((info->charger_enabled == FEAT_ENABLE && + isSystemResettedUp()) || force == 1) { + logError(0, "%s %s: Charger Mode setting...\n", + tag, __func__); + ret = featureEnableDisable(info->charger_enabled, + FEAT_CHARGER); + if (ret < OK) { + logError(1, + "%s %s:error set CHARGER_MODE!%08X\n", + tag, __func__, ret); + } + res |= ret; + + if (ret >= OK && info->charger_enabled == FEAT_ENABLE) { + info->mode |= FEAT_CHARGER; + logError(1, "%s %s: CHARGER_MODE Enabled!\n", + tag, __func__); + } else { + logError(1, "%s %s: CHARGER_MODE Disabled!\n", + tag, __func__); + } + } +#endif +#ifdef FEAT_VR + if ((info->vr_enabled == FEAT_ENABLE && + isSystemResettedUp()) || force == 1) { + logError(0, "%s %s: Vr Mode setting\n", tag, __func__); + ret = featureEnableDisable(info->vr_enabled, FEAT_VR); + if (ret < OK) { + logError(1, + "%s %s:error setting VR_MODE!:%08X\n", + tag, __func__, ret); + } + res |= ret; + + if (ret >= OK && info->vr_enabled == FEAT_ENABLE) { + info->mode |= FEAT_VR; + logError(1, "%s %s: VR_MODE Enabled!\n", + tag, __func__); + } else { + logError(1, "%s %s: VR_MODE Disabled!\n", + tag, __func__); + } + } +#endif +#ifdef FEAT_EDGE_REJECTION + if ((info->edge_rej_enabled == FEAT_ENABLE && + isSystemResettedUp()) || force == 1) { + logError(0, "%s %s: Edge Rejection Mode setting\n", + tag, __func__); + ret = featureEnableDisable(info->edge_rej_enabled, + FEAT_EDGE_REJECTION); + if (ret < OK) { + logError(1, + "%s %s:err set EDGE_REJECTION_MODE!%08X\n", + tag, __func__, ret); + } + res |= ret; + + if (ret >= OK && info->edge_rej_enabled == + FEAT_ENABLE) { + info->mode |= FEAT_EDGE_REJECTION; + logError(1, + "%s %s:EDGE_REJECTION_MODE Enabled!\n", + tag, __func__); + } else { + logError(1, + "%s %s:EDGE_REJECTION_MODE Disabled!\n", + tag, __func__); + } + } +#endif +#ifdef FEAT_CORNER_REJECTION + if ((info->corner_rej_enabled == FEAT_ENABLE && + isSystemResettedUp()) || force == 1) { + logError(0, "%s %s: Corner rejection Mode setting\n", + tag, __func__); + ret = featureEnableDisable(info->corner_rej_enabled, + FEAT_CORNER_REJECTION); + if (ret < OK) { + logError(1, + "%s%s:err CORNER_REJECTION_MODE!%08X\n", + tag, __func__, ret); + } + res |= ret; + + if (ret >= OK && info->corner_rej_enabled == + FEAT_ENABLE) { + info->mode |= FEAT_CORNER_REJECTION; + logError(1, + "%s%s:CORNER_REJECTION_MODE Enabled!\n", + tag, __func__); + } else { + logError(1, + "%s%s:CORNER_REJECTION_MODE Disabled\n", + tag, __func__); + } + } +#endif +#ifdef FEAT_EDGE_PALM_REJECTION + if ((info->edge_palm_rej_enabled == FEAT_ENABLE && + isSystemResettedUp()) || force == 1) { + logError(0, "%s %s:Edge Palm rejection Mode setting\n", + tag, __func__); + ret = featureEnableDisable(info->edge_palm_rej_enabled, + FEAT_EDGE_PALM_REJECTION); + if (ret < OK) { + logError(1, + "%s %s:err EDGE_PALM_REJECTION_MODE!%08X\n", + tag, __func__, ret); + } + res |= ret; + + if (ret >= OK && info->edge_palm_rej_enabled == + FEAT_ENABLE) { + info->mode |= FEAT_EDGE_PALM_REJECTION; + logError(1, + "%s %s:EDGE_PALM_REJECTION_MODE Enabled!\n", + tag, __func__); + } else { + logError(1, + "%s %s:EDGE_PALM_REJECTION_MODE Disabled!\n", + tag, __func__); + } + } +#endif + logError(0, "%s %s: Sense ON!\n", tag, __func__); + res |= fts_command(info, FTS_CMD_MS_MT_SENSE_ON); + info->mode |= MODE_SENSEON; +#ifdef PHONE_KEY + logError(0, "%s %s: Key ON!\n", tag, __func__); + res |= fts_command(info, FTS_CMD_MS_KEY_ON); +#endif + setSystemResettedUp(0); + break; + + default: + logError(1, + "%s %s: invalid resume_bit value = %d! ERROR %08X\n", + tag, __func__, info->resume_bit, ERROR_OP_NOT_ALLOW); + res = ERROR_OP_NOT_ALLOW; + } + logError(0, "%s %s: Mode Handler finished! res = %08X\n", tag, __func__, + res); + return res; +} + +static int fts_chip_power_switch(struct fts_ts_info *info, bool on) +{ + int error = -1; + + if (info->bdata->pwr_on_suspend) { + if (!info->ts_pinctrl) + return 0; + + if (on) { + error = pinctrl_select_state(info->ts_pinctrl, + info->pinctrl_state_active); + if (error < 0) + logError(1, "%s: Failed to select %s\n", + __func__, PINCTRL_STATE_ACTIVE); + } else { + error = pinctrl_select_state(info->ts_pinctrl, + info->pinctrl_state_suspend); + if (error < 0) + logError(1, "%s: Failed to select %s\n", + __func__, PINCTRL_STATE_SUSPEND); + } + + return 0; + } + + if (on) { + if (info->bus_reg) { + error = regulator_enable(info->bus_reg); + if (error < 0) + logError(1, "%s %s: Failed to enable AVDD\n", + tag, __func__); + } + + if (info->pwr_reg) { + error = regulator_enable(info->pwr_reg); + if (error < 0) + logError(1, "%s %s: Failed to enable DVDD\n", + tag, __func__); + } + + if (info->ts_pinctrl) { + if (pinctrl_select_state(info->ts_pinctrl, + info->pinctrl_state_active) < 0) { + logError(1, "%s: Failed to select %s\n", + __func__, PINCTRL_STATE_ACTIVE); + } + } + } else { + if (info->bdata->reset_gpio != GPIO_NOT_DEFINED) + gpio_set_value(info->bdata->reset_gpio, 0); + else + msleep(300); + + if (info->ts_pinctrl) { + if (pinctrl_select_state(info->ts_pinctrl, + info->pinctrl_state_suspend) < 0) { + logError(1, "%s: Failed to select %s\n", + __func__, PINCTRL_STATE_SUSPEND); + } + } + + if (info->pwr_reg) { + error = regulator_disable(info->pwr_reg); + if (error < 0) + logError(1, "%s %s: Failed to disable DVDD\n", + tag, __func__); + } + + if (info->bus_reg) { + error = regulator_disable(info->bus_reg); + if (error < 0) + logError(1, "%s %s: Failed to disable AVDD\n", + tag, __func__); + } + } + return error; +} + + +static void fts_resume_work(struct work_struct *work) +{ + struct fts_ts_info *info; + + info = container_of(work, struct fts_ts_info, resume_work); + + __pm_wakeup_event(info->wakeup_source, HZ); + + fts_chip_power_switch(info, true); + + info->resume_bit = 1; + + fts_system_reset(); +#ifdef USE_NOISE_PARAM + readNoiseParameters(noise_params); +#endif + +#ifdef USE_NOISE_PARAM + writeNoiseParameters(noise_params); +#endif + + release_all_touches(info); + + fts_mode_handler(info, 0); + + info->sensor_sleep = false; + + fts_interrupt_enable(info); +} + +static void fts_suspend_work(struct work_struct *work) +{ + struct fts_ts_info *info; + + info = container_of(work, struct fts_ts_info, suspend_work); + +#ifdef CONFIG_ST_TRUSTED_TOUCH + if (atomic_read(&info->trusted_touch_enabled)) + wait_for_completion_interruptible( + &info->trusted_touch_powerdown); +#endif + + __pm_wakeup_event(info->wakeup_source, HZ); + + info->resume_bit = 0; + + fts_mode_handler(info, 0); + + fts_interrupt_disable(info); + release_all_touches(info); + info->sensor_sleep = true; + + fts_chip_power_switch(info, false); +} + +#if defined(CONFIG_FB_MSM) +static int fts_fb_state_chg_callback(struct notifier_block *nb, + unsigned long val, void *data) +{ + struct fts_ts_info *info = container_of(nb, + struct fts_ts_info, notifier); + struct fb_event *evdata = data; + unsigned int blank; + + if (!evdata || (evdata->id != 0)) + return 0; + + if (val != FB_EVENT_BLANK) + return 0; + + logError(0, "%s %s: fts notifier begin!\n", tag, __func__); + + if (evdata->data && val == FB_EVENT_BLANK && info) { + + blank = *(int *) (evdata->data); + + switch (blank) { + case FB_BLANK_POWERDOWN: + if (info->sensor_sleep) + break; + + logError(0, "%s %s: FB_BLANK_POWERDOWN\n", + tag, __func__); + + queue_work(info->event_wq, &info->suspend_work); + + break; + + case FB_BLANK_UNBLANK: + if (!info->sensor_sleep) + break; + + logError(0, "%s %s: FB_BLANK_UNBLANK\n", + tag, __func__); + + queue_work(info->event_wq, &info->resume_work); + break; + default: + break; + } + } + return NOTIFY_OK; +} + +static struct notifier_block fts_noti_block = { + .notifier_call = fts_fb_state_chg_callback, +}; +#endif + +static int fts_pinctrl_init(struct fts_ts_info *info) +{ + int retval; + + /* Get pinctrl if target uses pinctrl */ + info->ts_pinctrl = devm_pinctrl_get(info->dev); + if (IS_ERR_OR_NULL(info->ts_pinctrl)) { + retval = PTR_ERR(info->ts_pinctrl); + logError(1, "Target does not use pinctrl %d\n", retval); + goto err_pinctrl_get; + } + + info->pinctrl_state_active + = pinctrl_lookup_state(info->ts_pinctrl, PINCTRL_STATE_ACTIVE); + if (IS_ERR_OR_NULL(info->pinctrl_state_active)) { + retval = PTR_ERR(info->pinctrl_state_active); + logError(1, "Can not lookup %s pinstate %d\n", + PINCTRL_STATE_ACTIVE, retval); + goto err_pinctrl_lookup; + } + + info->pinctrl_state_suspend + = pinctrl_lookup_state(info->ts_pinctrl, PINCTRL_STATE_SUSPEND); + if (IS_ERR_OR_NULL(info->pinctrl_state_suspend)) { + retval = PTR_ERR(info->pinctrl_state_suspend); + logError(1, "Can not lookup %s pinstate %d\n", + PINCTRL_STATE_SUSPEND, retval); + goto err_pinctrl_lookup; + } + + info->pinctrl_state_release + = pinctrl_lookup_state(info->ts_pinctrl, PINCTRL_STATE_RELEASE); + if (IS_ERR_OR_NULL(info->pinctrl_state_release)) { + retval = PTR_ERR(info->pinctrl_state_release); + logError(1, "Can not lookup %s pinstate %d\n", + PINCTRL_STATE_RELEASE, retval); + } + + return 0; + +err_pinctrl_lookup: + devm_pinctrl_put(info->ts_pinctrl); +err_pinctrl_get: + info->ts_pinctrl = NULL; + return retval; +} + +static int fts_get_reg(struct fts_ts_info *info, bool get) +{ + int retval; + const struct fts_i2c_platform_data *bdata = info->bdata; + + if (!get) { + retval = 0; + goto regulator_put; + } + + if ((bdata->pwr_reg_name != NULL) && (*bdata->pwr_reg_name != 0)) { + info->pwr_reg = regulator_get(info->dev, + bdata->pwr_reg_name); + if (IS_ERR(info->pwr_reg)) { + logError(1, "%s %s: Failed to get power regulator\n", + tag, __func__); + retval = PTR_ERR(info->pwr_reg); + goto regulator_put; + } + + retval = regulator_set_load(info->pwr_reg, FTS_DVDD_LOAD); + if (retval < 0) { + logError(1, "%s %s: Failed to set power load\n", + tag, __func__); + goto regulator_put; + } + + retval = regulator_set_voltage(info->pwr_reg, + FTS_DVDD_VOL_MIN, FTS_DVDD_VOL_MAX); + if (retval < 0) { + logError(1, "%s %s: Failed to set power voltage\n", + tag, __func__); + goto regulator_put; + } + } + + if ((bdata->bus_reg_name != NULL) && (*bdata->bus_reg_name != 0)) { + info->bus_reg = regulator_get(info->dev, + bdata->bus_reg_name); + if (IS_ERR(info->bus_reg)) { + logError(1, + "%s %s:Failed to get bus pullup regulator\n", + tag, __func__); + retval = PTR_ERR(info->bus_reg); + goto regulator_put; + } + + retval = regulator_set_load(info->bus_reg, FTS_AVDD_LOAD); + if (retval < 0) { + logError(1, "%s %s: Failed to set power load\n", + tag, __func__); + goto regulator_put; + } + + retval = regulator_set_voltage(info->bus_reg, + FTS_AVDD_VOL_MIN, FTS_AVDD_VOL_MAX); + + if (retval < 0) { + logError(1, "%s %s: Failed to set power voltage\n", + tag, __func__); + goto regulator_put; + } + } + + return 0; + +regulator_put: + if (info->pwr_reg) { + regulator_put(info->pwr_reg); + info->pwr_reg = NULL; + } + + if (info->bus_reg) { + regulator_put(info->bus_reg); + info->bus_reg = NULL; + } + + return retval; +} + +static int fts_enable_reg(struct fts_ts_info *info, + bool enable) +{ + int retval; + + if (!enable) { + retval = 0; + goto disable_pwr_reg; + } + + if (info->bus_reg) { + retval = regulator_enable(info->bus_reg); + if (retval < 0) { + logError(1, "%s %s: Failed to enable bus regulator\n", + tag, __func__); + goto exit; + } + } + + if (info->pwr_reg) { + retval = regulator_enable(info->pwr_reg); + if (retval < 0) { + logError(1, "%s %s: Failed to enable power regulator\n", + tag, __func__); + goto disable_bus_reg; + } + } + + return OK; + +disable_pwr_reg: + if (info->pwr_reg) + regulator_disable(info->pwr_reg); + +disable_bus_reg: + if (info->bus_reg) + regulator_disable(info->bus_reg); + +exit: + return retval; +} + +static int fts_gpio_setup(int gpio, bool config, int dir, int state) +{ + int retval = 0; + unsigned char buf[16]; + + if (config) { + snprintf(buf, 16, "fts_gpio_%u\n", gpio); + + retval = gpio_request(gpio, buf); + if (retval) { + logError(1, "%s %s: Failed to get gpio %d (code: %d)", + tag, __func__, gpio, retval); + return retval; + } + + if (dir == 0) + retval = gpio_direction_input(gpio); + else + retval = gpio_direction_output(gpio, state); + if (retval) { + logError(1, "%s %s: Failed to set gpio %d direction", + tag, __func__, gpio); + return retval; + } + } else { + gpio_free(gpio); + } + + return retval; +} + +static int fts_set_gpio(struct fts_ts_info *info) +{ + int retval; + const struct fts_i2c_platform_data *bdata = + info->bdata; + + retval = fts_gpio_setup(bdata->irq_gpio, true, 0, 0); + if (retval < 0) { + logError(1, "%s %s: Failed to configure irq GPIO\n", + tag, __func__); + goto err_gpio_irq; + } + + if (bdata->reset_gpio >= 0) { + retval = fts_gpio_setup(bdata->reset_gpio, true, 1, 0); + if (retval < 0) { + logError(1, "%s %s: Failed to configure reset GPIO\n", + tag, __func__); + goto err_gpio_reset; + } + } + if (bdata->reset_gpio >= 0) { + gpio_set_value(bdata->reset_gpio, 0); + msleep(20); + gpio_set_value(bdata->reset_gpio, 1); + } + + setResetGpio(bdata->reset_gpio); + return OK; + +err_gpio_reset: + fts_gpio_setup(bdata->irq_gpio, false, 0, 0); + setResetGpio(GPIO_NOT_DEFINED); +err_gpio_irq: + return retval; +} + +static int parse_dt(struct device *dev, + struct fts_i2c_platform_data *bdata) +{ + int retval; + const char *name; + struct device_node *np = dev->of_node; + + bdata->irq_gpio = of_get_named_gpio_flags(np, + "st,irq-gpio", 0, NULL); + + logError(0, "%s irq_gpio = %d\n", tag, bdata->irq_gpio); + + bdata->pwr_on_suspend = + of_property_read_bool(np, "st,power_on_suspend"); + + retval = of_property_read_string(np, "st,regulator_dvdd", &name); + if (retval == -EINVAL) + bdata->pwr_reg_name = NULL; + else if (retval < 0) + return retval; + + bdata->pwr_reg_name = name; + logError(0, "%s pwr_reg_name = %s\n", tag, name); + + retval = of_property_read_string(np, "st,regulator_avdd", &name); + if (retval == -EINVAL) + bdata->bus_reg_name = NULL; + else if (retval < 0) + return retval; + + bdata->bus_reg_name = name; + logError(0, "%s bus_reg_name = %s\n", tag, name); + + if (of_property_read_bool(np, "st,reset-gpio")) { + bdata->reset_gpio = of_get_named_gpio_flags(np, + "st,reset-gpio", 0, NULL); + logError(0, "%s reset_gpio =%d\n", tag, bdata->reset_gpio); + } else { + bdata->reset_gpio = GPIO_NOT_DEFINED; + } + + bdata->x_flip = of_property_read_bool(np, "st,x-flip"); + bdata->y_flip = of_property_read_bool(np, "st,y-flip"); + + return OK; +} + +static int check_dt(struct device_node *np) +{ + int i; + int count; + struct device_node *node; + struct drm_panel *panel; + + count = of_count_phandle_with_args(np, "panel", NULL); + if (count <= 0) + return OK; + + for (i = 0; i < count; i++) { + node = of_parse_phandle(np, "panel", i); + panel = of_drm_find_panel(node); + of_node_put(node); + if (!IS_ERR(panel)) { + active_panel = panel; + return OK; + } + } + + return PTR_ERR(panel); +} + +static int check_default_tp(struct device_node *dt, const char *prop) +{ + const char *active_tp; + const char *compatible; + char *start; + int ret; + + ret = of_property_read_string(dt->parent, prop, &active_tp); + if (ret) { + pr_err(" %s:fail to read %s %d\n", __func__, prop, ret); + return -ENODEV; + } + + ret = of_property_read_string(dt, "compatible", &compatible); + if (ret < 0) { + pr_err(" %s:fail to read %s %d\n", __func__, "compatible", ret); + return -ENODEV; + } + + start = strnstr(active_tp, compatible, strlen(active_tp)); + if (start == NULL) { + pr_err(" %s:no match compatible, %s, %s\n", + __func__, compatible, active_tp); + ret = -ENODEV; + } + + return ret; +} + +static int fts_probe_delayed(struct fts_ts_info *info) +{ + int error = 0; + int retval = 0; + +/* Avoid setting up hardware for TVM during probe */ +#ifdef CONFIG_ST_TRUSTED_TOUCH +#ifdef CONFIG_ARCH_QTI_VM + if (!atomic_read(&info->delayed_vm_probe_pending)) { + atomic_set(&info->delayed_vm_probe_pending, 1); + return 0; + } + goto tvm_setup; +#endif +#endif + logError(0, "%s SET Regulators:\n", tag); + retval = fts_get_reg(info, true); + if (retval < 0) { + logError(1, "%s ERROR: %s: Failed to get regulators\n", + tag, __func__); + goto Exit_1; + } + retval = fts_enable_reg(info, true); + if (retval < 0) { + logError(1, + "%s %s: ERROR Failed to enable regulators\n", + tag, __func__); + goto Exit_2; + } + + logError(0, "%s SET GPIOS:\n", tag); + retval = fts_set_gpio(info); + if (retval < 0) { + logError(1, "%s %s: ERROR Failed to set up GPIO's\n", + tag, __func__); + goto Exit_2; + } + + info->client->irq = gpio_to_irq(info->bdata->irq_gpio); + retval = fts_pinctrl_init(info); + if (!retval && info->ts_pinctrl) { + /* + * Pinctrl handle is optional. If pinctrl handle is + * found let pins to be configured in active state. + * If not found continue further without error. + */ + retval = pinctrl_select_state(info->ts_pinctrl, + info->pinctrl_state_active); + if (retval < 0) { + logError(1, + "%s: Failed to select %s pinstate %d\n", + __func__, PINCTRL_STATE_ACTIVE, retval); + } + } + +#ifdef CONFIG_ARCH_QTI_VM +tvm_setup: +#endif + /* init hardware device */ + logError(0, "%s Device Initialization:\n", tag); + error = fts_init(info); + if (error < OK) { + logError(1, "%s Cannot initialize the device ERROR %08X\n", + tag, error); + error = -ENODEV; +#ifdef CONFIG_ARCH_QTI_VM + return error; +#endif + goto Exit_3; + } + + queue_delayed_work(info->fwu_workqueue, &info->fwu_work, + msecs_to_jiffies(EXP_FN_WORK_DELAY_MS)); + return error; + +Exit_3: + if (info->ts_pinctrl) { + if (IS_ERR_OR_NULL(info->pinctrl_state_release)) { + devm_pinctrl_put(info->ts_pinctrl); + info->ts_pinctrl = NULL; + } else { + if (pinctrl_select_state(info->ts_pinctrl, + info->pinctrl_state_release)) + logError(1, "%s:Failed to select %s pinstate\n", + __func__, PINCTRL_STATE_RELEASE); + } + } + fts_enable_reg(info, false); + fts_gpio_setup(info->bdata->irq_gpio, false, 0, 0); + fts_gpio_setup(info->bdata->reset_gpio, false, 0, 0); + +Exit_2: + fts_get_reg(info, false); +Exit_1: + return error; +} + +static int fts_probe_internal(struct i2c_client *client, + const struct i2c_device_id *idp) +{ + struct fts_ts_info *info = NULL; + int error = 0; + struct device_node *dp = client->dev.of_node; + int skip_5_1 = 0; + + logError(0, "%s %s: driver probe begin!\n", tag, __func__); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + logError(1, "%s Unsupported I2C functionality\n", tag); + error = -EIO; + goto ProbeErrorExit_0; + } + + openChannel(client); + + info = kzalloc(sizeof(struct fts_ts_info), GFP_KERNEL); + if (!info) { + logError(1, + "%s can't allocate struct info!\n", + tag); + error = -ENOMEM; + goto ProbeErrorExit_0; + } + + info->client = client; + + i2c_set_clientdata(client, info); + + info->i2c_data = kmalloc(I2C_DATA_MAX_LEN, GFP_KERNEL); + if (info->i2c_data == NULL) { + error = -ENOMEM; + goto ProbeErrorExit_0P1; + } + info->i2c_data_len = I2C_DATA_MAX_LEN; + + logError(0, "%s i2c address: %x\n", tag, client->addr); + info->dev = &info->client->dev; + if (dp) { + info->bdata = devm_kzalloc(&client->dev, + sizeof(struct fts_i2c_platform_data), + GFP_KERNEL); + if (!info->bdata) { + logError(1, "%s ERROR:info.bdata kzalloc failed\n", + tag); + goto ProbeErrorExit_1; + } + parse_dt(&client->dev, info->bdata); + } + + logError(0, "%s SET Auto Fw Update:\n", tag); + info->fwu_workqueue = alloc_workqueue("fts-fwu-queue", + WQ_UNBOUND|WQ_HIGHPRI|WQ_CPU_INTENSIVE, 1); + if (!info->fwu_workqueue) { + logError(1, "%s ERROR: Cannot create fwu work thread\n", tag); + goto ProbeErrorExit_1; + } + + INIT_DELAYED_WORK(&info->fwu_work, fts_fw_update_auto); + + logError(0, "%s SET Event Handler:\n", tag); + info->wakeup_source = wakeup_source_register(&client->dev, + dev_name(&client->dev)); + + info->event_wq = alloc_workqueue("fts-event-queue", + WQ_UNBOUND|WQ_HIGHPRI|WQ_CPU_INTENSIVE, 1); + if (!info->event_wq) { + logError(1, "%s ERROR: Cannot create work thread\n", tag); + error = -ENOMEM; + goto ProbeErrorExit_4; + } + + INIT_WORK(&info->work, fts_event_handler); + + INIT_WORK(&info->resume_work, fts_resume_work); + INIT_WORK(&info->suspend_work, fts_suspend_work); + + logError(0, "%s SET Input Device Property:\n", tag); + /* info->dev = &info->client->dev; */ + info->input_dev = input_allocate_device(); + if (!info->input_dev) { + logError(1, "%s ERROR: No such input device defined!\n", + tag); + error = -ENODEV; + goto ProbeErrorExit_5; + } + info->input_dev->dev.parent = &client->dev; + info->input_dev->name = FTS_TS_DRV_NAME; + snprintf(fts_ts_phys, sizeof(fts_ts_phys), "%s/input0", + info->input_dev->name); + info->input_dev->phys = fts_ts_phys; + info->input_dev->id.bustype = BUS_I2C; + info->input_dev->id.vendor = 0x0001; + info->input_dev->id.product = 0x0002; + info->input_dev->id.version = 0x0100; + + __set_bit(EV_SYN, info->input_dev->evbit); + __set_bit(EV_KEY, info->input_dev->evbit); + __set_bit(EV_ABS, info->input_dev->evbit); + __set_bit(BTN_TOUCH, info->input_dev->keybit); + __set_bit(BTN_TOOL_FINGER, info->input_dev->keybit); + + input_mt_init_slots(info->input_dev, TOUCH_ID_MAX, INPUT_MT_DIRECT); + + input_set_abs_params(info->input_dev, ABS_MT_POSITION_X, + X_AXIS_MIN, X_AXIS_MAX, 0, 0); + input_set_abs_params(info->input_dev, ABS_MT_POSITION_Y, + Y_AXIS_MIN, Y_AXIS_MAX, 0, 0); + input_set_abs_params(info->input_dev, ABS_MT_TOUCH_MAJOR, + AREA_MIN, AREA_MAX, 0, 0); + input_set_abs_params(info->input_dev, ABS_MT_TOUCH_MINOR, + AREA_MIN, AREA_MAX, 0, 0); + +#ifdef PHONE_GESTURE + input_set_capability(info->input_dev, EV_KEY, KEY_WAKEUP); + + input_set_capability(info->input_dev, EV_KEY, KEY_M); + input_set_capability(info->input_dev, EV_KEY, KEY_O); + input_set_capability(info->input_dev, EV_KEY, KEY_E); + input_set_capability(info->input_dev, EV_KEY, KEY_W); + input_set_capability(info->input_dev, EV_KEY, KEY_C); + input_set_capability(info->input_dev, EV_KEY, KEY_L); + input_set_capability(info->input_dev, EV_KEY, KEY_F); + input_set_capability(info->input_dev, EV_KEY, KEY_V); + input_set_capability(info->input_dev, EV_KEY, KEY_S); + input_set_capability(info->input_dev, EV_KEY, KEY_Z); + input_set_capability(info->input_dev, EV_KEY, KEY_WWW); + + input_set_capability(info->input_dev, EV_KEY, KEY_LEFT); + input_set_capability(info->input_dev, EV_KEY, KEY_RIGHT); + input_set_capability(info->input_dev, EV_KEY, KEY_UP); + input_set_capability(info->input_dev, EV_KEY, KEY_DOWN); + + input_set_capability(info->input_dev, EV_KEY, KEY_F1); + input_set_capability(info->input_dev, EV_KEY, KEY_F2); + input_set_capability(info->input_dev, EV_KEY, KEY_F3); + input_set_capability(info->input_dev, EV_KEY, KEY_F4); + input_set_capability(info->input_dev, EV_KEY, KEY_F5); + + input_set_capability(info->input_dev, EV_KEY, KEY_LEFTBRACE); + input_set_capability(info->input_dev, EV_KEY, KEY_RIGHTBRACE); +#endif + +#ifdef PHONE_KEY + /* KEY associated to the touch screen buttons */ + input_set_capability(info->input_dev, EV_KEY, KEY_HOMEPAGE); + input_set_capability(info->input_dev, EV_KEY, KEY_BACK); + input_set_capability(info->input_dev, EV_KEY, KEY_MENU); +#endif + + mutex_init(&(info->input_report_mutex)); + +#ifdef PHONE_GESTURE + mutex_init(&gestureMask_mutex); +#endif + + /* register the multi-touch input device */ + error = input_register_device(info->input_dev); + if (error) { + logError(1, "%s ERROR: No such input device\n", tag); + error = -ENODEV; + goto ProbeErrorExit_5_1; + } + + skip_5_1 = 1; + /* track slots */ + info->touch_id = 0; +#ifdef STYLUS_MODE + info->stylus_id = 0; +#endif + + /* + * init feature switches (by default all the features + * are disable, if one feature want to be enabled from + * the start, set the corresponding value to 1) + */ + info->gesture_enabled = 0; + info->glove_enabled = 0; + info->charger_enabled = 0; + info->stylus_enabled = 0; + info->vr_enabled = 0; + info->cover_enabled = 0; + info->edge_rej_enabled = 0; + info->corner_rej_enabled = 0; + info->edge_palm_rej_enabled = 0; + + info->resume_bit = 1; +#if defined(CONFIG_FB_MSM) + info->notifier = fts_noti_block; +#endif + +#ifdef CONFIG_ST_TRUSTED_TOUCH + fts_trusted_touch_init(info); + mutex_init(&(info->fts_clk_io_ctrl_mutex)); +#endif + + logError(0, "%s SET Device File Nodes:\n", tag); + /* sysfs stuff */ + info->attrs.attrs = fts_attr_group; + error = sysfs_create_group(&client->dev.kobj, &info->attrs); + if (error) { + logError(1, "%s ERROR: Cannot create sysfs structure!\n", tag); + error = -ENODEV; + goto ProbeErrorExit_7; + } + + /* I2C cmd */ + fts_cmd_class = class_create(THIS_MODULE, FTS_TS_DRV_NAME); + +#ifdef SCRIPTLESS + info->i2c_cmd_dev = device_create(fts_cmd_class, + NULL, DCHIP_ID_0, info, "fts_i2c"); + if (IS_ERR(info->i2c_cmd_dev)) { + logError(1, + "%s ERROR: Failed to create device for the sysfs!\n", + tag); + goto ProbeErrorExit_8; + } + + dev_set_drvdata(info->i2c_cmd_dev, info); + + error = sysfs_create_group(&info->i2c_cmd_dev->kobj, + &i2c_cmd_attr_group); + if (error) { + logError(1, "%s ERROR: Failed to create sysfs group!\n", tag); + goto ProbeErrorExit_9; + } +#endif + +#ifdef DRIVER_TEST + info->test_cmd_dev = device_create(fts_cmd_class, + NULL, DCHIP_ID_0, info, "fts_driver_test"); + if (IS_ERR(info->test_cmd_dev)) { + logError(1, + "%s ERROR: Failed to create device for the sysfs!\n", + tag); + goto ProbeErrorExit_10; + } + + dev_set_drvdata(info->test_cmd_dev, info); + + error = sysfs_create_group(&info->test_cmd_dev->kobj, + &test_cmd_attr_group); + if (error) { + logError(1, "%s ERROR: Failed to create sysfs group!\n", tag); + goto ProbeErrorExit_11; + } +#endif + + info->aoi_cmd_dev = device_create(fts_cmd_class, + NULL, DCHIP_ID_0, info, "touch_aoi"); + if (IS_ERR(info->aoi_cmd_dev)) { + logError(1, + "%s ERROR: Failed to create device for the sysfs\n", + tag); + goto ProbeErrorExit_10; + } + + dev_set_drvdata(info->aoi_cmd_dev, info); + + error = sysfs_create_group(&info->aoi_cmd_dev->kobj, + &aoi_cmd_attr_group); + if (error) { + logError(1, "%s ERROR: Failed to create sysfs group\n", tag); + goto ProbeErrorExit_11; + } + + info->aoi_class = class_create(THIS_MODULE, "android_touch"); + if (info->aoi_class) { + info->aoi_dev = device_create(info->aoi_class, + NULL, DCHIP_ID_0, info, "touch"); + if (!IS_ERR(info->aoi_dev)) { + dev_set_drvdata(info->aoi_dev, info); + + error = sysfs_create_group(&info->aoi_dev->kobj, + &aoi_enable_attr_group); + } + } + + error = fts_probe_delayed(info); + if (error) { + logError(1, "%s ERROR: Failed to enable resources\n", + tag); + goto ProbeErrorExit_11; + } + logError(1, "%s Probe Finished!\n", tag); + return OK; + + /* error exit path */ +#ifdef DRIVER_TEST +ProbeErrorExit_11: +#ifndef SCRIPTLESS + device_destroy(fts_cmd_class, DCHIP_ID_0); +#endif + +ProbeErrorExit_10: +#ifndef SCRIPTLESS + sysfs_remove_group(&client->dev.kobj, &info->attrs); +#endif +#endif + +#ifdef SCRIPTLESS +ProbeErrorExit_9: + device_destroy(fts_cmd_class, DCHIP_ID_0); + +ProbeErrorExit_8: + sysfs_remove_group(&client->dev.kobj, &info->attrs); +#endif + +ProbeErrorExit_7: +#ifdef CONFIG_ST_TRUSTED_TOUCH + fts_vm_deinit(info); +#endif + /* fb_unregister_client(&info->notifier); */ + input_unregister_device(info->input_dev); + +ProbeErrorExit_5_1: + if (skip_5_1 != 1) + input_free_device(info->input_dev); + +ProbeErrorExit_5: + destroy_workqueue(info->event_wq); + +ProbeErrorExit_4: + destroy_workqueue(info->fwu_workqueue); + wakeup_source_unregister(info->wakeup_source); + +ProbeErrorExit_1: + kfree(info->i2c_data); +ProbeErrorExit_0P1: + kfree(info); + +ProbeErrorExit_0: + logError(1, "%s Probe Failed!\n", tag); + + return error; +} + +static int fts_probe(struct i2c_client *client, const struct i2c_device_id *idp) +{ + int error = 0; + struct device_node *dp = client->dev.of_node; + + error = check_dt(dp); + if (error == -EPROBE_DEFER) + return error; + + if (error) { + if (!check_default_tp(dp, "qcom,i2c-touch-active")) + error = -EPROBE_DEFER; + else + error = -ENODEV; + + return error; + } + + device_init_wakeup(&client->dev, true); + return fts_probe_internal(client, idp); +} + +static int fts_remove(struct i2c_client *client) +{ + struct fts_ts_info *info = i2c_get_clientdata(client); + + if (info->aoi_dev) { + sysfs_remove_group(&info->aoi_dev->kobj, + &aoi_enable_attr_group); + info->aoi_dev = NULL; + } + + if (info->aoi_class) { + device_destroy(info->aoi_class, DCHIP_ID_0); + info->aoi_class = NULL; + } + +#ifdef DRIVER_TEST + sysfs_remove_group(&info->test_cmd_dev->kobj, + &test_cmd_attr_group); +#endif + +#ifdef SCRIPTLESS + /* I2C cmd */ + sysfs_remove_group(&info->i2c_cmd_dev->kobj, &i2c_cmd_attr_group); +#endif + +#if defined(SCRIPTLESS) || defined(DRIVER_TEST) + device_destroy(fts_cmd_class, DCHIP_ID_0); +#endif + + /* sysfs stuff */ + sysfs_remove_group(&client->dev.kobj, &info->attrs); + + /* remove interrupt and event handlers */ + fts_interrupt_uninstall(info); + +#if defined(CONFIG_FB_MSM) + fb_unregister_client(&info->notifier); +#else + if (active_panel && info->notifier_cookie) + panel_event_notifier_unregister(info->notifier_cookie); +#endif + + /* unregister the device */ + input_unregister_device(info->input_dev); + + /* input_free_device(info->input_dev ); */ + + /* Empty the FIFO buffer */ + fts_command(info, FIFO_CMD_FLUSH); + /* flushFIFO(); */ + + /* Remove the work thread */ + destroy_workqueue(info->event_wq); + /* wake_lock_destroy(&info->wakelock); */ + wakeup_source_unregister(info->wakeup_source); + destroy_workqueue(info->fwu_workqueue); + + if (info->ts_pinctrl) { + if (IS_ERR_OR_NULL(info->pinctrl_state_release)) { + devm_pinctrl_put(info->ts_pinctrl); + info->ts_pinctrl = NULL; + } else { + pinctrl_select_state(info->ts_pinctrl, + info->pinctrl_state_release); + } + } + fts_enable_reg(info, false); + fts_gpio_setup(info->bdata->irq_gpio, false, 0, 0); + fts_gpio_setup(info->bdata->reset_gpio, false, 0, 0); + + fts_get_reg(info, false); + + /* free all */ + kfree(info->i2c_data); + kfree(info); + + device_init_wakeup(&client->dev, false); + return OK; +} + +static const struct of_device_id fts_of_match_table[] = { + { + .compatible = "st,fts", + }, + {}, +}; +static const struct i2c_device_id fts_device_id[] = { + {FTS_TS_DRV_NAME, 0}, + {} +}; + +static struct i2c_driver fts_i2c_driver = { + .driver = { + .name = FTS_TS_DRV_NAME, + .of_match_table = fts_of_match_table, + }, + .probe = fts_probe, + .remove = fts_remove, + .id_table = fts_device_id, +}; + +static int __init fts_driver_init(void) +{ + return i2c_add_driver(&fts_i2c_driver); +} + +static void __exit fts_driver_exit(void) +{ + i2c_del_driver(&fts_i2c_driver); +} + +module_init(fts_driver_init); +module_exit(fts_driver_exit); + +MODULE_DESCRIPTION("STMicroelectronics MultiTouch IC Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/qcom/opensource/touch-drivers/st/fts.h b/qcom/opensource/touch-drivers/st/fts.h new file mode 100644 index 0000000000..ca1888ac3b --- /dev/null +++ b/qcom/opensource/touch-drivers/st/fts.h @@ -0,0 +1,398 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * FTS Capacitive touch screen controller (FingerTipS) + * + * Copyright (C) 2016-2019, STMicroelectronics Limited. + * Authors: AMG(Analog Mems Group) + * + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#ifndef _LINUX_FTS_I2C_H_ +#define _LINUX_FTS_I2C_H_ + +/*#include */ +#include +#include +#include + +#include "fts_lib/ftsSoftware.h" +#include "fts_lib/ftsHardware.h" +#include "fts_lib/ftsGesture.h" + +#define FTS_POWER_ON 1 +#define FTS_POWER_OFF 0 + +/****************** CONFIGURATION SECTION ******************/ + +/**** CODE CONFIGURATION ****/ + +#define FTS_TS_DRV_NAME "fts" +#define FTS_TS_DRV_VERSION "4.2.14" /* version */ + +/*#define SCRIPTLESS*/ /*allow to work in scriptless mode with the GUI*/ +#ifdef SCRIPTLESS +#define SCRIPTLESS_DEBUG +/** + * uncomment this macro definition to print debug + * message for script less support + */ +#endif + +#define DRIVER_TEST + +/* #define FW_H_FILE */ /*include the FW as header file*/ +#ifdef FW_H_FILE + #define FW_SIZE_NAME myArray_size + #define FW_ARRAY_NAME myArray +#endif + +/*#define LIMITS_H_FILE*/ /*include the limits file as header file*/ +#ifdef LIMITS_H_FILE + #define LIMITS_SIZE_NAME myArray2_size + #define LIMITS_ARRAY_NAME myArray2 +#endif + +/**** END ****/ + + +/**** FEATURES USED IN THE IC ***/ +/* #define PHONE_KEY enable the keys */ + +#define PHONE_GESTURE /*allow to use the gestures*/ +#ifdef PHONE_GESTURE + #define USE_GESTURE_MASK + #define USE_CUSTOM_GESTURES +#endif + +#define USE_ONE_FILE_NODE +/*allow to enable/disable all the features just using one file node*/ + +#define EDGE_REJ +/*allow edge rej feature (comment to disable)*/ + +#define CORNER_REJ +/*allow corn rej feature (comment to disable)*/ + +#define EDGE_PALM_REJ +/*allow edge palm rej feature (comment to disable)*/ + +#define CHARGER_MODE +/*allow charger mode feature (comment to disable)*/ + +#define GLOVE_MODE +/*allow glove mode feature (comment to disable)*/ + +#define VR_MODE +/*allow vr mode feature (comment to disable)*/ + +#define COVER_MODE +/*allow cover mode feature (comment to disable)*/ + +#define STYLUS_MODE +/*allow stylus mode feature (comment to disable)*/ + +#define USE_NOISE_PARAM +/*set noise params during resume (comment to disable)*/ + +/**** END ****/ + + +/**** PANEL SPECIFICATION ****/ +#define X_AXIS_MAX 1440 +#define X_AXIS_MIN 0 +#define Y_AXIS_MAX 2880 +#define Y_AXIS_MIN 0 + +#define PRESSURE_MIN 0 +#define PRESSURE_MAX 127 + +#define TOUCH_ID_MAX 10 + +#define AREA_MIN PRESSURE_MIN +#define AREA_MAX PRESSURE_MAX +/**** END ****/ + +/*********************************************************/ + +/* Flash programming */ + +#define INIT_FLAG_CNT 3 + +/* KEYS */ +#define KEY1 0x02 +#define KEY2 0x01 +#define KEY3 0x04 + +/* + * Configuration mode + */ +/** + * bitmask which can assume the value defined as + * features in ftsSoftware.h or the following values + */ + +#define MODE_NOTHING 0x00000000 +#define MODE_SENSEON 0x10000000 +#define MODE_SENSEOFF 0x20000000 +#define FEAT_GESTURE 0x40000000 + + +/* + * Status Event Field: + * id of command that triggered the event + */ + +#define FTS_FLASH_WRITE_CONFIG 0x03 +#define FTS_FLASH_WRITE_COMP_MEMORY 0x04 +#define FTS_FORCE_CAL_SELF_MUTUAL 0x05 +#define FTS_FORCE_CAL_SELF 0x06 +#define FTS_WATER_MODE_ON 0x07 +#define FTS_WATER_MODE_OFF 0x08 + + +#define EXP_FN_WORK_DELAY_MS 1000 + +#define CMD_STR_LEN 32 +#define I2C_DATA_MAX_LEN 32 + +#ifdef SCRIPTLESS +/* + * I2C Command Read/Write Function + */ + +#define CMD_RESULT_STR_LEN 2048 +#endif + +#define TSP_BUF_SIZE 4096 + +#define PINCTRL_STATE_ACTIVE "pmx_ts_active" +#define PINCTRL_STATE_SUSPEND "pmx_ts_suspend" +#define PINCTRL_STATE_RELEASE "pmx_ts_release" + +/*add by guchong*/ +#ifdef PHONE_GESTURE +extern u16 gesture_coordinates_x[GESTURE_COORDS_REPORT_MAX]; +extern u16 gesture_coordinates_y[GESTURE_COORDS_REPORT_MAX]; +extern int gesture_coords_reported; +extern struct mutex gestureMask_mutex; +#endif + +struct fts_i2c_platform_data { + bool x_flip; + bool y_flip; + int (*power)(bool on); + int irq_gpio; + int reset_gpio; + const char *pwr_reg_name; + const char *bus_reg_name; + bool pwr_on_suspend; +}; + +/* + * Forward declaration + */ +struct fts_ts_info; + +/* + * Dispatch event handler + */ +struct event_dispatch_handler_t { + void (*handler)(struct fts_ts_info *info, unsigned char *data); +}; + +enum trusted_touch_mode_config { + TRUSTED_TOUCH_VM_MODE, + TRUSTED_TOUCH_MODE_NONE +}; + +#ifdef CONFIG_ST_TRUSTED_TOUCH +#define TRUSTED_TOUCH_MEM_LABEL 0x7 + +struct trusted_touch_vm_info { + enum gh_irq_label irq_label; + enum gh_vm_names vm_name; + u32 hw_irq; + gh_memparcel_handle_t vm_mem_handle; + u32 *iomem_bases; + u32 *iomem_sizes; + u32 iomem_list_size; + void *mem_cookie; +#ifdef CONFIG_ARCH_QTI_VM + atomic_t tvm_owns_iomem; + atomic_t tvm_owns_irq; +#else + atomic_t pvm_owns_iomem; + atomic_t pvm_owns_irq; +#endif +}; +#endif + +/* + * struct fts_ts_info - FTS capacitive touch screen device information + * @dev: Pointer to the structure device + * @client: I2C client structure + * @input_dev Input device structure + * @work Work thread + * @event_wq Event queue for work thread + * @event_dispatch_table Event dispatch table handlers + * @attrs SysFS attributes + * @mode Device operating mode (bitmask) + * @touch_id Bitmask for touch id (mapped to input slots) + * @stylus_id Bitmask for tracking the stylus touches + * (mapped using the touchId) + * @timer Timer when operating in polling mode + * @power Power on/off routine + * @bdata HW info retrived from device tree + * @pwr_reg DVDD power regulator + * @bus_reg AVDD power regulator + * @resume_bit Indicate if screen off/on + * @fwupdate_stat Store the result of a fw update triggered by the host + * @notifier Used for be notified from a suspend/resume event + * @notifier_cookie saved cookie during panel event notification + * @sensor_sleep true susped was called, false resume was called + * @wakelock Wake Lock struct + * @input_report_mutex mutex for handling the pressure of keys + * @series of switches to store the enabling status of a particular + * feature from the host + */ +struct fts_ts_info { + struct device *dev; + struct i2c_client *client; + struct input_dev *input_dev; + + struct work_struct work; + struct work_struct suspend_work; + struct work_struct resume_work; + struct workqueue_struct *event_wq; + + struct delayed_work fwu_work; + struct workqueue_struct *fwu_workqueue; + struct completion cmd_done; + + struct pinctrl *ts_pinctrl; + struct pinctrl_state *pinctrl_state_active; + struct pinctrl_state *pinctrl_state_suspend; + struct pinctrl_state *pinctrl_state_release; + + struct event_dispatch_handler_t *event_dispatch_table; + + struct attribute_group attrs; + + unsigned int mode; + unsigned long touch_id; +#ifdef STYLUS_MODE + unsigned long stylus_id; +#endif + + +#ifdef FTS_USE_POLLING_MODE + struct hrtimer timer; +#endif + + +#ifdef SCRIPTLESS + /*I2C cmd*/ + struct device *i2c_cmd_dev; + char cmd_read_result[CMD_RESULT_STR_LEN]; + char cmd_wr_result[CMD_RESULT_STR_LEN]; + char cmd_write_result[20]; +#endif + +#ifdef DRIVER_TEST + struct device *test_cmd_dev; +#endif + int (*power)(bool on); + + struct fts_i2c_platform_data *bdata; + struct regulator *pwr_reg; + struct regulator *bus_reg; + + int resume_bit; + int fwupdate_stat; + + struct notifier_block notifier; + void *notifier_cookie; + bool sensor_sleep; + struct wakeup_source *wakeup_source; + + /* input lock */ + struct mutex input_report_mutex; + + /*switches for features*/ + unsigned int gesture_enabled; + unsigned int glove_enabled; + unsigned int charger_enabled; + unsigned int stylus_enabled; + unsigned int vr_enabled; + unsigned int cover_enabled; + unsigned int edge_rej_enabled; + unsigned int corner_rej_enabled; + unsigned int edge_palm_rej_enabled; + + uint8_t *i2c_data; + uint8_t i2c_data_len; + + struct device *aoi_cmd_dev; + bool aoi_notify_enabled; + bool aoi_wake_on_suspend; + + /* aoi region */ + struct class *aoi_class; + struct device *aoi_dev; + int aoi_left; + int aoi_top; + int aoi_bottom; + int aoi_right; + +#ifdef CONFIG_ST_TRUSTED_TOUCH + struct trusted_touch_vm_info *vm_info; + struct mutex fts_clk_io_ctrl_mutex; + const char *touch_environment; + struct completion trusted_touch_powerdown; + struct completion resource_checkpoint; + struct clk *core_clk; + struct clk *iface_clk; + atomic_t trusted_touch_initialized; + atomic_t trusted_touch_enabled; + atomic_t delayed_vm_probe_pending; + atomic_t trusted_touch_mode; +#endif +}; + +extern struct chipInfo ftsInfo; + +int fts_chip_powercycle(struct fts_ts_info *info); +int fts_chip_powercycle2(struct fts_ts_info *info, unsigned long sleep); +void release_all_touches(struct fts_ts_info *info); +/*int fts_get_fw_version(struct fts_ts_info *info);*/ +/*extern unsigned int le_to_uint(const unsigned char *ptr);*/ +/*extern unsigned int be_to_uint(const unsigned char *ptr);*/ +extern int input_register_notifier_client(struct notifier_block *nb); +extern int input_unregister_notifier_client(struct notifier_block *nb); + +extern struct attribute_group aoi_cmd_attr_group; +extern struct attribute_group aoi_enable_attr_group; + +#ifdef SCRIPTLESS +extern struct attribute_group i2c_cmd_attr_group; +#endif + +#ifdef DRIVER_TEST +extern struct attribute_group test_cmd_attr_group; +#endif + + +#endif + diff --git a/qcom/opensource/touch-drivers/st/fts_aoi_event.c b/qcom/opensource/touch-drivers/st/fts_aoi_event.c new file mode 100644 index 0000000000..1a37f659e1 --- /dev/null +++ b/qcom/opensource/touch-drivers/st/fts_aoi_event.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2019-2020, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + */ + +#include +#include +#include "fts.h" + +static ssize_t touch_event_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + + return 0; +} + +ssize_t aoi_set_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + int left, top, right, bottom; + + ret = sscanf(buf, "%d %d %d %d", &left, &top, &right, &bottom); + if (ret != 4) + return -EINVAL; + + if (right > X_AXIS_MAX) + right = X_AXIS_MAX; + if (bottom > Y_AXIS_MAX) + bottom = Y_AXIS_MAX; + + if (left < 0 || left > X_AXIS_MAX || right < 0 || + top > Y_AXIS_MAX || bottom < 0) + return -EINVAL; + + if (left >= right || top >= bottom) { + info->aoi_left = 0; + info->aoi_top = 0; + info->aoi_right = 0; + info->aoi_bottom = 0; + info->aoi_notify_enabled = false; + return count; + } + + info->aoi_left = left; + info->aoi_top = top; + info->aoi_right = right; + info->aoi_bottom = bottom; + + info->aoi_notify_enabled = true; + return count; +} + +static ssize_t aoi_set_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + size_t len = 0; + + len = scnprintf(buf + len, PAGE_SIZE, + "%d %d %d %d", + info->aoi_left, + info->aoi_top, + info->aoi_right, + info->aoi_bottom); + + return len; +} + +static ssize_t power_set_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int enable; + + if (kstrtoint(buf, 10, &enable)) + return -EINVAL; + + return count; +} + +static DEVICE_ATTR_RO(touch_event); +static DEVICE_ATTR_RW(aoi_set); +static DEVICE_ATTR_WO(power_set); + +static struct attribute *aoi_cmd_attributes[] = { + &dev_attr_touch_event.attr, + &dev_attr_aoi_set.attr, + &dev_attr_power_set.attr, + NULL, +}; + +struct attribute_group aoi_cmd_attr_group = { + .attrs = aoi_cmd_attributes, +}; + +static ssize_t enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct fts_ts_info *info = dev_get_drvdata(dev); + int enable; + + if (kstrtoint(buf, 10, &enable)) + return -EINVAL; + + if (!enable && info->aoi_notify_enabled) { + info->aoi_left = 0; + info->aoi_top = 0; + info->aoi_right = 0; + info->aoi_bottom = 0; + info->aoi_notify_enabled = false; + } else { + info->aoi_left = 0; + info->aoi_top = 0; + info->aoi_right = X_AXIS_MAX; + info->aoi_bottom = Y_AXIS_MAX; + info->aoi_notify_enabled = true; + } + + return count; +} + +static ssize_t enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fts_ts_info *info = dev_get_drvdata(dev); + size_t len = 0; + + len = scnprintf(buf, PAGE_SIZE, + "%d", + info->aoi_notify_enabled); + + return len; +} + +static DEVICE_ATTR_RW(enable); + +static struct attribute *aoi_enable_attributes[] = { + &dev_attr_aoi_set.attr, + &dev_attr_enable.attr, + NULL, +}; + +struct attribute_group aoi_enable_attr_group = { + .attrs = aoi_enable_attributes, +}; diff --git a/qcom/opensource/touch-drivers/st/fts_driver_test.c b/qcom/opensource/touch-drivers/st/fts_driver_test.c new file mode 100644 index 0000000000..fa9ed503cf --- /dev/null +++ b/qcom/opensource/touch-drivers/st/fts_driver_test.c @@ -0,0 +1,1107 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * FTS Capacitive touch screen controller (FingerTipS) + * + * Copyright (C) 2016-2019, STMicroelectronics Limited. + * Authors: AMG(Analog Mems Group) + * + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +/*#include */ +#include + +#include +#include +#include +#include "fts.h" +#include "fts_lib/ftsCompensation.h" +#include "fts_lib/ftsIO.h" +#include "fts_lib/ftsError.h" +#include "fts_lib/ftsFrame.h" +#include "fts_lib/ftsFlash.h" +#include "fts_lib/ftsTest.h" +#include "fts_lib/ftsTime.h" +#include "fts_lib/ftsTool.h" + +#ifdef DRIVER_TEST + +#define MAX_PARAMS 50 + +/*DEFINE COMMANDS TO TEST*/ +#define CMD_READ 0x00 +#define CMD_WRITE 0x01 +#define CMD_READU16 0x02 +#define CMD_READB2 0x03 +#define CMD_READB2U16 0x04 +#define CMD_POLLFOREVENT 0x05 +#define CMD_SYSTEMRESET 0x06 +#define CMD_CLEANUP 0x07 +#define CMD_GETFORCELEN 0x08 +#define CMD_GETSENSELEN 0x09 +#define CMD_GETMSFRAME 0x0A +/*#define CMD_GETMSKEYFRAME 0x0B*/ +#define CMD_GETSSFRAME 0x0C +#define CMD_REQCOMPDATA 0x0D +#define CMD_READCOMPDATAHEAD 0x0E +#define CMD_READMSCOMPDATA 0x0F +#define CMD_READSSCOMPDATA 0x10 +#define CMD_READGNCOMPDATA 0x11 +#define CMD_GETFWVER 0x12 +#define CMD_FLASHSTATUS 0x13 +#define CMD_FLASHUNLOCK 0x14 +#define CMD_READFWFILE 0x15 +#define CMD_FLASHPROCEDURE 0x16 +#define CMD_ITOTEST 0x17 +#define CMD_INITTEST 0x18 +#define CMD_MSRAWTEST 0x19 +#define CMD_MSINITDATATEST 0x1A +#define CMD_SSRAWTEST 0x1B +#define CMD_SSINITDATATEST 0x1C +#define CMD_MAINTEST 0x1D +#define CMD_POWERCYCLE 0x1E +#define CMD_FWWRITE 0x1F +#define CMD_READCHIPINFO 0x20 +#define CMD_REQFRAME 0x21 + +static char tag[8] = "[ FTS ]\0"; +static u32 functionToTest[MAX_PARAMS]; +static int numberParam; + +static ssize_t stm_driver_test_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int n; + char *p = (char *)buf; + int ret; + + memset(functionToTest, 0, MAX_PARAMS * sizeof(u32)); + + for (n = 0; n < (count + 1) / 3 && n < MAX_PARAMS; n++) { + ret = sscanf(p, "%02X ", &functionToTest[n]); + if (ret != 1) + return -EINVAL; + p += 3; + logError(1, "%s functionToTest[%d] = %02X\n", tag, n, + functionToTest[n]); + } + + numberParam = n; + logError(1, "%s Number of Parameters = %d\n", tag, numberParam); + return count; +} + +static ssize_t stm_driver_test_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + char buff[CMD_STR_LEN] = {0}; + int res = -1, j, count; + int size = 6 * 2; + int temp = 0; + int i; + int byteToRead = 0; + u8 *readData = NULL; + u8 *all_strbuff = NULL; + u8 *cmd = NULL; + + struct MutualSenseFrame frameMS = {0}; + struct SelfSenseFrame frameSS = {0}; + + struct DataHeader dataHead = {0}; + struct MutualSenseData compData = {0}; + struct SelfSenseData comData = {0}; + struct GeneralData gnData = {0}; + + u16 address = 0; + u16 fw_version = 0; + u16 config_id = 0; + + struct Firmware fw; + + /*struct used for defining which test*/ + /*perform during the MP test*/ + + struct TestToDo todoDefault; + + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + + fw.data = NULL; + + todoDefault.MutualRaw = 1; + todoDefault.MutualRawGap = 1; + todoDefault.MutualCx1 = 0; + todoDefault.MutualCx2 = 1; + todoDefault.MutualCx2Adj = 1; + todoDefault.MutualCxTotal = 0; + todoDefault.MutualCxTotalAdj = 0; + + todoDefault.MutualKeyRaw = 0; + todoDefault.MutualKeyCx1 = 0; + todoDefault.MutualKeyCx2 = 0; + todoDefault.MutualKeyCxTotal = 0; + + todoDefault.SelfForceRaw = 1; + todoDefault.SelfForceRawGap = 0; + todoDefault.SelfForceIx1 = 0; + todoDefault.SelfForceIx2 = 0; + todoDefault.SelfForceIx2Adj = 0; + todoDefault.SelfForceIxTotal = 1; + todoDefault.SelfForceIxTotalAdj = 0; + todoDefault.SelfForceCx1 = 0; + todoDefault.SelfForceCx2 = 0; + todoDefault.SelfForceCx2Adj = 0; + todoDefault.SelfForceCxTotal = 0; + todoDefault.SelfForceCxTotalAdj = 0; + + todoDefault.SelfSenseRaw = 1; + todoDefault.SelfSenseRawGap = 0; + todoDefault.SelfSenseIx1 = 0; + todoDefault.SelfSenseIx2 = 0; + todoDefault.SelfSenseIx2Adj = 0; + todoDefault.SelfSenseIxTotal = 1; + todoDefault.SelfSenseIxTotalAdj = 0; + todoDefault.SelfSenseCx1 = 0; + todoDefault.SelfSenseCx2 = 0; + todoDefault.SelfSenseCx2Adj = 0; + todoDefault.SelfSenseCxTotal = 0; + todoDefault.SelfSenseCxTotalAdj = 0; + + if (numberParam < 1) { + logError(1, "%s NO COMMAND SPECIFIED!!! ", tag); + logError(1, "do: 'echo [cmd_code] [args] > stm_fts_cmd' "); + logError(1, "before looking for result!\n"); + res = ERROR_OP_NOT_ALLOW; + goto END; + } + + res = fts_disableInterrupt(); + if (res < 0) { + logError(0, "%s %s: ERROR %08X\n", tag, __func__, res); + res = (res | ERROR_DISABLE_INTER); + goto END; + } + switch (functionToTest[0]) { + case CMD_READ: + if (numberParam != 4) { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + break; + } + /** + * need to pass:cmdLength + * cmd[0]cmd[1]…cmd[cmdLength-1] + * byteToRead + */ + temp = (int)functionToTest[1]; + if (numberParam == 4 + (temp - 1) && temp != 0) { + cmd = (u8 *)kmalloc_array(temp, sizeof(u8), GFP_KERNEL); + if (!cmd) { + res = ERROR_OP_NOT_ALLOW; + break; + } + for (i = 0; i < temp; i++) + cmd[i] = functionToTest[i + 2]; + byteToRead = functionToTest[i + 2]; + readData = (u8 *)kmalloc_array(byteToRead, sizeof(u8), + GFP_KERNEL); + if (!readData) { + kfree(cmd); + res = ERROR_OP_NOT_ALLOW; + break; + } + res = fts_readCmd(cmd, temp, readData, byteToRead); + size += (byteToRead * sizeof(u8)) * 2; + kfree(cmd); + } else { + logError(1, "%s Wrong parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_WRITE: + if (numberParam != 3) { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + break; + } + /** + * need to pass:cmdLength + * cmd[0] cmd[1]…cmd[cmdLength-1] + */ + temp = (int)functionToTest[1]; + if (numberParam == 3 + (temp - 1) && temp != 0) { + cmd = (u8 *)kmalloc_array(temp, sizeof(u8), GFP_KERNEL); + if (!cmd) { + res = ERROR_OP_NOT_ALLOW; + break; + } + for (i = 0; i < temp; i++) + cmd[i] = functionToTest[i + 2]; + res = fts_writeCmd(cmd, temp); + kfree(cmd); + } else { + logError(1, "%s Wrong parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_FWWRITE: + if (numberParam != 3) { + logError(1, "%s Wrong number parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + break; + } + /** + * need to pass:cmdLength + * cmd[0] cmd[1]…cmd[cmdLength-1] + */ + temp = (int)functionToTest[1]; + if (numberParam == 3 + (temp - 1) && temp != 0) { + cmd = (u8 *)kmalloc_array(temp, sizeof(u8), GFP_KERNEL); + if (!cmd) { + res = ERROR_OP_NOT_ALLOW; + break; + } + for (i = 0; i < temp; i++) + cmd[i] = functionToTest[i + 2]; + res = fts_writeFwCmd(cmd, temp); + kfree(cmd); + } else { + logError(1, "%s Wrong parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_READU16: + if (numberParam != 6) { + logError(1, "%s Wrong number parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + break; + } + /** + * need to pass: cmd addr[0] addr[1] + * byteToRead hasDummyByte + */ + byteToRead = functionToTest[4]; + readData = kmalloc_array(byteToRead, + sizeof(u8), GFP_KERNEL); + if (!readData) { + res = ERROR_OP_NOT_ALLOW; + break; + } + res = readCmdU16((u8)functionToTest[1], + (u16)((((u8) functionToTest[2] + & 0x00FF) << 8) + ((u8) functionToTest[3] + & 0x00FF)), + readData, + byteToRead, + functionToTest[5]); + size += (byteToRead * sizeof(u8)) * 2; + break; + + case CMD_READB2: + if (numberParam != 4) { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + break; + } + /*need to pass: addr[0] addr[1] byteToRead*/ + byteToRead = functionToTest[3]; + readData = kmalloc_array(byteToRead, + sizeof(u8), GFP_KERNEL); + if (!readData) { + res = ERROR_OP_NOT_ALLOW; + break; + } + res = readB2((u16)( + (((u8)functionToTest[1] & 0x00FF) << 8) + + ((u8) functionToTest[2] & 0x00FF)), + readData, + byteToRead); + size += (byteToRead * sizeof(u8)) * 2; + break; + + case CMD_READB2U16: + if (numberParam != 4) { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + break; + } + /*need to pass: addr[0] addr[1] byteToRead*/ + byteToRead = functionToTest[3]; + readData = (u8 *)kmalloc_array(byteToRead, + sizeof(u8), GFP_KERNEL); + if (!readData) { + res = ERROR_OP_NOT_ALLOW; + break; + } + res = readB2U16((u16)((((u8)functionToTest[1] + & 0x00FF) << 8) + ((u8)functionToTest[2] + & 0x00FF)), readData, byteToRead); + size += (byteToRead * sizeof(u8)) * 2; + break; + + case CMD_POLLFOREVENT: + if (numberParam < 5) { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + break; + } + + /** + * need to pass: eventLength event[0] event[1] + * … event[eventLength-1] timeTowait + */ + temp = (int)functionToTest[1]; + if (numberParam == 5 + (temp - 1) && temp != 0) { + readData = (u8 *)kmalloc_array(FIFO_EVENT_SIZE, + sizeof(u8), GFP_KERNEL); + if (!readData) { + res = ERROR_OP_NOT_ALLOW; + break; + } + res = pollForEvent((int *)&functionToTest[2], + temp, + readData, + ((functionToTest[temp + 2] & 0x00FF) << 8) + + (functionToTest[temp + 3] & 0x00FF)); + //pollForEvent return the number of error found + if (res >= OK) + res = OK; + size += (FIFO_EVENT_SIZE * sizeof(u8)) * 2; + byteToRead = FIFO_EVENT_SIZE; + } else { + logError(1, "%s Wrong parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_SYSTEMRESET: + res = fts_system_reset(); + break; + + case CMD_READCHIPINFO: + if (numberParam != 2) { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + break; + } + /*need to pass: doRequest */ + res = readChipInfo(functionToTest[1]); + break; + + /* TOUCH ENABLE/DISABLE */ + case CMD_CLEANUP: + if (numberParam != 2) { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + break; + } + /* need to pass: enableTouch*/ + res = cleanUp(functionToTest[1]); + break; + + case CMD_GETFORCELEN: + /*read number Tx channels */ + temp = getForceLen(); + if (temp < OK) + res = temp; + else { + size += (1 * sizeof(u8)) * 2; + res = OK; + } + break; + + case CMD_GETSENSELEN: + /* read number Rx channels */ + temp = getSenseLen(); + if (temp < OK) + res = temp; + else { + size += (1 * sizeof(u8)) * 2; + res = OK; + } + break; + + case CMD_REQFRAME: + /* request a frame */ + if (numberParam != 3) { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + break; + } + logError(0, "%s Requesting Frame\n", tag); + res = requestFrame((u16)((((u8)functionToTest[1] & 0x00FF) << 8) + + ((u8)functionToTest[2] & 0x00FF))); + + if (res < OK) { + logError(0, "%s Err requesting frame ERROR:%02X\n", + tag, res); + } else { + logError(0, "%s Requesting Frame Finished!\n", tag); + } + break; + + case CMD_GETMSFRAME: + if (numberParam != 3) { + logError(1, "%s Wrong number of param!\n", tag); + res = ERROR_OP_NOT_ALLOW; + break; + } + logError(0, "%s Get 1 MS Frame\n", tag); + flushFIFO(); + /** + * delete the events related to some + * touch (allow to call this function + * while touching the sreen without + * having a flooding of the FIFO) + */ + res = getMSFrame2((u16)((((u8)functionToTest[1] & 0x00FF) << 8) + + ((u8)functionToTest[2] & 0x00FF)), &frameMS); + if (res < 0) { + logError(0, "%s Err while taking MS frame:%02X\n", + tag, res); + } else { + logError(0, "%s:frame size is %d words\n", tag, res); + size = (res * sizeof(short) + 8) * 2; + /*set res to OK because if getMSFrame is*/ + /*successful res = number of words read*/ + res = OK; + print_frame_short("MS frame =", + array1dTo2d_short(frameMS.node_data, + frameMS.node_data_size, + frameMS.header.sense_node), + frameMS.header.force_node, + frameMS.header.sense_node); + } + break; + + /*read self raw*/ + case CMD_GETSSFRAME: + if (numberParam != 3) { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + break; + } + + logError(0, "%s Get 1 SS Frame\n", tag); + flushFIFO(); + /** + * delete the events related to some + * touch (allow to call this function + * while touching the sreen without + * having a flooding of the FIFO) + */ + res = getSSFrame2((u16)((((u8)functionToTest[1] & 0x00FF) << 8) + + ((u8)functionToTest[2] & 0x00FF)), &frameSS); + + if (res < OK) { + logError(0, + "%s Error while taking the SS frame... ERROR %02X\n", + tag, res); + } else { + logError(0, "%s The frame size is %d words\n", + tag, res); + size = (res * sizeof(short) + 8) * 2 + 1; + + /*set res to OK because if getMSFrame is*/ + /*successful res = number of words read*/ + res = OK; + print_frame_short("SS force frame =", + array1dTo2d_short(frameSS.force_data, + frameSS.header.force_node, 1), + frameSS.header.force_node, + 1); + print_frame_short("SS sense frame =", + array1dTo2d_short(frameSS.sense_data, + frameSS.header.sense_node, + frameSS.header.sense_node), + 1, + frameSS.header.sense_node); + } + break; + + case CMD_REQCOMPDATA: + /*request comp data*/ + if (numberParam != 3) { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + break; + } + + logError(0, "%s Requesting Compensation Data\n", tag); + res = requestCompensationData((u16) + ((((u8)functionToTest[1] & 0x00FF) << 8) + + ((u8)functionToTest[2] & 0x00FF))); + + if (res < OK) { + logError(0, + "%s Error requesting compensation data ERROR %02X\n", + tag, res); + } else { + logError(0, + "%s Requesting Compensation Data Finished!\n", + tag); + } + break; + + case CMD_READCOMPDATAHEAD: + /*read comp data header*/ + if (numberParam != 3) { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + break; + } + + logError(0, "%s Requesting Compensation Data\n", tag); + res = requestCompensationData( + (u16) ((((u8)functionToTest[1] & 0x00FF) << 8) + + ((u8)functionToTest[2] & 0x00FF))); + if (res < OK) { + logError(0, "%s Error requesting:%02X\n", tag, res); + } else { + logError(0, + "%s Requesting Compensation Data Finished!\n", tag); + res = readCompensationDataHeader( + (u16)((((u8)functionToTest[1] & 0x00FF) << 8) + +((u8)functionToTest[2] & 0x00FF)), + &dataHead, + &address); + if (res < OK) { + logError(0, "%s Read Header ERROR:%02X\n", + tag, res); + } else { + logError(0, "%s Read Header OK!\n", tag); + size += (2 * sizeof(u8)) * 2; + } + } + break; + case CMD_READMSCOMPDATA: + if (numberParam != 3) { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + break; + } + /*read mutual comp data */ + logError(0, "%s Get MS Compensation Data\n", tag); + res = readMutualSenseCompensationData( + (u16)((((u8)functionToTest[1] & 0x00FF) << 8) + + ((u8)functionToTest[2] & 0x00FF)), + &compData); + + if (res < OK) { + logError(0, "%s Error reading MS compe data:%02X\n", + tag, res); + } else { + logError(0, "%s MS Compensa Reading Finished!\n", + tag); + + size = ((compData.node_data_size + 9) * sizeof(u8)) * 2; + print_frame_u8("MS Data (Cx2) = ", + array1dTo2d_u8(compData.node_data, + compData.node_data_size, + compData.header.sense_node), + compData.header.force_node, + compData.header.sense_node); + } + break; + case CMD_READSSCOMPDATA: + if (numberParam != 3) { + logError(1, "%sWrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + break; + } + + /*read self comp data*/ + logError(0, "%s Get SS Compensation Data...\n", tag); + res = readSelfSenseCompensationData((u16) + ((((u8)functionToTest[1] & 0x00FF) << 8) + + ((u8)functionToTest[2] & 0x00FF)), + &comData); + if (res < OK) { + logError(0, "%s Error reading SS Compensa data %02X\n", + tag, res); + } else { + logError(0, "%s SS Compensa Reading Finished!\n", tag); + size = comData.header.force_node + + comData.header.sense_node; + size = (size * 2 + 12) * sizeof(u8) * 2; + print_frame_u8("SS Data Ix2_fm = ", + array1dTo2d_u8(comData.ix2_fm, + comData.header.force_node, + comData.header.force_node), + 1, + comData.header.force_node); + print_frame_u8("SS Data Cx2_fm = ", + array1dTo2d_u8(comData.cx2_fm, + comData.header.force_node, + comData.header.force_node), + 1, + comData.header.force_node); + print_frame_u8("SS Data Ix2_sn = ", + array1dTo2d_u8(comData.ix2_sn, + comData.header.sense_node, + comData.header.sense_node), + 1, + comData.header.sense_node); + print_frame_u8("SS Data Cx2_sn = ", + array1dTo2d_u8(comData.cx2_sn, + comData.header.sense_node, + comData.header.sense_node), + 1, + comData.header.sense_node); + } + break; + + case CMD_READGNCOMPDATA: + if (numberParam != 3) { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + break; + } + /*read self comp data */ + logError(0, "%s Get General Compensation Data...\n", tag); + res = readGeneralCompensationData((u16) + ((((u8)functionToTest[1] + & 0x00FF) << 8) + ((u8)functionToTest[2] + & 0x00FF)), &gnData); + if (res < OK) { + logError(0, + "%s Reading General compensa data ERROR %02X\n", + tag, res); + } else { + logError(0, "%s:General compensa Reading Finished!\n", + tag); + size = (14) * sizeof(u8) * 2; + } + break; + case CMD_GETFWVER: + res = getFirmwareVersion(&fw_version, &config_id); + if (res < OK) { + logError(1, "%s Reading firmware version ERROR %02X\n", + tag, res); + } else { + logError(0, "%s getFirmware Version Finished!\n", tag); + size += (4) * sizeof(u8) * 2; + } + break; +#ifdef FTM3_CHIP + case CMD_FLASHSTATUS: + res = flash_status(); + /*return 0 = flash ready, 1 = flash busy, <0 error*/ + if (res < OK) { + logError(1, "%s Reading flash status ERROR %02X\n", + tag, res); + } else { + logError(0, "%s Flash Status: %d\n", tag, res); + size += (1 * sizeof(u8)) * 2; + /*need to store the value for further display */ + temp = res; + + /*set res =ok for returning code*/ + res = OK; + } + break; +#endif + case CMD_FLASHUNLOCK: + res = flash_unlock(); + if (res < OK) { + logError(1, "%s:Impossible Unlock Flash ERROR %02X\n", + tag, res); + } else { + logError(0, "%s Flash Unlock OK!\n", tag); + } + break; + case CMD_READFWFILE: + if (numberParam != 2) { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + break; + } + /*read fw file */ + logError(0, "%s Reading FW File...\n", tag); + res = readFwFile(PATH_FILE_FW, &fw, functionToTest[1]); + if (res < OK) { + logError(0, "%s Error reading FW File:%02X\n", + tag, res); + } else { + logError(0, "%s Read FW File Finished!\n", tag); + } + kfree(fw.data); + break; + case CMD_FLASHPROCEDURE: + if (numberParam != 3) { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + break; + } + /*flashing procedure*/ + logError(0, "%s Starting Flashing Procedure\n", tag); + res = flashProcedure(PATH_FILE_FW, + functionToTest[1], functionToTest[2]); + if (res < OK) { + logError(0, "%s During flash procedure ERROR %02X", + tag, res); + } else { + logError(0, "%s Flash Procedure Finished!\n", tag); + } + break; + + /*ITO TEST*/ + case CMD_ITOTEST: + res = production_test_ito(); + break; + + /*Initialization*/ + case CMD_INITTEST: + if (numberParam != 2) { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + break; + } + /*need to specify if if save value on Flash*/ + if (functionToTest[1] == 0x01) + res = production_test_initialization(); + else + res = production_test_split_initialization(false); + break; + + case CMD_MSRAWTEST: + if (numberParam != 2) { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + break; + } + /* MS Raw DATA TEST*/ + /* need to specify if stopOnFail */ + res = production_test_ms_raw(LIMITS_FILE, functionToTest[1], + &todoDefault); + break; + + case CMD_MSINITDATATEST: + /*MS CX DATA TEST*/ + if (numberParam != 2) { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + break; + } + /*need to specify if stopOnFail*/ + res = production_test_ms_cx(LIMITS_FILE, functionToTest[1], + &todoDefault); + break; + + case CMD_SSRAWTEST: + /*SS RAW DATA TEST*/ + if (numberParam != 2) { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + break; + } + /*need to specify if stopOnFail*/ + res = production_test_ss_raw(LIMITS_FILE, functionToTest[1], + &todoDefault); + break; + + case CMD_SSINITDATATEST: + /*SS IX CX DATA TEST*/ + if (numberParam != 2) { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + break; + } + /*need to specify if stopOnFail*/ + res = production_test_ss_ix_cx(LIMITS_FILE, functionToTest[1], + &todoDefault); + break; + + case CMD_MAINTEST: + /*PRODUCTION TEST*/ + if (numberParam != 3) { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + break; + } + /*need to specify if stopOnFail and saveInit*/ + res = production_test_main(LIMITS_FILE, functionToTest[1], + functionToTest[2], &todoDefault, INIT_FIELD); + break; + + case CMD_POWERCYCLE: + res = fts_chip_powercycle(info); + break; + + default: + logError(1, "%s COMMAND ID NOT VALID!!\n", tag); + logError(1, "%s Inset a value between 00 and 1E.\n", tag); + res = ERROR_OP_NOT_ALLOW; + break; + } + +END: + /** + * here start the reporting phase, + * assembling the data to send in the file node + */ + all_strbuff = kmalloc(size, GFP_KERNEL); + memset(all_strbuff, 0, size); + + snprintf(buff, sizeof(buff), "%02X", 0xAA); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%08X", res); + strlcat(all_strbuff, buff, size); + + if (res >= OK) { + /*all the other cases are already*/ + /*fine printing only the res.*/ + switch (functionToTest[0]) { + case CMD_READ: + case CMD_READU16: + case CMD_READB2: + case CMD_READB2U16: + case CMD_POLLFOREVENT: + for (j = 0; j < byteToRead; j++) { + snprintf(buff, sizeof(buff), "%02X", + readData[j]); + strlcat(all_strbuff, buff, size); + } + break; + + case CMD_GETFORCELEN: + case CMD_GETSENSELEN: + case CMD_FLASHSTATUS: + snprintf(buff, sizeof(buff), "%02X", (u8)temp); + strlcat(all_strbuff, buff, size); + break; + + case CMD_GETMSFRAME: + snprintf(buff, sizeof(buff), "%02X", + (u8) frameMS.header.force_node); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", + (u8)frameMS.header.sense_node); + strlcat(all_strbuff, buff, size); + + for (j = 0; j < frameMS.node_data_size; j++) { + snprintf(buff, sizeof(buff), "%04X", + frameMS.node_data[j]); + strlcat(all_strbuff, buff, size); + } + kfree(frameMS.node_data); + break; + + case CMD_GETSSFRAME: + snprintf(buff, sizeof(buff), "%02X", + (u8) frameSS.header.force_node); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", + (u8)frameSS.header.sense_node); + strlcat(all_strbuff, buff, size); + + /* Copying self raw data Force */ + for (j = 0; j < frameSS.header.force_node; j++) { + snprintf(buff, sizeof(buff), "%04X", + frameSS.force_data[j]); + strlcat(all_strbuff, buff, size); + } + + + /* Copying self raw data Sense */ + for (j = 0; j < frameSS.header.sense_node; j++) { + snprintf(buff, sizeof(buff), "%04X", + frameSS.sense_data[j]); + strlcat(all_strbuff, buff, size); + } + kfree(frameSS.force_data); + kfree(frameSS.sense_data); + break; + + case CMD_READMSCOMPDATA: + snprintf(buff, sizeof(buff), "%02X", + (u8)compData.header.force_node); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", + (u8)compData.header.sense_node); + strlcat(all_strbuff, buff, size); + + /* Cpying CX1 value */ + snprintf(buff, sizeof(buff), "%02X", + compData.cx1); + strlcat(all_strbuff, buff, size); + + /* Copying CX2 values */ + for (j = 0; j < compData.node_data_size; j++) { + snprintf(buff, sizeof(buff), "%02X", + *(compData.node_data + j)); + strlcat(all_strbuff, buff, size); + } + kfree(compData.node_data); + break; + + case CMD_READSSCOMPDATA: + snprintf(buff, sizeof(buff), "%02X", + comData.header.force_node); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", + comData.header.sense_node); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", comData.f_ix1); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", comData.s_ix1); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", comData.f_cx1); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", comData.s_cx1); + strlcat(all_strbuff, buff, size); + + /* Copying IX2 Force */ + for (j = 0; j < comData.header.force_node; j++) { + snprintf(buff, sizeof(buff), "%02X", + comData.ix2_fm[j]); + strlcat(all_strbuff, buff, size); + } + /* Copying IX2 Sense*/ + for (j = 0; j < comData.header.sense_node; j++) { + snprintf(buff, sizeof(buff), "%02X", + comData.ix2_sn[j]); + strlcat(all_strbuff, buff, size); + } + /* Copying CX2 Force */ + for (j = 0; j < comData.header.force_node; j++) { + snprintf(buff, sizeof(buff), "%02X", + comData.cx2_fm[j]); + strlcat(all_strbuff, buff, size); + } + + /* Copying CX2 Sense */ + for (j = 0; j < comData.header.sense_node; j++) { + snprintf(buff, sizeof(buff), "%02X", + comData.cx2_sn[j]); + strlcat(all_strbuff, buff, size); + } + + kfree(comData.ix2_fm); + kfree(comData.ix2_sn); + kfree(comData.cx2_fm); + kfree(comData.cx2_sn); + break; + + case CMD_READGNCOMPDATA: + snprintf(buff, sizeof(buff), "%02X", + gnData.header.force_node); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", + gnData.header.sense_node); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", + gnData.ftsd_lp_timer_cal0); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", + gnData.ftsd_lp_timer_cal1); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", + gnData.ftsd_lp_timer_cal2); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", + gnData.ftsd_lp_timer_cal3); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", + gnData.ftsa_lp_timer_cal0); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", + gnData.ftsa_lp_timer_cal1); + strlcat(all_strbuff, buff, size); + break; + + case CMD_GETFWVER: + snprintf(buff, sizeof(buff), "%04X", fw_version); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%04X", config_id); + strlcat(all_strbuff, buff, size); + break; + + case CMD_READCOMPDATAHEAD: + snprintf(buff, sizeof(buff), "%02X", + dataHead.force_node); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", + dataHead.sense_node); + strlcat(all_strbuff, buff, size); + break; + + default: + break; + } + } + + snprintf(buff, sizeof(buff), "%02X", 0xBB); + strlcat(all_strbuff, buff, size); + + count = snprintf(buf, TSP_BUF_SIZE, "%s\n", all_strbuff); + numberParam = 0; + /** + * need to reset the number of parameters + * in order to wait the next command, + * comment if you want to repeat + * the last command sent just doing a cat + */ + + kfree(readData); + kfree(all_strbuff); + return count; +} + +static DEVICE_ATTR_RW(stm_driver_test); + +static struct attribute *test_cmd_attributes[] = { + &dev_attr_stm_driver_test.attr, + NULL, +}; + +struct attribute_group test_cmd_attr_group = { + .attrs = test_cmd_attributes, +}; +#endif diff --git a/qcom/opensource/touch-drivers/st/fts_gui.c b/qcom/opensource/touch-drivers/st/fts_gui.c new file mode 100644 index 0000000000..4adec7d96e --- /dev/null +++ b/qcom/opensource/touch-drivers/st/fts_gui.c @@ -0,0 +1,412 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * FTS Capacitive touch screen controller (FingerTipS) + * + * Copyright (C) 2016-2019, STMicroelectronics Limited. + * Authors: AMG(Analog Mems Group) + * + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include +#include +#include +#include +#include +#include "fts.h" +#include "fts_lib/ftsIO.h" + +#ifdef SCRIPTLESS + +static unsigned int fts_data[CMD_RESULT_STR_LEN] = {0}; +static unsigned char fts_pAddress_i2c[CMD_RESULT_STR_LEN] = {0}; +static int byte_count_read; +static char Out_buff[TSP_BUF_SIZE]; + + +/*I2C CMd functions: functions to interface with GUI without script */ + +ssize_t fts_i2c_wr_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + int i; + char buff[16]; + + memset(Out_buff, 0x00, sizeof(Out_buff)); + if (byte_count_read == 0) { + snprintf(Out_buff, sizeof(Out_buff), "{FAILED}\n"); + return snprintf(buf, TSP_BUF_SIZE, "%s\n", Out_buff); + } +#ifdef SCRIPTLESS_DEBUG + pr_err("%s:DATA READ {\n", __func__); + for (i = 0; i < byte_count_read; i++) { + pr_err(" %02X\n", (unsigned int)info->cmd_wr_result[i]); + if (i < (byte_count_read - 1)) + pr_err("\n"); + } + pr_err("}\n"); +#endif + snprintf(buff, sizeof(buff), "{"); + strlcat(Out_buff, buff, sizeof(Out_buff)); + for (i = 0; i < (byte_count_read + 2); i++) { + char temp_byte_count_read; + + if (i == 0) { + temp_byte_count_read = (byte_count_read >> 8) & 0xFF; + + snprintf(buff, sizeof(buff), "%02X", + temp_byte_count_read); + } else if (i == 1) { + temp_byte_count_read = (byte_count_read) & 0xFF; + + snprintf(buff, sizeof(buff), "%02X", + temp_byte_count_read); + + } else { + snprintf(buff, sizeof(buff), "%02X", + info->cmd_wr_result[i-2]); + } + //snprintf(buff, sizeof(buff), "%02X", info->cmd_wr_result[i]); + strlcat(Out_buff, buff, sizeof(Out_buff)); + if (i < (byte_count_read + 1)) { + snprintf(buff, sizeof(buff), " "); + strlcat(Out_buff, buff, sizeof(Out_buff)); + } + } + snprintf(buff, sizeof(buff), "}"); + strlcat(Out_buff, buff, sizeof(Out_buff)); + return snprintf(buf, TSP_BUF_SIZE, "%s\n", Out_buff); +} + +ssize_t fts_i2c_wr_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + unsigned char pAddress[9]; + unsigned int byte_count = 0; + int i; + unsigned int data[9]; + + memset(data, 0x00, sizeof(data)); + memset(pAddress, 0x00, sizeof(pAddress)); + memset(info->cmd_wr_result, 0x00, CMD_RESULT_STR_LEN); + ret = sscanf(buf, "%x %x %x %x %x %x %x %x %x ", + (data + 8), (data), (data + 1), (data + 2), (data + 3), + (data + 4), (data + 5), (data + 6), (data + 7)); + + byte_count = data[8]; + + /** + * if(sizeof(buf) != byte_count ) + * { + * printk("%s : Byte count is wrong\n",__func__); + * return count; + * } + */ + + if (byte_count > sizeof(pAddress)) + return -EINVAL; +#ifdef SCRIPTLESS_DEBUG + + pr_err("%s: Input Data 1:\n", __func__); + + for (i = 0 ; i < byte_count; i++) { + pr_err(" %02X\n", data[i]); + pAddress[i] = (unsigned char)data[i]; + } + pr_err("\n"); +#else + for (i = 0 ; i < byte_count; i++) + pAddress[i] = (unsigned char)data[i]; +#endif + byte_count_read = (((unsigned int)data[byte_count - 2]) << 8) + | data[byte_count - 1]; + ret = fts_writeCmd(pAddress, 3); + msleep(20); + ret = fts_readCmd(&pAddress[3], (byte_count - 5), + info->cmd_wr_result, byte_count_read); +#ifdef SCRIPTLESS_DEBUG + pr_err("%s:DATA READ {\n", __func__); + for (i = 0; i < (2 + byte_count_read); i++) { + char temp_byte_count_read; + + if (i == 0) { + temp_byte_count_read = (byte_count_read >> 8) & 0xFF; + pr_err("%02X\n", (unsigned int)temp_byte_count_read); + } else if (i == 1) { + temp_byte_count_read = (byte_count_read) & 0xFF; + pr_err("%02X\n", (unsigned int)temp_byte_count_read); + + } else { + pr_err("%02X\n", + (unsigned int)info->cmd_read_result[i - 2]); + } + + if (i < (byte_count_read + 1)) + pr_err("\n"); + + } + pr_err("}\n"); +#endif + if (ret) + dev_err(dev, "Unable to read register\n"); + return count; +} + +ssize_t fts_i2c_read_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + int i; + char buff[16]; + + memset(Out_buff, 0x00, sizeof(Out_buff)); + if (byte_count_read == 0) { + snprintf(Out_buff, sizeof(Out_buff), "{FAILED}"); + return snprintf(buf, TSP_BUF_SIZE, "%s\n", Out_buff); + } +#ifdef SCRIPTLESS_DEBUG + pr_err("%s:DATA READ {\n", __func__); + for (i = 0; i < byte_count_read; i++) { + pr_err("%02X\n", (unsigned int)info->cmd_read_result[i]); + if (i < (byte_count_read - 1)) + pr_err("\n"); + } + pr_err("}\n"); +#endif + snprintf(buff, sizeof(buff), "{"); + strlcat(Out_buff, buff, sizeof(Out_buff)); + for (i = 0; i < (byte_count_read + 2); i++) { + char temp_byte_count_read; + + if (i == 0) { + temp_byte_count_read = (byte_count_read >> 8) & 0xFF; + snprintf(buff, sizeof(buff), "%02X", + temp_byte_count_read); + } else if (i == 1) { + temp_byte_count_read = (byte_count_read) & 0xFF; + snprintf(buff, sizeof(buff), "%02X", + temp_byte_count_read); + } else { + snprintf(buff, sizeof(buff), "%02X", + info->cmd_read_result[i - 2]); + } + strlcat(Out_buff, buff, sizeof(Out_buff)); + if (i < (byte_count_read + 1)) { + snprintf(buff, sizeof(buff), " "); + strlcat(Out_buff, buff, sizeof(Out_buff)); + } + } + snprintf(buff, sizeof(buff), "}"); + strlcat(Out_buff, buff, sizeof(Out_buff)); + + return snprintf(buf, TSP_BUF_SIZE, "%s\n", Out_buff); +} + +ssize_t fts_i2c_read_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + unsigned char pAddress[9]; + unsigned int byte_count = 0; + int i; + unsigned int data[9]; + + byte_count_read = 0; + memset(data, 0x00, sizeof(data)); + memset(pAddress, 0x00, sizeof(pAddress)); + memset(info->cmd_read_result, 0x00, CMD_RESULT_STR_LEN); + ret = sscanf(buf, "%x %x %x %x %x %x %x %x %x ", + (data + 8), (data), (data + 1), (data + 2), (data + 3), + (data + 4), (data + 5), (data + 6), (data + 7)); + + byte_count = data[8]; + + if (byte_count > 8) { +#ifdef SCRIPTLESS_DEBUG + pr_err("%s:Byte count is more than 8\n", __func__); +#endif + return count; + } + /*if(sizeof(buf) != byte_count )*/ + /*{*/ + /* printk("%s : Byte count is wrong\n",__func__);*/ + /* return count;*/ + /*}*/ +#ifdef SCRIPTLESS_DEBUG + pr_err("%s: Input Data 1:\n", __func__); + for (i = 0 ; i < byte_count; i++) { + pr_err("%02X\n", data[i]); + pAddress[i] = (unsigned char)data[i]; + } + pr_err("\n"); +#else + for (i = 0 ; i < byte_count; i++) + pAddress[i] = (unsigned char)data[i]; +#endif + byte_count_read = (((unsigned int)data[byte_count - 2]) << 8) + | data[byte_count - 1]; + ret = fts_readCmd(pAddress, (byte_count - 2), info->cmd_read_result, + byte_count_read); +#ifdef SCRIPTLESS_DEBUG + pr_err("%s:DATA READ\n{\n", __func__); + for (i = 0; i < (byte_count_read + 2); i++) { + char temp_byte_count_read; + + if (i == 0) { + temp_byte_count_read = (byte_count_read >> 8) & 0xFF; + + pr_err("%02X\n", (unsigned int)temp_byte_count_read); + } else if (i == 1) { + temp_byte_count_read = (byte_count_read) & 0xFF; + + pr_err("%02X\n", (unsigned int)temp_byte_count_read); + + } else { + pr_err("%02X\n", + (unsigned int)info->cmd_read_result[i - 2]); + } + if (i < (byte_count_read + 1)) + pr_err("\n"); + } + pr_err("}\n"); +#endif + if (ret) + dev_err(dev, "Unable to read register\n"); + return count; +} + + +ssize_t fts_i2c_write_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + + return snprintf(buf, TSP_BUF_SIZE, "%s", info->cmd_write_result); + +} + +ssize_t fts_i2c_write_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + unsigned int byte_count = 0; + int i; + unsigned int *data = &fts_data[0]; + + memset(fts_data, 0x00, sizeof(fts_data)); + memset(fts_pAddress_i2c, 0x00, sizeof(fts_pAddress_i2c)); + memset(info->cmd_write_result, 0x00, sizeof(info->cmd_write_result)); + ret = sscanf(buf, "%x %x", data, (data + 1)); + if (ret != 2) + return -EINVAL; + byte_count = data[0] << 8 | data[1]; + + if (byte_count <= sizeof(fts_pAddress_i2c)) { + for (i = 0; i < (byte_count); i++) { + ret = sscanf(&buf[3 * (i + 2)], "%x ", (data + i)); + if (ret != 1) + return -EINVAL; + } + } else { +#ifdef SCRIPTLESS_DEBUG + pr_err("%s:message size is > allowed limit of 512 bytes\n", + __func__); +#endif + snprintf(info->cmd_write_result, sizeof(info->cmd_write_result), + "{Write NOT OK}\n"); + return -EINVAL; + } +#ifdef SCRIPTLESS_DEBUG + pr_err("\n"); + pr_err("%s:Byte_count = %02d|Count = %02d |size of buf:%02d\n", + __func__, byte_count, (int)count, (int)sizeof(buf)); + pr_err("%s: Input Data 1:\n", __func__); + for (i = 0 ; i < byte_count; i++) { + pr_err(" %02X\n", data[i]); + fts_pAddress_i2c[i] = (unsigned char)data[i]; + } + pr_err("\n"); +#else + for (i = 0 ; i < byte_count; i++) + fts_pAddress_i2c[i] = (unsigned char)data[i]; +#endif + if ((fts_pAddress_i2c[0] == 0xb3) && (fts_pAddress_i2c[3] == 0xb1)) { + ret = fts_writeCmd(fts_pAddress_i2c, 3); + msleep(20); + ret = fts_writeCmd(&fts_pAddress_i2c[3], byte_count-3); + } else + ret = fts_writeCmd(fts_pAddress_i2c, byte_count); + +#ifdef SCRIPTLESS_DEBUG + pr_err("%s:DATA :\n", __func__); + for (i = 0; i < byte_count; i++) + pr_err(" %02X\n", (unsigned int)fts_pAddress_i2c[i]); + pr_err(" byte_count: %02X\n", byte_count); +#endif + if (ret < 0) { + dev_err(dev, "{Write NOT OK}\n"); + snprintf(info->cmd_write_result, sizeof(info->cmd_write_result), + "{Write NOT OK}\n"); + } else { + snprintf(info->cmd_write_result, sizeof(info->cmd_write_result), + "{Write OK}\n"); +#ifdef SCRIPTLESS_DEBUG + pr_err("%s : {Write OK}\n", __func__); +#endif + } + return count; +} + +static DEVICE_ATTR_RW(fts_i2c_read); +static DEVICE_ATTR_RW(fts_i2c_wr); +static DEVICE_ATTR_RW(fts_i2c_write); + +static struct attribute *i2c_cmd_attributes[] = { + &dev_attr_fts_i2c_read.attr, + &dev_attr_fts_i2c_wr.attr, + &dev_attr_fts_i2c_write.attr, + NULL, +}; + +struct attribute_group i2c_cmd_attr_group = { + .attrs = i2c_cmd_attributes, +}; + + #endif diff --git a/qcom/opensource/touch-drivers/st/fts_lib/ftsCompensation.c b/qcom/opensource/touch-drivers/st/fts_lib/ftsCompensation.c new file mode 100644 index 0000000000..2bffaa9060 --- /dev/null +++ b/qcom/opensource/touch-drivers/st/fts_lib/ftsCompensation.c @@ -0,0 +1,744 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * FTS Capacitive touch screen controller (FingerTipS) + * + * Copyright (C) 2016-2019, STMicroelectronics Limited. + * Authors: AMG(Analog Mems Group) + * + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +/** + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS functions for getting Initialization Data * + * * + ************************************************************************** + ************************************************************************** + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include + +#include "ftsCrossCompile.h" +#include "ftsCompensation.h" +#include "ftsError.h" +#include "ftsFrame.h" +#include "ftsHardware.h" +#include "ftsIO.h" +#include "ftsSoftware.h" +#include "ftsTool.h" + + +static char tag[8] = "[ FTS ]\0"; +struct chipInfo ftsInfo; + +int requestCompensationData(u16 type) +{ + int retry = 0; + int ret; + char *temp = NULL; + u16 answer; + + int event_to_search[3]; + u8 readEvent[FIFO_EVENT_SIZE]; + + u8 cmd[3] = { FTS_CMD_REQU_COMP_DATA, 0x00, 0x00}; + /* B8 is the command for asking compensation data*/ + u16ToU8(type, &cmd[1]); + + event_to_search[0] = (int)EVENTID_COMP_DATA_READ; + event_to_search[1] = cmd[1]; + event_to_search[2] = cmd[2]; + + while (retry < COMP_DATA_READ_RETRY) { + temp = printHex("Command = ", cmd, 3); + if (temp != NULL) + logError(0, "%s %s", tag, temp); + kfree(temp); + ret = fts_writeFwCmd(cmd, 3); + /*send the request to the chip to load*/ + /*in memory the Compensation Data*/ + if (ret < OK) { + logError(1, "%s %s:ERROR %02X\n", + tag, __func__, ERROR_I2C_W); + return ERROR_I2C_W; + } + ret = pollForEvent(event_to_search, 3, readEvent, + TIMEOUT_REQU_COMP_DATA); + if (ret < OK) { + logError(0, "%s Event did not Found at %d attemp!\n", + tag, retry + 1); + retry += 1; + } else { + retry = 0; + break; + } + } + + if (retry == COMP_DATA_READ_RETRY) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_TIMEOUT); + return ERROR_TIMEOUT; + } + + u8ToU16_le(&readEvent[1], &answer); + + if (answer == type) + return OK; + + logError(1, "%sThe event found has a different type of ", tag); + logError(1, "Compensation data %02X\n", ERROR_DIFF_COMP_TYPE); + return ERROR_DIFF_COMP_TYPE; + +} + +int readCompensationDataHeader(u16 type, struct DataHeader *header, + u16 *address) +{ + u16 offset = ADDR_FRAMEBUFFER_DATA; + u16 answer; + u8 data[COMP_DATA_HEADER]; + + if (readCmdU16(FTS_CMD_FRAMEBUFFER_R, offset, data, COMP_DATA_HEADER, + DUMMY_FRAMEBUFFER) < 0) { + logError(1, "%s %s: ERROR %02X\n", tag, __func__, ERROR_I2C_R); + return ERROR_I2C_R; + } + logError(0, "%s Read Data Header done!\n", tag); + + if (data[0] != HEADER_SIGNATURE) { + logError(1, "%s %s:%02X The Header Signature was wrong!", + tag, __func__, ERROR_WRONG_COMP_SIGN); + logError(1, "%02X != %02X\n", data[0], HEADER_SIGNATURE); + + return ERROR_WRONG_COMP_SIGN; + } + + + u8ToU16_le(&data[1], &answer); + + if (answer != type) { + logError(1, "%s %s:ERROR %02X\n", + tag, __func__, ERROR_DIFF_COMP_TYPE); + + return ERROR_DIFF_COMP_TYPE; + } + + logError(0, "%s Type of Compensation data OK!\n", tag); + + header->type = type; + header->force_node = (int)data[4]; + header->sense_node = (int)data[5]; + + *address = offset + COMP_DATA_HEADER; + + return OK; + +} + +int readMutualSenseGlobalData(u16 *address, struct MutualSenseData *global) +{ + + u8 data[COMP_DATA_GLOBAL]; + + logError(0, "%s Address for Global data= %02X\n", tag, *address); + + if (readCmdU16(FTS_CMD_FRAMEBUFFER_R, *address, data, + COMP_DATA_GLOBAL, DUMMY_FRAMEBUFFER) < 0) { + logError(1, "%s %s: ERROR %02X\n", tag, __func__, ERROR_I2C_R); + + return ERROR_I2C_R; + } + logError(0, "%s Global data Read!\n", tag); + + global->tuning_ver = data[0]; + global->cx1 = data[1]; + + logError(0, "%s tuning_ver = %d CX1 = %d\n", + tag, global->tuning_ver, global->cx1); + + *address += COMP_DATA_GLOBAL; + return OK; +} + + + +int readMutualSenseNodeData(u16 address, struct MutualSenseData *node) +{ + int size = node->header.force_node*node->header.sense_node; + + logError(0, "%s Address for Node data = %02X\n", tag, address); + + node->node_data = (u8 *)kmalloc_array(size, (sizeof(u8)), GFP_KERNEL); + + if (node->node_data == NULL) { + logError(1, "%s %s: ERROR %02X", tag, __func__, ERROR_ALLOC); + return ERROR_ALLOC; + } + + logError(0, "%s Node Data to read %d bytes\n", tag, size); + + if (readCmdU16(FTS_CMD_FRAMEBUFFER_R, address, node->node_data, + size, DUMMY_FRAMEBUFFER) < 0) { + logError(1, "%s %s:ERROR %02X\n", tag, __func__, ERROR_I2C_R); + kfree(node->node_data); + return ERROR_I2C_R; + } + node->node_data_size = size; + + logError(0, "%s Read node data ok!\n", tag); + + return size; +} + + +int readMutualSenseCompensationData(u16 type, struct MutualSenseData *data) +{ + int ret; + u16 address; + + data->node_data = NULL; + + if (!(type == MS_TOUCH_ACTIVE || type == MS_TOUCH_LOW_POWER + || type == MS_TOUCH_ULTRA_LOW_POWER || type == MS_KEY)) { + logError(1, "%s %s: Choose a MS type of compensation data ", + tag, __func__); + logError(1, "ERROR %02X\n", ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + ret = requestCompensationData(type); + if (ret < 0) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_REQU_COMP_DATA); + return (ret|ERROR_REQU_COMP_DATA); + } + + ret = readCompensationDataHeader(type, &(data->header), &address); + if (ret < 0) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_COMP_DATA_HEADER); + return (ret | ERROR_COMP_DATA_HEADER); + } + + ret = readMutualSenseGlobalData(&address, data); + if (ret < 0) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_COMP_DATA_GLOBAL); + return (ret|ERROR_COMP_DATA_GLOBAL); + } + + ret = readMutualSenseNodeData(address, data); + if (ret < 0) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_COMP_DATA_NODE); + return (ret | ERROR_COMP_DATA_NODE); + } + + return OK; +} + + +int readSelfSenseGlobalData(u16 *address, struct SelfSenseData *global) +{ + u8 data[COMP_DATA_GLOBAL]; + + logError(0, "%s Address for Global data= %02X\n", tag, *address); + + if (readCmdU16(FTS_CMD_FRAMEBUFFER_R, *address, data, + COMP_DATA_GLOBAL, DUMMY_FRAMEBUFFER) < 0) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_I2C_R); + return ERROR_I2C_R; + } + + logError(0, "%s Global data Read!\n", tag); + + global->tuning_ver = data[0]; + global->f_ix1 = data[1]; + global->s_ix1 = data[2]; + global->f_cx1 = data[3]; + global->s_cx1 = data[4]; + global->f_max_n = data[5]; + global->s_max_n = data[6]; + + logError(0, + "%stuning_ver = %df_ix1 = %ds_ix1 = %df_cx1 = %d s_cx1 = %d\n", + tag, global->tuning_ver, global->f_ix1, + global->s_ix1, global->f_cx1, global->s_cx1); + logError(0, "%s max_n = %d s_max_n = %d\n", + tag, global->f_max_n, global->s_max_n); + + *address += COMP_DATA_GLOBAL; + return OK; +} + +int readSelfSenseNodeData(u16 address, struct SelfSenseData *node) +{ + int size = node->header.force_node * 2 + node->header.sense_node * 2; + u8 *data; + + node->ix2_fm = (u8 *)kmalloc_array(node->header.force_node, + sizeof(u8), GFP_KERNEL); + if (node->ix2_fm == NULL) { + logError(1, "%s %s: ERROR %02X", tag, __func__, ERROR_ALLOC); + return ERROR_ALLOC; + } + + node->cx2_fm = (u8 *)kmalloc_array(node->header.force_node, + sizeof(u8), GFP_KERNEL); + if (node->cx2_fm == NULL) { + logError(1, "%s %s: ERROR %02X", tag, __func__, ERROR_ALLOC); + kfree(node->ix2_fm); + return ERROR_ALLOC; + } + node->ix2_sn = (u8 *)kmalloc_array(node->header.sense_node, + sizeof(u8), GFP_KERNEL); + if (node->ix2_sn == NULL) { + logError(1, "%s %s: ERROR %02X", tag, __func__, ERROR_ALLOC); + kfree(node->ix2_fm); + kfree(node->cx2_fm); + return ERROR_ALLOC; + } + node->cx2_sn = (u8 *)kmalloc_array(node->header.sense_node, + sizeof(u8), GFP_KERNEL); + if (node->cx2_sn == NULL) { + logError(1, "%s %s: ERROR %02X", tag, __func__, ERROR_ALLOC); + kfree(node->ix2_fm); + kfree(node->cx2_fm); + kfree(node->ix2_sn); + return ERROR_ALLOC; + } + + logError(0, "%s Address for Node data = %02X\n", tag, address); + + logError(0, "%s Node Data to read %d bytes\n", tag, size); + + data = (u8 *)kmalloc_array(size, sizeof(u8), GFP_KERNEL); + if (data == NULL) { + logError(1, "%s %s: ERROR %02X", tag, __func__, ERROR_ALLOC); + kfree(node->ix2_fm); + kfree(node->cx2_fm); + kfree(node->ix2_sn); + kfree(node->cx2_sn); + return ERROR_ALLOC; + } + + if (readCmdU16(FTS_CMD_FRAMEBUFFER_R, address, data, size, + DUMMY_FRAMEBUFFER) < 0) { + logError(1, "%s %s: ERROR %02X\n", tag, __func__, ERROR_I2C_R); + kfree(node->ix2_fm); + kfree(node->cx2_fm); + kfree(node->ix2_sn); + kfree(node->cx2_sn); + kfree(data); + return ERROR_I2C_R; + } + + logError(0, "%s Read node data ok!\n", tag); + + memcpy(node->ix2_fm, data, node->header.force_node); + memcpy(node->ix2_sn, &data[node->header.force_node], + node->header.sense_node); + memcpy(node->cx2_fm, + &data[node->header.force_node + node->header.sense_node], + node->header.force_node); + memcpy(node->cx2_sn, + &data[node->header.force_node * 2 + node->header.sense_node], + node->header.sense_node); + + kfree(data); + + return OK; +} + +int readSelfSenseCompensationData(u16 type, struct SelfSenseData *data) +{ + + int ret; + u16 address; + + data->ix2_fm = NULL; + data->cx2_fm = NULL; + data->ix2_sn = NULL; + data->cx2_sn = NULL; + + if (!(type == SS_TOUCH || type == SS_KEY || type == SS_HOVER + || type == SS_PROXIMITY)) { + logError(1, "%s %s:Choose a SS type of compensation data ", + tag, __func__); + logError(1, "ERROR %02X\n", ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + ret = requestCompensationData(type); + if (ret < 0) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_REQU_COMP_DATA); + return (ret | ERROR_REQU_COMP_DATA); + } + + ret = readCompensationDataHeader(type, &(data->header), &address); + if (ret < 0) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_COMP_DATA_HEADER); + return (ret|ERROR_COMP_DATA_HEADER); + } + + ret = readSelfSenseGlobalData(&address, data); + if (ret < 0) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_COMP_DATA_GLOBAL); + return (ret | ERROR_COMP_DATA_GLOBAL); + } + + ret = readSelfSenseNodeData(address, data); + if (ret < 0) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_COMP_DATA_NODE); + return (ret | ERROR_COMP_DATA_NODE); + } + + return OK; +} + + +int readGeneralGlobalData(u16 address, struct GeneralData *global) +{ + u8 data[COMP_DATA_GLOBAL]; + + if (readCmdU16(FTS_CMD_FRAMEBUFFER_R, address, data, COMP_DATA_GLOBAL, + DUMMY_FRAMEBUFFER) < 0) { + logError(1, "%s %s: ERROR %02X\n", tag, __func__, ERROR_I2C_R); + return ERROR_I2C_R; + } + + global->ftsd_lp_timer_cal0 = data[0]; + global->ftsd_lp_timer_cal1 = data[1]; + global->ftsd_lp_timer_cal2 = data[2]; + global->ftsd_lp_timer_cal3 = data[3]; + global->ftsa_lp_timer_cal0 = data[4]; + global->ftsa_lp_timer_cal1 = data[5]; + + return OK; +} + + +int readGeneralCompensationData(u16 type, struct GeneralData *data) +{ + int ret; + u16 address; + + if (!(type == GENERAL_TUNING)) { + logError(1, "%s %s:Choose a GENERAL type of compensation data ", + tag); + logError(1, "ERROR %02X\n", ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + ret = requestCompensationData(type); + if (ret < 0) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_REQU_COMP_DATA); + return ERROR_REQU_COMP_DATA; + } + + ret = readCompensationDataHeader(type, &(data->header), &address); + if (ret < 0) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_COMP_DATA_HEADER); + return ERROR_COMP_DATA_HEADER; + } + + ret = readGeneralGlobalData(address, data); + if (ret < 0) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_COMP_DATA_GLOBAL); + return ERROR_COMP_DATA_GLOBAL; + } + + return OK; + +} + + +int defaultChipInfo(int i2cError) +{ + int i; + + logError(0, "%s Setting default Chip Info...\n", tag); + ftsInfo.u32_echoEn = 0x00000000; + ftsInfo.u8_msScrConfigTuneVer = 0; + ftsInfo.u8_ssTchConfigTuneVer = 0; + ftsInfo.u8_msScrCxmemTuneVer = 0; + ftsInfo.u8_ssTchCxmemTuneVer = 0; + if (i2cError == 1) { + ftsInfo.u16_fwVer = 0xFFFF; + ftsInfo.u16_cfgId = 0xFFFF; + for (i = 0; i < EXTERNAL_RELEASE_INFO_SIZE; i++) + ftsInfo.u8_extReleaseInfo[i] = 0xFF; + } else { + ftsInfo.u16_fwVer = 0x0000; + ftsInfo.u16_cfgId = 0x0000; + for (i = 0; i < EXTERNAL_RELEASE_INFO_SIZE; i++) + ftsInfo.u8_extReleaseInfo[i] = 0x00; + } + ftsInfo.u32_mpPassFlag = INIT_FIELD; + ftsInfo.u16_errOffset = INVALID_ERROR_OFFS; + logError(0, "%s default Chip Info DONE!\n", tag); + return OK; +} + +int readChipInfo(int doRequest) +{ + int ret, i; + u16 answer; + u8 data[CHIP_INFO_SIZE + 3]; + /*+3 because need to read all the field of*/ + /*the struct plus the signature and 2 address bytes*/ + int index = 0; + + logError(0, "%s Starting Read Chip Info...\n", tag); + if (doRequest == 1) { + ret = requestCompensationData(CHIP_INFO); + if (ret < 0) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_REQU_COMP_DATA); + ret = (ret | ERROR_REQU_COMP_DATA); + goto FAIL; + } + } + + logError(0, "%s Byte to read = %d bytes\n", tag, CHIP_INFO_SIZE + 3); + + if (readCmdU16(FTS_CMD_FRAMEBUFFER_R, ADDR_FRAMEBUFFER_DATA, data, + CHIP_INFO_SIZE + 3, DUMMY_FRAMEBUFFER) < 0) { + logError(1, "%s %s: ERROR %02X\n", tag, __func__, ERROR_I2C_R); + ret = ERROR_I2C_R; + goto FAIL; + } + + logError(0, "%s Read data ok!\n", tag); + + logError(0, "%s Starting parsing of data...\n", tag); + + if (data[0] != HEADER_SIGNATURE) { + logError(1, "%s %s:ERROR ", tag, __func__); + logError(1, "%02X The Header Signature is wrong!%02X != %02X\n", + ERROR_WRONG_COMP_SIGN, data[0], HEADER_SIGNATURE); + ret = ERROR_WRONG_COMP_SIGN; + goto FAIL; + } + + u8ToU16_le(&data[1], &answer); + + if (answer != CHIP_INFO) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_DIFF_COMP_TYPE); + ret = ERROR_DIFF_COMP_TYPE; + goto FAIL; + } + + index += 3; + ftsInfo.u8_loadCnt = data[index++]; + ftsInfo.u8_infoVer = data[index++]; + u8ToU16(&data[index], &ftsInfo.u16_ftsdId); + index += 2; + ftsInfo.u8_ftsdVer = data[index++]; + ftsInfo.u8_ftsaId = data[index++]; + ftsInfo.u8_ftsaVer = data[index++]; + ftsInfo.u8_tchRptVer = data[index++]; + + logError(0, "%s External Release = ", tag); + for (i = 0; i < EXTERNAL_RELEASE_INFO_SIZE; i++) { + ftsInfo.u8_extReleaseInfo[i] = data[index++]; + logError(0, "%02X ", ftsInfo.u8_extReleaseInfo[i]); + } + logError(0, "\n"); + + for (i = 0; i < sizeof(ftsInfo.u8_custInfo); i++) + ftsInfo.u8_custInfo[i] = data[index++]; + + u8ToU16(&data[index], &ftsInfo.u16_fwVer); + index += 2; + logError(1, "%s FW VERSION = %04X\n", tag, ftsInfo.u16_fwVer); + + u8ToU16(&data[index], &ftsInfo.u16_cfgId); + index += 2; + logError(1, "%s CONFIG ID = %04X\n", tag, ftsInfo.u16_cfgId); + + ftsInfo.u32_projId = ((data[index + 3] & 0x000000FF) << 24) + + ((data[index + 2] & 0x000000FF) << 16) + + ((data[index + 1] & 0x000000FF) << 8) + + (data[index] & 0x000000FF); + index += 4; + + u8ToU16(&data[index], &ftsInfo.u16_scrXRes); + index += 2; + + u8ToU16(&data[index], &ftsInfo.u16_scrYRes); + index += 2; + + ftsInfo.u8_scrForceLen = data[index++]; + logError(0, "%s Force Len = %d\n", tag, ftsInfo.u8_scrForceLen); + + ftsInfo.u8_scrSenseLen = data[index++]; + logError(0, "%s Sense Len = %d\n", tag, ftsInfo.u8_scrSenseLen); + + for (i = 0; i < 8; i++) + ftsInfo.u64_scrForceEn[i] = data[index++]; + + for (i = 0; i < 8; i++) + ftsInfo.u64_scrSenseEn[i] = data[index++]; + + ftsInfo.u8_msKeyLen = data[index++]; + logError(0, "%s MS Key Len = %d\n", tag, ftsInfo.u8_msKeyLen); + + for (i = 0; i < 8; i++) + ftsInfo.u64_msKeyForceEn[i] = data[index++]; + + for (i = 0; i < 8; i++) + ftsInfo.u64_msKeySenseEn[i] = data[index++]; + + ftsInfo.u8_ssKeyLen = data[index++]; + logError(0, "%s SS Key Len = %d\n", tag, ftsInfo.u8_ssKeyLen); + + for (i = 0; i < 8; i++) + ftsInfo.u64_ssKeyForceEn[i] = data[index++]; + + for (i = 0; i < 8; i++) + ftsInfo.u64_ssKeySenseEn[i] = data[index++]; + + ftsInfo.u8_frcTchXLen = data[index++]; + + ftsInfo.u8_frcTchYLen = data[index++]; + + for (i = 0; i < 8; i++) + ftsInfo.u64_frcTchForceEn[i] = data[index++]; + + for (i = 0; i < 8; i++) + ftsInfo.u64_frcTchSenseEn[i] = data[index++]; + + + ftsInfo.u8_msScrConfigTuneVer = data[index++]; + logError(0, "%s CFG MS TUNING VERSION = %02X\n", + tag, ftsInfo.u8_msScrConfigTuneVer); + ftsInfo.u8_msScrLpConfigTuneVer = data[index++]; + ftsInfo.u8_msScrHwulpConfigTuneVer = data[index++]; + ftsInfo.u8_msKeyConfigTuneVer = data[index++]; + ftsInfo.u8_ssTchConfigTuneVer = data[index++]; + logError(0, "%s CFG SS TUNING VERSION = %02X\n", + tag, ftsInfo.u8_ssTchConfigTuneVer); + ftsInfo.u8_ssKeyConfigTuneVer = data[index++]; + ftsInfo.u8_ssHvrConfigTuneVer = data[index++]; + ftsInfo.u8_frcTchConfigTuneVer = data[index++]; + ftsInfo.u8_msScrCxmemTuneVer = data[index++]; + logError(0, "%s CX MS TUNING VERSION = %02X\n", + tag, ftsInfo.u8_msScrCxmemTuneVer); + ftsInfo.u8_msScrLpCxmemTuneVer = data[index++]; + ftsInfo.u8_msScrHwulpCxmemTuneVer = data[index++]; + ftsInfo.u8_msKeyCxmemTuneVer = data[index++]; + ftsInfo.u8_ssTchCxmemTuneVer = data[index++]; + logError(0, "%s CX SS TUNING VERSION = %02X\n", + tag, ftsInfo.u8_ssTchCxmemTuneVer); + ftsInfo.u8_ssKeyCxmemTuneVer = data[index++]; + ftsInfo.u8_ssHvrCxmemTuneVer = data[index++]; + ftsInfo.u8_frcTchCxmemTuneVer = data[index++]; + ftsInfo.u32_mpPassFlag = ((data[index + 3] & 0x000000FF) << 24) + + ((data[index + 2] & 0x000000FF) << 16) + + ((data[index + 1] & 0x000000FF) << 8) + + (data[index] & 0x000000FF); + index += 4; + logError(0, "%s MP SIGNATURE = %08X\n", tag, ftsInfo.u32_mpPassFlag); + ftsInfo.u32_featEn = ((data[index + 3] & 0x000000FF) << 24) + + ((data[index + 2] & 0x000000FF) << 16) + + ((data[index + 1] & 0x000000FF) << 8) + + (data[index] & 0x000000FF); + index += 4; + ftsInfo.u32_echoEn = ((data[index + 3] & 0x000000FF) << 24) + + ((data[index + 2] & 0x000000FF) << 16) + + ((data[index + 1] & 0x000000FF) << 8) + + (data[index] & 0x000000FF); + index += 4; + logError(0, "%s FEATURES = %08X\n", tag, ftsInfo.u32_echoEn); + ftsInfo.u8_sideTchConfigTuneVer = data[index++]; + ftsInfo.u8_sideTchCxmemTuneVer = data[index++]; + ftsInfo.u8_sideTchForceLen = data[index++]; + logError(0, "%s Side Touch Force Len = %d\n", + tag, ftsInfo.u8_sideTchForceLen); + ftsInfo.u8_sideTchSenseLen = data[index++]; + logError(0, "%s Side Touch Sense Len = %d\n", + tag, ftsInfo.u8_sideTchSenseLen); + for (i = 0; i < 8; i++) + ftsInfo.u64_sideTchForceEn[i] = data[index++]; + for (i = 0; i < 8; i++) + ftsInfo.u64_sideTchSenseEn[i] = data[index++]; + ftsInfo.u8_errSign = data[index++]; + logError(0, "%s ERROR SIGNATURE = %02X\n", tag, ftsInfo.u8_errSign); + if (ftsInfo.u8_errSign == ERROR_SIGN_HEAD) { + logError(0, "%s Correct Error Signature found!\n", tag); + u8ToU16(&data[index], &ftsInfo.u16_errOffset); + } else { + logError(1, "%s Error Signature NOT FOUND!\n", tag); + ftsInfo.u16_errOffset = INVALID_ERROR_OFFS; + } + logError(0, "%s ERROR OFFSET = %04X\n", tag, ftsInfo.u16_errOffset); + index += 2; + logError(0, "%s Parsed %d bytes!\n", tag, index); + + + if (index != CHIP_INFO_SIZE + 3) { + logError(1, "%s %s: index = %d different from %d ERROR %02X\n", + tag, __func__, index, CHIP_INFO_SIZE + 3, + ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + logError(0, "%s Chip Info Read DONE!\n", tag); + return OK; + +FAIL: + defaultChipInfo(isI2cError(ret)); + return ret; +} + diff --git a/qcom/opensource/touch-drivers/st/fts_lib/ftsCompensation.h b/qcom/opensource/touch-drivers/st/fts_lib/ftsCompensation.h new file mode 100644 index 0000000000..fcd1cccfe5 --- /dev/null +++ b/qcom/opensource/touch-drivers/st/fts_lib/ftsCompensation.h @@ -0,0 +1,207 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * FTS Capacitive touch screen controller (FingerTipS) + * + * Copyright (C) 2016-2019, STMicroelectronics Limited. + * Authors: AMG(Analog Mems Group) + * + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +/** + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS API for Flashing the IC * + * * + ************************************************************************** + ************************************************************************** + * + */ + +#ifndef __FTS_COMPENSATION_H +#define __FTS_COMPENSATION_H + + +#include "ftsCrossCompile.h" +#include "ftsSoftware.h" + + +#define COMP_DATA_READ_RETRY 2 + +//Bytes dimension of Compensation Data Format + +#define COMP_DATA_HEADER 8 +#define COMP_DATA_GLOBAL 8 + + +#define HEADER_SIGNATURE 0xA5 +#define INVALID_ERROR_OFFS 0xFFFF + +//Possible Compensation/Frame Data Type +#define GENERAL_TUNING 0x0100 +#define MS_TOUCH_ACTIVE 0x0200 +#define MS_TOUCH_LOW_POWER 0x0400 +#define MS_TOUCH_ULTRA_LOW_POWER 0x0800 +#define MS_KEY 0x1000 +#define SS_TOUCH 0x2000 +#define SS_KEY 0x4000 +#define SS_HOVER 0x8000 +#define SS_PROXIMITY 0x0001 +#define CHIP_INFO 0xFFFF + + +#define TIMEOUT_REQU_COMP_DATA 1000 //ms + +//CHIP INFO +#define CHIP_INFO_SIZE 161 +/*bytes to read from framebuffer (exclude the signature and the type*/ +/*because already checked during the reading)*/ +#define EXTERNAL_RELEASE_INFO_SIZE 8 //bytes + +struct DataHeader { + int force_node, sense_node; + u16 type; +}; + + +struct MutualSenseData { + struct DataHeader header; + u8 tuning_ver; + u8 cx1; + u8 *node_data; + int node_data_size; +}; + + +struct SelfSenseData { + struct DataHeader header; + u8 tuning_ver; + u8 f_ix1, s_ix1; + u8 f_cx1, s_cx1; + u8 f_max_n, s_max_n; + + u8 *ix2_fm; + u8 *ix2_sn; + u8 *cx2_fm; + u8 *cx2_sn; +}; + + +struct GeneralData { + struct DataHeader header; + u8 ftsd_lp_timer_cal0; + u8 ftsd_lp_timer_cal1; + u8 ftsd_lp_timer_cal2; + + u8 ftsd_lp_timer_cal3; + u8 ftsa_lp_timer_cal0; + u8 ftsa_lp_timer_cal1; +}; + +struct chipInfo { + u8 u8_loadCnt; ///< 03 - Load Counter + u8 u8_infoVer; ///< 04 - New chip info version + u16 u16_ftsdId; ///< 05 - FTSD ID + u8 u8_ftsdVer; ///< 07 - FTSD version + u8 u8_ftsaId; ///< 08 - FTSA ID + u8 u8_ftsaVer; ///< 09 - FTSA version + u8 u8_tchRptVer; ///< 0A - Touch report version (e.g. ST, Samsung etc) + + ///< 0B - External release information + u8 u8_extReleaseInfo[EXTERNAL_RELEASE_INFO_SIZE]; + u8 u8_custInfo[12]; ///< 13 - Customer information + u16 u16_fwVer; ///< 1F - Firmware version + u16 u16_cfgId; ///< 21 - Configuration ID + u32 u32_projId; ///< 23 - Project ID + u16 u16_scrXRes; ///< 27 - X resolution on main screen + u16 u16_scrYRes; ///< 29 - Y resolution on main screen + u8 u8_scrForceLen; ///< 2B - Number of force channel on main screen + u8 u8_scrSenseLen; ///< 2C - Number of sense channel on main screen + u8 u64_scrForceEn[8]; ///< 2D - Force channel enabled on main screen + u8 u64_scrSenseEn[8]; ///< 35 - Sense channel enabled on main screen + u8 u8_msKeyLen; ///< 3D - Number of MS Key channel + u8 u64_msKeyForceEn[8]; ///< 3E - MS Key force channel enable + u8 u64_msKeySenseEn[8]; ///< 46 - MS Key sense channel enable + u8 u8_ssKeyLen; ///< 4E - Number of SS Key channel + u8 u64_ssKeyForceEn[8]; ///< 4F - SS Key force channel enable + u8 u64_ssKeySenseEn[8]; ///< 57 - SS Key sense channel enable + u8 u8_frcTchXLen; ///< 5F - Number of force touch force channel + u8 u8_frcTchYLen; ///< 60 - Number of force touch sense channel + u8 u64_frcTchForceEn[8];///< 61 - Force touch force channel enable + u8 u64_frcTchSenseEn[8];///< 69 - Force touch sense channel enable + u8 u8_msScrConfigTuneVer; ///< 71 - MS screen tuning version in config + + ///< 72 - MS screen LP mode tuning version in config + u8 u8_msScrLpConfigTuneVer; + + ///< 73 - MS screen ultra low power mode tuning version in config + u8 u8_msScrHwulpConfigTuneVer; + u8 u8_msKeyConfigTuneVer; ///< 74 - MS Key tuning version in config + u8 u8_ssTchConfigTuneVer; ///< 75 - SS touch tuning version in config + u8 u8_ssKeyConfigTuneVer; ///< 76 - SS Key tuning version in config + u8 u8_ssHvrConfigTuneVer; ///< 77 - SS hover tuning version in config + + ///< 78 - Force touch tuning version in config + u8 u8_frcTchConfigTuneVer; + u8 u8_msScrCxmemTuneVer; ///< 79 - MS screen tuning version in cxmem + + ///< 7A - MS screen LP mode tuning version in cxmem + u8 u8_msScrLpCxmemTuneVer; + + ///< 7B - MS screen ultra low power mode tuning version in cxmem + u8 u8_msScrHwulpCxmemTuneVer; + u8 u8_msKeyCxmemTuneVer; ///< 7C - MS Key tuning version in cxmem + u8 u8_ssTchCxmemTuneVer; ///< 7D - SS touch tuning version in cxmem + u8 u8_ssKeyCxmemTuneVer; ///< 7E - SS Key tuning version in cxmem + u8 u8_ssHvrCxmemTuneVer; ///< 7F - SS hover tuning version in cxmem + + ///< 80 - Force touch tuning version in cxmem + u8 u8_frcTchCxmemTuneVer; + u32 u32_mpPassFlag; ///< 81 - Mass production pass flag + u32 u32_featEn; ///< 85 - Supported features + + ///< 89 - enable of particular features: first bit is Echo Enables + u32 u32_echoEn; + + ///< 8D - Side Touch tuning version in config + u8 u8_sideTchConfigTuneVer; + u8 u8_sideTchCxmemTuneVer; ///< 8E - Side Touch tuning version in cxmem + u8 u8_sideTchForceLen; ///< 8F - Number of force channel on side touch + u8 u8_sideTchSenseLen; ///< 90 - Number of sense channel on side touch + u8 u64_sideTchForceEn[8];///< 91 - Side touch force channel enable + u8 u64_sideTchSenseEn[8];///< 99 - Side touch sense channel enable + u8 u8_errSign; ///< A1 - Signature for error field + u16 u16_errOffset; ///< A2 - Error Offset +}; + +int requestCompensationData(u16 type); +int readCompensationDataHeader(u16 type, struct DataHeader *header, + u16 *address); +int readMutualSenseGlobalData(u16 *address, struct MutualSenseData *global); +int readMutualSenseNodeData(u16 address, struct MutualSenseData *node); +int readMutualSenseCompensationData(u16 type, struct MutualSenseData *data); +int readSelfSenseGlobalData(u16 *address, struct SelfSenseData *global); +int readSelfSenseNodeData(u16 address, struct SelfSenseData *node); +int readSelfSenseCompensationData(u16 type, struct SelfSenseData *data); +int readGeneralGlobalData(u16 address, struct GeneralData *global); +int readGeneralCompensationData(u16 type, struct GeneralData *data); +int defaultChipInfo(int i2cError); +int readChipInfo(int doRequest); + +#endif diff --git a/qcom/opensource/touch-drivers/st/fts_lib/ftsCrossCompile.c b/qcom/opensource/touch-drivers/st/fts_lib/ftsCrossCompile.c new file mode 100644 index 0000000000..e44899c0a6 --- /dev/null +++ b/qcom/opensource/touch-drivers/st/fts_lib/ftsCrossCompile.c @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * FTS Capacitive touch screen controller (FingerTipS) + * + * Copyright (C) 2016-2019, STMicroelectronics Limited. + * Authors: AMG(Analog Mems Group) + * + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +/** + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS Cross Compile * + * * + ************************************************************************** + ************************************************************************** + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include +#include +#include +#include +#include +#include + +#include "ftsCrossCompile.h" +#include "ftsError.h" + +void *stmalloc(size_t size) +{ + return kmalloc(size, GFP_KERNEL); +} + +void stfree(void *ptr) +{ + kfree(ptr); +} diff --git a/qcom/opensource/touch-drivers/st/fts_lib/ftsCrossCompile.h b/qcom/opensource/touch-drivers/st/fts_lib/ftsCrossCompile.h new file mode 100644 index 0000000000..15f51b3d72 --- /dev/null +++ b/qcom/opensource/touch-drivers/st/fts_lib/ftsCrossCompile.h @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * FTS Capacitive touch screen controller (FingerTipS) + * + * Copyright (C) 2016-2018, STMicroelectronics Limited. + * Authors: AMG(Analog Mems Group) + * + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +/** + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS cross compile * + * * + ************************************************************************** + ************************************************************************** + * + */ + +#ifndef __FTS_CROSS_COMPILE_H +#define __FTS_CROSS_COMPILE_H + +//#define NDK +//#define DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include +#include +#include +#include +#include +#include + +void *stmalloc(size_t size); +void stfree(void *ptr); + +#endif diff --git a/qcom/opensource/touch-drivers/st/fts_lib/ftsError.c b/qcom/opensource/touch-drivers/st/fts_lib/ftsError.c new file mode 100644 index 0000000000..80919322e2 --- /dev/null +++ b/qcom/opensource/touch-drivers/st/fts_lib/ftsError.c @@ -0,0 +1,262 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * FTS Capacitive touch screen controller (FingerTipS) + * + * Copyright (C) 2016-2019, STMicroelectronics Limited. + * Authors: AMG(Analog Mems Group) + * + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +/** + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS error/info kernel log reporting * + * * + ************************************************************************** + ************************************************************************** + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include +#include + +#include +#include +#include + +#include "../fts.h" +#include "ftsCrossCompile.h" +#include "ftsError.h" +#include "ftsIO.h" +#include "ftsTool.h" +#include "ftsCompensation.h" + +static char tag[8] = "[ FTS ]\0"; + + +void logError(int force, const char *msg, ...) +{ + + if (force == 1 +#ifdef DEBUG + || 1 +#endif + ) { + va_list args; + + va_start(args, msg); + vprintk(msg, args); + va_end(args); + } +} + +int isI2cError(int error) +{ + if (((error & 0x000000FF) >= (ERROR_I2C_R & 0x000000FF)) + && ((error & 0x000000FF) <= (ERROR_I2C_O & 0x000000FF))) + return 1; + else + return 0; +} + +int dumpErrorInfo(void) +{ + int ret, i; + u8 data[ERROR_INFO_SIZE] = {0}; + u32 sign = 0; + + logError(0, "%s %s: Starting dump of error info...\n", tag, __func__); + if (ftsInfo.u16_errOffset == INVALID_ERROR_OFFS) { + logError(1, "%s %s: Invalid error offset ERROR %02X\n", + tag, __func__, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + ret = readCmdU16(FTS_CMD_FRAMEBUFFER_R, ftsInfo.u16_errOffset, + data, ERROR_INFO_SIZE, DUMMY_FRAMEBUFFER); + if (ret < OK) { + logError(1, "%s %s: reading data ERROR %02X\n", + tag, __func__, ret); + return ret; + } + logError(0, "%s %s: Error Info =\n", tag, __func__); + u8ToU32(data, &sign); + if (sign != ERROR_SIGNATURE) + logError(1, "%s %s:Wrong Signature! Data may be invalid!\n", + tag, __func__); + else + logError(1, "%s %s: Error Signature OK! Data are valid!\n", + tag, __func__); + + for (i = 0; i < ERROR_INFO_SIZE; i++) { + if (i % 4 == 0) + logError(1, KERN_ERR "\n%s %s: %d) ", + tag, __func__, i / 4); + logError(1, "%02X ", data[i]); + } + logError(1, "\n"); + + logError(0, "%s %s: dump of error info FINISHED!\n", tag, __func__); + return OK; + +} + +int errorHandler(u8 *event, int size) +{ + int res = OK; + struct fts_ts_info *info = NULL; + + if (getClient() != NULL) + info = i2c_get_clientdata(getClient()); + + if (info == NULL || event == NULL || size <= 1 || event[0] != + EVENTID_ERROR_EVENT) { + logError(1, "%s %s: event Null or not correct size! ", + tag, __func__, ERROR_OP_NOT_ALLOW); + logError(1, "ERROR %08X\n", ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + logError(0, "%s %s: Starting handling...\n", tag, __func__); + //TODO: write an error log for undefinied command subtype 0xBA + switch (event[1]) { + case EVENT_TYPE_ESD_ERROR: //esd + res = fts_chip_powercycle(info); + if (res < OK) { + logError(1, "%s %s: ", tag, res); + logError(1, "Error performing powercycle ERROR %08X\n"); + } + + res = fts_system_reset(); + if (res < OK) + logError(1, "%s %s:Cannot reset device ERROR%08X\n", + tag, __func__, res); + res = (ERROR_HANDLER_STOP_PROC | res); + break; + + case EVENT_TYPE_WATCHDOG_ERROR: //watchdog + dumpErrorInfo(); + res = fts_system_reset(); + if (res < OK) + logError(1, "%s %s:Cannot reset device:ERROR%08X\n", + tag, __func__, res); + res = (ERROR_HANDLER_STOP_PROC | res); + break; + case EVENT_TYPE_CHECKSUM_ERROR: //CRC ERRORS + switch (event[2]) { + case CRC_CONFIG_SIGNATURE: + logError(1, "%s %s: Config Signature ERROR!\n", + tag, __func__); + break; + case CRC_CONFIG: + logError(1, "%s %s:Config CRC ERROR!\n", tag, __func__); + break; + case CRC_CX_MEMORY: + logError(1, "%s %s: CX CRC ERROR!\n", tag, __func__); + break; + } + break; + case EVENT_TYPE_LOCKDOWN_ERROR: + //res = (ERROR_HANDLER_STOP_PROC|res); + //stop lockdown code routines in order to retry + switch (event[2]) { + case 0x01: + logError(1, "%s %s:Lockdown code alredy ", + tag, __func__); + logError(1, "written into the IC!\n"); + break; + case 0x02: + logError(1, "%s %s:Lockdown CRC ", tag, __func__); + logError(1, "check fail during a WRITE!\n"); + break; + + case 0x03: + logError(1, + "%s %s:Lockdown WRITE command format wrong!\n", + tag, __func__); + break; + case 0x04: + pr_err("Lockdown Memory Corrupted!\n"); + logError(1, "%s %s:Please contact ST for support!\n", + tag, __func__); + break; + case 0x11: + logError(1, + "%s %s:NO Lockdown code to READ into the IC!\n", + tag, __func__); + break; + case 0x12: + logError(1, + "%s %s:Lockdown code data corrupted\n", + tag, __func__); + break; + case 0x13: + logError(1, + "%s %s:Lockdown READ command format wrong!\n", + tag, __func__); + break; + case 0x21: + pr_err("Exceeded maximum number of\n"); + logError(1, + "%s %s:Lockdown code REWRITE into IC!\n", + tag, __func__); + break; + case 0x22: + logError(1, "%s %s:Lockdown CRC check", tag, __func__); + logError(1, " fail during a REWRITE!\n"); + break; + case 0x23: + logError(1, "%s %s:", tag, __func__); + logError(1, "Lockdown REWRITE command format wrong!\n"); + break; + case 0x24: + pr_err("Lockdown Memory Corrupted!\n"); + logError(1, "%s %s:Please contact ST for support!\n", + tag, __func__); + break; + default: + logError(1, "%s %s:No valid error type for LOCKDOWN!\n", + tag, __func__); + } + break; + + default: + logError(0, "%s %s: No Action taken!\n", tag, __func__); + break; + } + logError(0, "%s %s: handling Finished! res = %08X\n", + tag, __func__, res); + return res; +} + diff --git a/qcom/opensource/touch-drivers/st/fts_lib/ftsError.h b/qcom/opensource/touch-drivers/st/fts_lib/ftsError.h new file mode 100644 index 0000000000..c96a587691 --- /dev/null +++ b/qcom/opensource/touch-drivers/st/fts_lib/ftsError.h @@ -0,0 +1,181 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * FTS Capacitive touch screen controller (FingerTipS) + * + * Copyright (C) 2016-2019, STMicroelectronics Limited. + * Authors: AMG(Analog Mems Group) + * + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +/** + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS error/info kernel log reporting * + * * + ************************************************************************** + ************************************************************************** + */ + +#ifndef __FTS_ERROR_H +#define __FTS_ERROR_H + + +//FIRST LEVEL ERROR CODE +#define OK (int)(0x00000000)/* No ERROR*/ +/*allocation of memory failed*/ +#define ERROR_ALLOC (int)(0x80000001) +#define ERROR_I2C_R (int)(0x80000002)//i2c read failed +#define ERROR_I2C_W (int)(0x80000003)//i2c write failed +#define ERROR_I2C_WR (int)(0x80000004)//i2c write/read failed + +//error during opening i2c device +#define ERROR_I2C_O (int)(0x80000005) +#define ERROR_OP_NOT_ALLOW (int)(0x80000006)//operation not allowed + +//timeout expired! exceed the max number of +//retries or the max waiting time +#define ERROR_TIMEOUT (int)(0x80000007) + +//the file that i want to open is not found +#define ERROR_FILE_NOT_FOUND (int)(0x80000008) +//error during parsing the file +#define ERROR_FILE_PARSE (int)(0x80000009) +//error during reading the file +#define ERROR_FILE_READ (int)(0x8000000A) +#define ERROR_LABEL_NOT_FOUND (int)(0x8000000B)//label not found + +//fw in the chip newer than the one in the memmh +#define ERROR_FW_NO_UPDATE (int)(0x8000000C) +//flash status busy or unknown +#define ERROR_FLASH_UNKNOWN (int)(0x8000000D) + +//SECOND LEVEL ERROR CODE +//unable to disable the interrupt +#define ERROR_DISABLE_INTER (int)(0x80000200) + +//unable to activate the interrupt +#define ERROR_ENABLE_INTER (int)(0x80000300) + +#define ERROR_READ_B2 (int)(0x80000400)//B2 command failed + +//unable to read an offset from memory +#define ERROR_GET_OFFSET (int)(0x80000500) + +//unable to retrieve the data of a required frame +#define ERROR_GET_FRAME_DATA (int)(0x80000600) + +//FW answers with an event that has a +//different address respect the request done +#define ERROR_DIFF_COMP_TYPE (int)(0x80000700) + +//the signature of the compensation data is not A5 +#define ERROR_WRONG_COMP_SIGN (int)(0x80000800) +//the command Sense On failed +#define ERROR_SENSE_ON_FAIL (int)(0x80000900) +//the command Sense Off failed +#define ERROR_SENSE_OFF_FAIL (int)(0x80000A00) + +//the command SYSTEM RESET failed +#define ERROR_SYSTEM_RESET_FAIL (int)(0x80000B00) + +//flash status not ready within a timeout +#define ERROR_FLASH_NOT_READY (int)(0x80000C00) + +//unable to retrieve fw_vers or the config_id +#define ERROR_FW_VER_READ (int)(0x80000D00) + +//unable to enable/disable the gesture +#define ERROR_GESTURE_ENABLE_FAIL (int)(0x80000E00) + +//unable to start to add custom gesture +#define ERROR_GESTURE_START_ADD (int)(0x80000F00) + +//unable to finish to add custom gesture +#define ERROR_GESTURE_FINISH_ADD (int)(0x80001000) + +//unable to add custom gesture data +#define ERROR_GESTURE_DATA_ADD (int)(0x80001100) + +//unable to remove custom gesture data +#define ERROR_GESTURE_REMOVE (int)(0x80001200) + +//unable to enable/disable a feature mode in the IC +#define ERROR_FEATURE_ENABLE_DISABLE (int)(0x80001300) + +//unable to set/read noise parameter in the IC +#define ERROR_NOISE_PARAMETERS (int)(0x80001400) + +//unable to write/rewrite/read lockdown code in the IC +#define ERROR_LOCKDOWN_CODE (int)(0x80001500) + +//THIRD LEVEL ERROR CODE +//unable to retrieve the force and/or sense length +#define ERROR_CH_LEN (int)(0x80010000) + +//compensation data request failed +#define ERROR_REQU_COMP_DATA (int)(0x80020000) + +//unable to retrieve the compensation data header +#define ERROR_COMP_DATA_HEADER (int)(0x80030000) + +//unable to retrieve the global compensation data +#define ERROR_COMP_DATA_GLOBAL (int)(0x80040000) + +//unable to retrieve the compensation data for each node +#define ERROR_COMP_DATA_NODE (int)(0x80050000) + +//check of production limits or of fw answers failed +#define ERROR_TEST_CHECK_FAIL (int)(0x80060000) +#define ERROR_MEMH_READ (int)(0x80070000)//memh reading failed +#define ERROR_FLASH_BURN_FAILED (int)(0x80080000)//flash burn failed +#define ERROR_MS_TUNING (int)(0x80090000)//ms tuning failed +#define ERROR_SS_TUNING (int)(0x800A0000)//ss tuning failed +//lp timer calibration failed +#define ERROR_LP_TIMER_TUNING (int)(0x800B0000) +//save cx data to flash failed +#define ERROR_SAVE_CX_TUNING (int)(0x800C0000) + +//stop the poll of the FIFO if particular errors are found +#define ERROR_HANDLER_STOP_PROC (int)(0x800D0000) +//unable to retrieve echo event +#define ERROR_CHECK_ECHO_FAIL (int)(0x800E0000) + +//FOURTH LEVEL ERROR CODE +//production data test failed +#define ERROR_PROD_TEST_DATA (int)(0x81000000) + +//complete flash procedure failed +#define ERROR_FLASH_PROCEDURE (int)(0x82000000) +//production ito test failed +#define ERROR_PROD_TEST_ITO (int)(0x83000000) + +//production initialization test failed +#define ERROR_PROD_TEST_INITIALIZATION (int)(0x84000000) + +//mismatch of the MS or SS tuning_version +#define ERROR_GET_INIT_STATUS (int)(0x85000000) + + +void logError(int force, const char *msg, ...); +int isI2cError(int error); +int dumpErrorInfo(void); +int errorHandler(u8 *event, int size); + +#endif diff --git a/qcom/opensource/touch-drivers/st/fts_lib/ftsFlash.c b/qcom/opensource/touch-drivers/st/fts_lib/ftsFlash.c new file mode 100644 index 0000000000..9fc3926dec --- /dev/null +++ b/qcom/opensource/touch-drivers/st/fts_lib/ftsFlash.c @@ -0,0 +1,1178 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * FTS Capacitive touch screen controller (FingerTipS) + * + * Copyright (C) 2016-2019, STMicroelectronics Limited. + * Authors: AMG(Analog Mems Group) + * + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +/** + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS API for Flashing the IC * + * * + ************************************************************************** + ************************************************************************** + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ftsCrossCompile.h" +#include "ftsCompensation.h" +#include "ftsError.h" +#include "ftsFlash.h" +#include "ftsFrame.h" +#include "ftsIO.h" +#include "ftsSoftware.h" +#include "ftsTest.h" +#include "ftsTime.h" +#include "ftsTool.h" +#include "../fts.h"//needed for including the define FW_H_FILE + +#ifdef FW_H_FILE +#include <../fts_fw.h> +#define LOAD_FW_FROM 1 +#else +#define LOAD_FW_FROM 0 +#endif + +#define FTS_LATEST_VERSION 0x1101 + +static char tag[8] = "[ FTS ]\0"; + +int getFirmwareVersion(u16 *fw_vers, u16 *config_id) +{ + u8 fwvers[DCHIP_FW_VER_BYTE]; + u8 confid[CONFIG_ID_BYTE]; + int res; + + res = readCmdU16(FTS_CMD_HW_REG_R, DCHIP_FW_VER_ADDR, + fwvers, DCHIP_FW_VER_BYTE, DUMMY_HW_REG); + if (res < OK) { + logError(1, + "%s %s:unable to read fw_version ERROR %02X\n", + tag, __func__, ERROR_FW_VER_READ); + return (res | ERROR_FW_VER_READ); + } + + u8ToU16(fwvers, fw_vers); //fw version use big endian + if (*fw_vers != 0) { + // if fw_version is 00 00 means that there is + //no firmware running in the chip therefore will be + //impossible find the config_id + res = readB2(CONFIG_ID_ADDR, confid, CONFIG_ID_BYTE); + if (res < OK) { + logError(1, "%s %s:unable to read config_id ", + tag, __func__); + logError(1, "ERROR %02X\n", ERROR_FW_VER_READ); + return (res | ERROR_FW_VER_READ); + } + u8ToU16(confid, config_id); //config id use little endian + } else { + *config_id = 0x0000; + } + + logError(0, "%s FW VERS = %04X\n", tag, *fw_vers); + logError(0, "%s CONFIG ID = %04X\n", tag, *config_id); + return OK; +} + +int getFWdata_nocheck(const char *pathToFile, u8 **data, int *size, int from) +{ + const struct firmware *fw = NULL; + struct device *dev = getDev(); + int res; + + if (dev == NULL) + return ERROR_OP_NOT_ALLOW; + + logError(0, "%s Read FW from BIN file!\n", tag); + + res = firmware_request_nowarn(&fw, pathToFile, dev); + if (res) { + logError(1, "%s %s:No File found! ERROR %08X\n", + tag, __func__, ERROR_FILE_NOT_FOUND); + return ERROR_FILE_NOT_FOUND; + } + + *size = fw->size; + *data = (u8 *)kmalloc_array((*size), sizeof(u8), GFP_KERNEL); + if (*data == NULL) { + logError(1, "%s %s:Impossible to allocate! %08X\n", __func__); + release_firmware(fw); + return ERROR_ALLOC; + } + memcpy(*data, (u8 *)fw->data, (*size)); + release_firmware(fw); + + logError(0, "%s %s:Finshed!\n", tag, __func__); + return OK; +} + +int getFWdata(const char *pathToFile, u8 **data, int *size, int from) +{ + const struct firmware *fw = NULL; + struct device *dev = NULL; + int res; + + logError(0, "%s %s starting...\n", tag, __func__); + switch (from) { +#ifdef FW_H_FILE + case 1: + logError(1, "%s Read FW from .h file!\n", tag); + *size = FW_SIZE_NAME; + *data = (u8 *)kmalloc_array((*size), sizeof(u8), GFP_KERNEL); + if (*data == NULL) { + logError(1, "%s %s:Impossible to allocate memory! ", + tag, __func__); + logError(1, "ERROR %08X\n", ERROR_ALLOC); + + return ERROR_ALLOC; + } + memcpy(*data, (u8 *)FW_ARRAY_NAME, (*size)); + break; +#endif + default: + logError(0, "%s Read FW from BIN file!\n", tag); + + if (ftsInfo.u16_fwVer >= FTS_LATEST_VERSION) + return ERROR_FW_NO_UPDATE; + + dev = getDev(); + + if (dev != NULL) { + res = firmware_request_nowarn(&fw, pathToFile, dev); + if (res == 0) { + *size = fw->size; + *data = (u8 *)kmalloc_array((*size), sizeof(u8), + GFP_KERNEL); + if (*data == NULL) { + logError(1, "%s %s:Impossible to ", + tag, __func__); + logError(1, "%allocate! %08X\n", + ERROR_ALLOC); + release_firmware(fw); + return ERROR_ALLOC; + } + memcpy(*data, (u8 *)fw->data, (*size)); + release_firmware(fw); + } else { + logError(0, "%s %s:No File found! ERROR %08X\n", + tag, __func__, ERROR_FILE_NOT_FOUND); + return ERROR_FILE_NOT_FOUND; + } + } else { + logError(1, "%s %s:No device found! ERROR %08X\n", + tag, __func__, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + /* break; */ + } + + logError(0, "%s %s:Finshed!\n", tag, __func__); + return OK; +} + +int readFwFile(const char *path, struct Firmware *fw, int keep_cx) +{ + int res; + int orig_size; + u8 *orig_data = NULL; + + + res = getFWdata(path, &orig_data, &orig_size, LOAD_FW_FROM); + if (res < OK) { + logError(0, "%s %s:impossible retrieve FW... ERROR %08X\n", + tag, __func__, ERROR_MEMH_READ); + + return (res | ERROR_MEMH_READ); + } + res = parseBinFile(orig_data, orig_size, fw, keep_cx); + + if (res < OK) { + logError(1, "%s %s:impossible parse ERROR %08X\n", + tag, __func__, ERROR_MEMH_READ); + return (res | ERROR_MEMH_READ); + } + + return OK; +} + +int flashProcedure(const char *path, int force, int keep_cx) +{ + struct Firmware fw; + int res; + + fw.data = NULL; + logError(0, "%s Reading Fw file...\n", tag); + res = readFwFile(path, &fw, keep_cx); + if (res < OK) { + logError(0, "%s %s: ERROR %02X\n", + tag, __func__, (res | ERROR_FLASH_PROCEDURE)); + kfree(fw.data); + return (res | ERROR_FLASH_PROCEDURE); + } + logError(0, "%s Fw file read COMPLETED!\n", tag); + + logError(0, "%s Starting flashing procedure...\n", tag); + res = flash_burn(&fw, force, keep_cx); + if (res < OK && res != (ERROR_FW_NO_UPDATE | ERROR_FLASH_BURN_FAILED)) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_FLASH_PROCEDURE); + kfree(fw.data); + return (res | ERROR_FLASH_PROCEDURE); + } + logError(0, "%s flashing procedure Finished!\n", tag); + kfree(fw.data); + + return res; +} + +#ifdef FTM3_CHIP +int flash_status(void) +{ + u8 cmd[2] = {FLASH_CMD_READSTATUS, 0x00}; + u8 readData = 0; + + logError(0, "%s %s:Reading ...\n", tag, __func__); + if (fts_readCmd(cmd, 2, &readData, FLASH_STATUS_BYTES) < 0) { + logError(1, "%s %s: ERROR % 02X\n", tag, __func__, ERROR_I2C_R); + return ERROR_I2C_R; + } + + readData &= 0x01; + logError(0, "%s %s = %d\n", tag, __func__, readData); + return (int) readData; +} + +int flash_status_ready(void) +{ + + int status = flash_status(); + + if (status == ERROR_I2C_R) { + logError(1, "%s %s: ERROR % 02X\n", tag, __func__, ERROR_I2C_R); + return ERROR_I2C_R; + } + + if (status != FLASH_READY) { + //logError(1, + //"%s %s:flash busy or unknown STATUS = % 02X\n", + //tag, status); + return ERROR_FLASH_UNKNOWN; + } + + return FLASH_READY; +} + +int wait_for_flash_ready(void) +{ + int status; + int (*code)(void); + + code = flash_status_ready; + + logError(0, "%s Waiting for flash ready...\n", tag); + status = attempt_function(code, FLASH_WAIT_BEFORE_RETRY, + FLASH_RETRY_COUNT); + + if (status != FLASH_READY) { + logError(1, "%s %s: ERROR % 02X\n", + tag, __func__, ERROR_FLASH_NOT_READY); + return (status | ERROR_FLASH_NOT_READY); + } + + logError(0, "%s Flash ready!\n", tag); + return OK; +} + +int flash_unlock(void) +{ + int status; + //write the command to perform the unlock + u8 cmd[3] = {FLASH_CMD_UNLOCK, FLASH_UNLOCK_CODE0, + FLASH_UNLOCK_CODE1}; + + logError(0, "%s Try to unlock flash...\n", tag); + status = wait_for_flash_ready(); + + if (status != OK) { + logError(1, "%s %s: ERROR % 02X\n", + tag, __func__, ERROR_FLASH_NOT_READY); + //Flash not ready within the chosen time, better exit! + return (status | ERROR_FLASH_NOT_READY); + } + + logError(0, "%s Command unlock ...\n", tag); + if (fts_writeCmd(cmd, sizeof(cmd)) < 0) { + logError(1, "%s %s: ERROR % 02X\n", + tag, __func__, ERROR_I2C_W); + return ERROR_I2C_W; + } + + status = wait_for_flash_ready(); + + if (status != OK) { + logError(1, "%s %s: ERROR % 02X\n", + tag, __func__, ERROR_FLASH_NOT_READY); + //Flash not ready within the chosen time, + //better exit! + return (status | ERROR_FLASH_NOT_READY); + } + logError(0, "%s Unlock flash DONE!\n", tag); + + return OK; +} + +int parseBinFile(u8 *fw_data, int fw_size, Firmware *fwData, int keep_cx) +{ + int dimension; + + if (keep_cx) { + dimension = FW_SIZE - FW_CX_SIZE; + logError(1, "%s %s: Selected 124k Configuration!\n", + tag, __func__); + } else { + dimension = FW_SIZE; + logError(1, "%s %s: Selected 128k Configuration!\n", + tag, __func__); + } + + if (fw_size - FW_HEADER_SIZE != FW_SIZE || fw_data == NULL) { + logError(1, "%s %s:Read only %d instead of %d... ERROR %02X\n", + tag, __func__, + fw_size - FW_HEADER_SIZE, + FW_SIZE, ERROR_FILE_PARSE); + kfree(fw_data); + return ERROR_FILE_PARSE; + } + + fwData->data = (u8 *)kmalloc_array(dimension, sizeof(u8), GFP_KERNEL); + if (fwData->data == NULL) { + logError(1, "%s %s: ERROR %02X\n", tag, __func__, ERROR_ALLOC); + + kfree(fw_data); + return ERROR_ALLOC; + } + + memcpy(fwData->data, ((u8 *)(fw_data) + FW_HEADER_SIZE), + dimension); + fwData->data_size = dimension; + + fwData->fw_ver = (u16)(((fwData->data[FW_VER_MEMH_BYTE1] & 0x00FF) << 8) + + (fwData->data[FW_VER_MEMH_BYTE0] & 0x00FF)); + + fwData->config_id = (u16)(((fwData->data[(FW_CODE_SIZE) + + FW_OFF_CONFID_MEMH_BYTE1] & 0x00FF) << 8) + + (fwData->data[(FW_CODE_SIZE) + + FW_OFF_CONFID_MEMH_BYTE0] & 0x00FF)); + + logError(0, "%s %s: FW VERS File = %04X\n", + tag, __func__, fwData->fw_ver); + logError(0, "%s %s: CONFIG ID File = %04X\n", + tag, __func__, fwData->config_id); + + logError(0, "%s READ FW DONE %d bytes!\n", tag, fwData->data_size); + + kfree(fw_data); + return OK; +} + +int fillMemory(u32 address, u8 *data, int size) +{ + int remaining = size; + int toWrite = 0; + int delta; + + u8 *buff = (u8 *)kmalloc_array((MEMORY_CHUNK + 3), sizeof(u8), + GFP_KERNEL); + if (buff == NULL) { + logError(1, "%s %s: ERROR %02X\n", tag, __func__, ERROR_ALLOC); + return ERROR_ALLOC; + } + + while (remaining > 0) { + if (remaining >= MEMORY_CHUNK) { + if ((address + MEMORY_CHUNK) < FLASH_ADDR_SWITCH_CMD) { + buff[0] = FLASH_CMD_WRITE_LOWER_64; + toWrite = MEMORY_CHUNK; + remaining -= MEMORY_CHUNK; + } else { + if (address < FLASH_ADDR_SWITCH_CMD) { + delta = FLASH_ADDR_SWITCH_CMD - address; + + buff[0] = FLASH_CMD_WRITE_LOWER_64; + toWrite = delta; + remaining -= delta; + } else { + buff[0] = FLASH_CMD_WRITE_UPPER_64; + toWrite = MEMORY_CHUNK; + remaining -= MEMORY_CHUNK; + } + } + } else { + if ((address + remaining) < FLASH_ADDR_SWITCH_CMD) { + buff[0] = FLASH_CMD_WRITE_LOWER_64; + toWrite = remaining; + remaining = 0; + } else { + if (address < FLASH_ADDR_SWITCH_CMD) { + delta = FLASH_ADDR_SWITCH_CMD - address; + + buff[0] = FLASH_CMD_WRITE_LOWER_64; + toWrite = delta; + remaining -= delta; + } else { + buff[0] = FLASH_CMD_WRITE_UPPER_64; + toWrite = remaining; + remaining = 0; + } + } + } + + buff[1] = (u8) ((address & 0x0000FF00) >> 8); + buff[2] = (u8) (address & 0x000000FF); + memcpy(buff + 3, data, toWrite); + //logError(0, + //"%s Command = %02X , address = %02X %02X, bytes = %d\n", + //tag, buff[0], buff[1], buff[2], toWrite); + if (fts_writeCmd(buff, 3 + toWrite) < 0) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_I2C_W); + kfree(buff); + return ERROR_I2C_W; + } + address += toWrite; + data += toWrite; + } + kfree(buff); + return OK; +} + +int flash_burn(Firmware *fw, int force_burn, int keep_cx) +{ + u8 cmd; + int res; + + if (!force_burn && (ftsInfo.u16_fwVer >= fw->fw_ver) + && (ftsInfo.u16_cfgId >= fw->config_id)) { + logError(0, "Firmware in the chip newer"); + logError(0, " or equal to the one to burn! "); + logError(0, "%s %s:NO UPDATE ERROR %02X\n", + tag, __func__, ERROR_FW_NO_UPDATE); + return (ERROR_FW_NO_UPDATE | ERROR_FLASH_BURN_FAILED); + } + + //programming procedure start + + logError(0, "%s Programming Procedure for flashing started:\n", tag); + + logError(0, "%s 1) SYSTEM RESET:\n", tag); + res = fts_system_reset(); + if (res < 0) { + logError(1, "%s system reset FAILED!\n", tag); + //if there is no firmware i will not + //get the controller ready event and + //there will be a timeout but i can + //keep going, but if there is + //an I2C error i have to exit + if (res != (ERROR_SYSTEM_RESET_FAIL | ERROR_TIMEOUT)) + return (res | ERROR_FLASH_BURN_FAILED); + } else + logError(0, "%s system reset COMPLETED!\n\n", tag); + + logError(0, "%s 2) FLASH UNLOCK:\n", tag); + res = flash_unlock(); + if (res < 0) { + logError(1, "%s flash unlock FAILED! ERROR %02X\n", + tag, ERROR_FLASH_BURN_FAILED); + return (res | ERROR_FLASH_BURN_FAILED); + } + logError(0, "%s flash unlock COMPLETED!\n\n", tag); + + + //Write the lower part of the Program RAM + logError(0, "%s 3) PREPARING DATA FOR FLASH BURN:\n", tag); + + res = fillMemory(FLASH_ADDR_CODE, fw->data, fw->data_size); + if (res < 0) { + logError(1, "%s Error During filling the memory!%02X\n", + tag, ERROR_FLASH_BURN_FAILED); + return (res | ERROR_FLASH_BURN_FAILED); + } + logError(0, "%s Data copy COMPLETED!\n\n", tag); + + logError(0, "%s 4) ERASE FLASH:\n", tag); + res = wait_for_flash_ready(); + if (res < 0) { + logError(1, "%s Flash not ready! ERROR %02X\n", + tag, ERROR_FLASH_BURN_FAILED); + return (res | ERROR_FLASH_BURN_FAILED); + } + + logError(0, "%s Command erase ...\n", tag); + cmd = FLASH_CMD_ERASE; + if (fts_writeCmd(&cmd, 1) < 0) { + logError(1, "%s Error during erasing flash! ERROR %02X\n", + tag, ERROR_FLASH_BURN_FAILED); + return (ERROR_I2C_W | ERROR_FLASH_BURN_FAILED); + } + + res = wait_for_flash_ready(); + if (res < 0) { + logError(1, "%s Flash not ready 2! ERROR %02X\n", + tag, ERROR_FLASH_BURN_FAILED); + return (res | ERROR_FLASH_BURN_FAILED); + } + + logError(0, "%s Flash erase COMPLETED!\n\n", tag); + + logError(0, "%s 5) BURN FLASH:\n", tag); + logError(0, "%s Command burn ...\n", tag); + cmd = FLASH_CMD_BURN; + if (fts_writeCmd(&cmd, 1) < 0) { + logError(1, "%s Error during burning data! ERROR %02X\n", + tag, ERROR_FLASH_BURN_FAILED); + return (ERROR_I2C_W | ERROR_FLASH_BURN_FAILED); + } + + res = wait_for_flash_ready(); + if (res < 0) { + logError(1, "%s Flash not ready! ERROR %02X\n", + tag, ERROR_FLASH_BURN_FAILED); + return (res | ERROR_FLASH_BURN_FAILED); + } + + logError(0, "%s Flash burn COMPLETED!\n\n", tag); + + logError(0, "%s 6) SYSTEM RESET:\n", tag); + res = fts_system_reset(); + if (res < 0) { + logError(1, "%s system reset FAILED! ERROR %02X\n", + tag, ERROR_FLASH_BURN_FAILED); + return (res | ERROR_FLASH_BURN_FAILED); + } + logError(0, "%s system reset COMPLETED!\n\n", tag); + + + logError(0, "%s 7) FINAL CHECK:\n", tag); + res = readChipInfo(0); + if (res < 0) { + logError(1, "%s %s:Unable to retrieve Chip INFO!%02X\n", + tag, ERROR_FLASH_BURN_FAILED); + return (res | ERROR_FLASH_BURN_FAILED); + } + + if ((ftsInfo.u16_fwVer != fw->fw_ver) + && (ftsInfo.u16_cfgId != fw->config_id)) { + logError(1, "Firmware in the chip different"); + logError(1, " from the one that was burn!"); + logError(1, "%s fw: %x != %x , conf: %x != %x\n", + tag, ftsInfo.u16_fwVer, + fw->fw_ver, + ftsInfo.u16_cfgId, + fw->config_id); + return ERROR_FLASH_BURN_FAILED; + } + + logError(0, "%s Final check OK! fw: %02X, conf: %02X\n", + tag, ftsInfo.u16_fwVer, ftsInfo.u16_cfgId); + + return OK; +} + +#else + +int wait_for_flash_ready(u8 type) +{ + u8 cmd[2] = {FLASH_CMD_READ_REGISTER, type}; + u8 readData = 0; + int i, res = -1; + + logError(0, "%s Waiting for flash ready ...\n", tag); + for (i = 0; i < FLASH_RETRY_COUNT && res != 0; i++) { + if (fts_readCmd(cmd, sizeof(cmd), &readData, 1) < 0) { + logError(1, "%s %s: ERROR % 02X\n", + tag, __func__, ERROR_I2C_R); + } else { + res = readData & 0x80; + //logError(0, "%s flash status = %d\n", tag, res); + } + msleep(FLASH_WAIT_BEFORE_RETRY); + } + + if (i == FLASH_RETRY_COUNT && res != 0) { + logError(1, "%s Wait for flash TIMEOUT! ERROR %02X\n", + tag, ERROR_TIMEOUT); + return ERROR_TIMEOUT; + } + + logError(0, "%s Flash READY!\n", tag); + return OK; +} + +int fts_warm_boot(void) +{ + //write the command to perform the warm boot + u8 cmd[4] = {FTS_CMD_HW_REG_W, 0x00, 0x00, WARM_BOOT_VALUE}; + + u16ToU8_be(ADDR_WARM_BOOT, &cmd[1]); + + logError(0, "%s Command warm boot ...\n", tag); + if (fts_writeCmd(cmd, sizeof(cmd)) < 0) { + logError(1, "%s flash_unlock: ERROR % 02X\n", tag, ERROR_I2C_W); + return ERROR_I2C_W; + } + + logError(0, "%s Warm boot DONE!\n", tag); + + return OK; +} + +int parseBinFile(u8 *data, int fw_size, + struct Firmware *fwData, int keep_cx) +{ + int dimension, index = 0; + u32 temp; + int res, i; + + //the file should contain at least the header plus the content_crc + if (fw_size < FW_HEADER_SIZE+FW_BYTES_ALIGN || data == NULL) { + logError(1, "%s %s:Read only %d instead of %d...ERROR %02X\n", + tag, __func__, fw_size, + FW_HEADER_SIZE + FW_BYTES_ALIGN, + ERROR_FILE_PARSE); + res = ERROR_FILE_PARSE; + goto END; + } else { + //start parsing of bytes + u8ToU32(&data[index], &temp); + if (temp != FW_HEADER_SIGNATURE) { + logError(1, "%s %s:Wrong Signature %08X...ERROR %02X\n", + tag, __func__, temp, ERROR_FILE_PARSE); + res = ERROR_FILE_PARSE; + goto END; + } + + logError(0, "%s %s: Fw Signature OK!\n", tag, __func__); + index += FW_BYTES_ALIGN; + u8ToU32(&data[index], &temp); + if (temp != FW_FTB_VER) { + logError(1, "%s %s:Wrong ftb_version %08X.ERROR %02X\n", + tag, __func__, temp, ERROR_FILE_PARSE); + res = ERROR_FILE_PARSE; + goto END; + } + logError(0, "%s %s:ftb_version OK!\n", __func__, tag); + index += FW_BYTES_ALIGN; + if (data[index] != DCHIP_ID_0 || data[index+1] != DCHIP_ID_1) { + logError(1, "%s %s:Wrong target %02X != %02X ", + tag, __func__, data[index]); + logError(1, "%%02X != %02X:%08X\n", + DCHIP_ID_0, data[index+1], + DCHIP_ID_1, ERROR_FILE_PARSE); + res = ERROR_FILE_PARSE; + goto END; + } + index += FW_BYTES_ALIGN; + u8ToU32(&data[index], &temp); + logError(0, "%s %s: Fw ID = %08X\n", tag, __func__, temp); + index += FW_BYTES_ALIGN; + u8ToU32(&data[index], &temp); + fwData->fw_ver = temp; + logError(0, "%s %s:FILE Fw Version = %04X\n", + tag, __func__, fwData->fw_ver); + + index += FW_BYTES_ALIGN; + u8ToU32(&data[index], &temp); + fwData->config_id = temp; + logError(0, "%s %s:FILE Config ID = %04X\n", + tag, __func__, fwData->config_id); + + index += FW_BYTES_ALIGN; + u8ToU32(&data[index], &temp); + logError(0, "%s %s:Config Version = %08X\n", + tag, __func__, temp); + //skip reserved data + index += FW_BYTES_ALIGN * 2; + index += FW_BYTES_ALIGN; + logError(0, "%s %s:File External Release = ", + tag, __func__); + for (i = 0; i < EXTERNAL_RELEASE_INFO_SIZE; i++) { + fwData->externalRelease[i] = data[index++]; + logError(0, "%02X", fwData->externalRelease[i]); + } + logError(0, "\n"); + + //index += FW_BYTES_ALIGN; + u8ToU32(&data[index], &temp); + fwData->sec0_size = temp; + logError(0, "%s %s:sec0_size = %08X (%d bytes)\n", + tag, __func__, fwData->sec0_size, fwData->sec0_size); + + index += FW_BYTES_ALIGN; + u8ToU32(&data[index], &temp); + fwData->sec1_size = temp; + logError(0, "%s %s:sec1_size = %08X (%d bytes)\n", + tag, __func__, fwData->sec1_size, fwData->sec1_size); + + index += FW_BYTES_ALIGN; + u8ToU32(&data[index], &temp); + fwData->sec2_size = temp; + logError(0, "%s %s:sec2_size = %08X (%d bytes)\n", + tag, __func__, fwData->sec2_size, fwData->sec2_size); + + index += FW_BYTES_ALIGN; + u8ToU32(&data[index], &temp); + fwData->sec3_size = temp; + logError(0, "%s %s:sec3_size = %08X (%d bytes)\n", + tag, __func__, fwData->sec3_size, fwData->sec3_size); + + //skip header crc + index += FW_BYTES_ALIGN; + if (!keep_cx) { + dimension = fwData->sec0_size + fwData->sec1_size + + fwData->sec2_size + fwData->sec3_size; + temp = fw_size; + } else { + //sec2 may contain cx data (future implementation) + //sec3 atm not used + dimension = fwData->sec0_size + fwData->sec1_size; + temp = fw_size - fwData->sec2_size - fwData->sec3_size; + fwData->sec2_size = 0; + fwData->sec3_size = 0; + } + if (dimension + FW_HEADER_SIZE + FW_BYTES_ALIGN != temp) { + logError(1, "%s %s:Read only %d instead of %d...", + tag, __func__, fw_size, + dimension + FW_HEADER_SIZE + FW_BYTES_ALIGN); + logError(1, "ERROR %02X\n", ERROR_FILE_PARSE); + res = ERROR_FILE_PARSE; + goto END; + } + fwData->data = (u8 *)kmalloc_array(dimension, sizeof(u8), + GFP_KERNEL); + if (fwData->data == NULL) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_ALLOC); + res = ERROR_ALLOC; + goto END; + } + index += FW_BYTES_ALIGN; + memcpy(fwData->data, &data[index], dimension); + fwData->data_size = dimension; + logError(0, "%s READ FW DONE %d bytes!\n", + tag, fwData->data_size); + res = OK; + goto END; + } +END: + kfree(data); + return res; +} + +int flash_unlock(void) +{ + //write the command to perform the unlock + u8 cmd[3] = {FLASH_CMD_UNLOCK, + FLASH_UNLOCK_CODE0, FLASH_UNLOCK_CODE1}; + logError(0, "%s Command unlock ...\n", tag); + if (fts_writeCmd(cmd, sizeof(cmd)) < 0) { + logError(1, "%s %s: ERROR % 02X\n", tag, __func__, ERROR_I2C_W); + return ERROR_I2C_W; + } + //mdelay(FLASH_WAIT_TIME); + logError(0, "%s Unlock flash DONE!\n", tag); + + return OK; +} + +int flash_erase_unlock(void) +{ + //write the command to perform + //the unlock for erasing the flash + u8 cmd[3] = {FLASH_CMD_WRITE_REGISTER, FLASH_ERASE_UNLOCK_CODE0, + FLASH_ERASE_UNLOCK_CODE1}; + + logError(0, "%s Try to erase unlock flash...\n", tag); + + logError(0, "%s Command erase unlock ...\n", tag); + if (fts_writeCmd(cmd, sizeof(cmd)) < 0) { + logError(1, "%s %s:ERROR % 02X\n", tag, __func__, ERROR_I2C_W); + return ERROR_I2C_W; + } + + logError(0, "%s Erase Unlock flash DONE!\n", tag); + + return OK; +} + +int flash_full_erase(void) +{ + int status; + //write the command to erase the flash + u8 cmd[3] = {FLASH_CMD_WRITE_REGISTER, FLASH_ERASE_CODE0, + FLASH_ERASE_CODE1}; + + logError(0, "%s Command full erase sent...\n", + tag); + if (fts_writeCmd(cmd, sizeof(cmd)) < 0) { + logError(1, "%s %s:ERROR % 02X\n", tag, __func__, ERROR_I2C_W); + return ERROR_I2C_W; + } + + status = wait_for_flash_ready(FLASH_ERASE_CODE0); + + if (status != OK) { + logError(1, "%s %s:ERROR % 02X\n", + tag, __func__, ERROR_FLASH_NOT_READY); + //Flash not ready within the chosen time, + //better exit! + return (status | ERROR_FLASH_NOT_READY); + } + + logError(0, "%s Full Erase flash DONE!\n", tag); + + return OK; +} + +int flash_erase_page_by_page(int keep_cx) +{ + u8 status, i = 0; + //write the command to erase the flash + u8 cmd[4] = {FLASH_CMD_WRITE_REGISTER, FLASH_ERASE_CODE0, 0x00, 0x00}; + + for (i = 0; i < FLASH_NUM_PAGE; i++) { + if (i >= FLASH_CX_PAGE_START && i <= FLASH_CX_PAGE_END + && keep_cx == 1) { + logError(0, "%s Skipping erase page %d!\n", tag, i); + continue; + } + cmd[2] = (0x3F & i) | FLASH_ERASE_START; + logError(0, "Command erase page %d sent", i); + logError(0, "%s:%02X %02X %02X %02X\n", + tag, i, cmd[0], cmd[1], cmd[2], cmd[3]); + if (fts_writeCmd(cmd, sizeof(cmd)) < 0) { + logError(1, + "%s %s:ERROR % 08X\n", + tag, __func__, ERROR_I2C_W); + return ERROR_I2C_W; + } + + status = wait_for_flash_ready(FLASH_ERASE_CODE0); + if (status != OK) { + logError(1, "%s %s:ERROR % 08X\n", + tag, __func__, ERROR_FLASH_NOT_READY); + //Flash not ready within the chosen time, + //better exit! + return (status | ERROR_FLASH_NOT_READY); + } + } + + logError(0, "%s Erase flash page by page DONE!\n", tag); + + return OK; +} + +int start_flash_dma(void) +{ + int status; + //write the command to erase the flash + u8 cmd[3] = {FLASH_CMD_WRITE_REGISTER, FLASH_DMA_CODE0, + FLASH_DMA_CODE1}; + + logError(0, "%s Command flash DMA ...\n", tag); + if (fts_writeCmd(cmd, sizeof(cmd)) < 0) { + logError(1, "%s %s: ERROR % 02X\n", + tag, __func__, ERROR_I2C_W); + return ERROR_I2C_W; + } + + status = wait_for_flash_ready(FLASH_DMA_CODE0); + + if (status != OK) { + logError(1, "%s %s: ERROR % 02X\n", + tag, __func__, ERROR_FLASH_NOT_READY); + //Flash not ready within the chosen time, better exit! + return (status | ERROR_FLASH_NOT_READY); + } + + logError(0, "%s flash DMA DONE!\n", tag); + + return OK; +} + +int fillFlash(u32 address, u8 *data, int size) +{ + int remaining = size; + int toWrite = 0; + int byteBlock = 0; + int wheel = 0; + u32 addr = 0; + int res; + int delta; + u8 *buff = NULL; + u8 buff2[9] = {0}; + + + buff = (u8 *)kmalloc_array((DMA_CHUNK + 3), sizeof(u8), GFP_KERNEL); + if (buff == NULL) { + logError(1, "%s %s: ERROR %02X\n", tag, __func__, ERROR_ALLOC); + return ERROR_ALLOC; + } + + while (remaining > 0) { + byteBlock = 0; + addr = 0; + while (byteBlock < FLASH_CHUNK && remaining > 0) { + buff[0] = FLASH_CMD_WRITE_64K; + if (remaining >= DMA_CHUNK) { + if ((byteBlock + DMA_CHUNK) <= FLASH_CHUNK) { + //logError(1, "%s fillFlash:1\n", tag); + toWrite = DMA_CHUNK; + remaining -= DMA_CHUNK; + byteBlock += DMA_CHUNK; + } else { + //logError(1, "%s fillFlash:2\n", tag); + delta = FLASH_CHUNK - byteBlock; + toWrite = delta; + remaining -= delta; + byteBlock += delta; + } + } else { + if ((byteBlock + remaining) <= FLASH_CHUNK) { + //logError(1, "%s fillFlash:3\n", tag); + toWrite = remaining; + byteBlock += remaining; + remaining = 0; + } else { + //logError(1, "%s fillFlash:4\n", tag); + delta = FLASH_CHUNK - byteBlock; + toWrite = delta; + remaining -= delta; + byteBlock += delta; + } + } + + buff[1] = (u8) ((addr & 0x0000FF00) >> 8); + buff[2] = (u8) (addr & 0x000000FF); + memcpy(&buff[3], data, toWrite); + //logError(0, + //"%s Command = %02X, address = %02X %02X, + //bytes = %d, data = %02X %02X, %02X %02X\n", + //tag, buff[0], buff[1], buff[2], toWrite, + //buff[3], buff[4], buff[3 + toWrite-2], + //buff[3 + toWrite-1]); + if (fts_writeCmd(buff, 3 + toWrite) < 0) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_I2C_W); + kfree(buff); + return ERROR_I2C_W; + } + + addr += toWrite; + data += toWrite; + } + //configuring the DMA + byteBlock = byteBlock / 4 - 1; + + buff2[0] = FLASH_CMD_WRITE_REGISTER; + buff2[1] = FLASH_DMA_CONFIG; + buff2[2] = 0x00; + buff2[3] = 0x00; + + addr = address + ((wheel * FLASH_CHUNK)/4); + buff2[4] = (u8) ((addr & 0x000000FF)); + buff2[5] = (u8) ((addr & 0x0000FF00) >> 8); + buff2[6] = (u8) (byteBlock & 0x000000FF); + buff2[7] = (u8) ((byteBlock & 0x0000FF00) >> 8); + buff2[8] = 0x00; + + logError(0, "%s:Command:%02X, address:%02X %02X, ", + tag, buff2[0], buff2[5], buff2[4]); + logError(0, "words:%02X %02X\n", buff2[7], buff2[6]); + if (fts_writeCmd(buff2, 9) < OK) { + logError(1, "%s Error during filling Flash!:%02X\n", + tag, ERROR_I2C_W); + kfree(buff); + return ERROR_I2C_W; + } + //mdelay(FLASH_WAIT_TIME); + res = start_flash_dma(); + if (res < OK) { + logError(1, "%s Error during flashing DMA!:%02X\n", + tag, res); + kfree(buff); + return res; + } + wheel++; + } + kfree(buff); + return OK; +} + +int flash_burn(struct Firmware *fw, int force_burn, int keep_cx) +{ + int res; + + if (!force_burn && (ftsInfo.u16_fwVer >= fw->fw_ver) + && (ftsInfo.u16_cfgId >= fw->config_id)) { + for (res = EXTERNAL_RELEASE_INFO_SIZE-1; res >= 0; res--) { + if (fw->externalRelease[res] > + ftsInfo.u8_extReleaseInfo[res]) + goto start; + } + + logError(0, "Firmware in the chip newer or "); + logError(0, "equal to the one to burn!"); + logError(0, "%s %s:NO UPDATE ERROR %02X\n", + tag, __func__, ERROR_FW_NO_UPDATE); + return (ERROR_FW_NO_UPDATE | ERROR_FLASH_BURN_FAILED); + } + + //programming procedure start +start: + logError(0, "%s Programming Procedure for flashing started:\n\n", tag); + + logError(0, "%s 1) SYSTEM RESET:\n", tag); + + logError(0, "%s 2) WARM BOOT:\n", tag); + res = fts_warm_boot(); + if (res < OK) { + logError(1, "%s warm boot FAILED!\n", tag); + return (res | ERROR_FLASH_BURN_FAILED); + } + logError(0, "%s warm boot COMPLETED!\n\n", tag); + + //mdelay(FLASH_WAIT_TIME); + logError(0, "%s 3) FLASH UNLOCK:\n", tag); + res = flash_unlock(); + if (res < OK) { + logError(1, "%s flash unlock FAILED! ERROR %02X\n", + tag, ERROR_FLASH_BURN_FAILED); + return (res | ERROR_FLASH_BURN_FAILED); + } + logError(0, "%s flash unlock COMPLETED!\n\n", tag); + + //mdelay(200); + logError(0, "%s 4) FLASH ERASE UNLOCK:\n", tag); + res = flash_erase_unlock(); + if (res < 0) { + logError(1, "%s flash unlock FAILED! ERROR %02X\n", + tag, ERROR_FLASH_BURN_FAILED); + return (res | ERROR_FLASH_BURN_FAILED); + } + logError(0, "%s flash unlock COMPLETED!\n\n", tag); + + //mdelay(FLASH_WAIT_TIME); + logError(0, "%s 5) FLASH ERASE:\n", tag); + if (keep_cx == 1) + res = flash_erase_page_by_page(keep_cx); + else + res = flash_full_erase(); + if (res < 0) { + logError(1, "%s flash erase FAILED! ERROR %02X\n", + tag, ERROR_FLASH_BURN_FAILED); + return (res | ERROR_FLASH_BURN_FAILED); + } + logError(0, "%s flash erase COMPLETED!\n\n", tag); + + + //mdelay(FLASH_WAIT_TIME); + logError(0, "%s 6) LOAD PROGRAM:\n", tag); + res = fillFlash(FLASH_ADDR_CODE, (u8 *)(&fw->data[0]), + fw->sec0_size); + if (res < OK) { + logError(1, "%s load program ERROR %02X\n", + tag, ERROR_FLASH_BURN_FAILED); + return (res | ERROR_FLASH_BURN_FAILED); + } + logError(0, "%s load program DONE!\n", tag); + logError(0, "%s 7) LOAD CONFIG:\n", tag); + res = fillFlash(FLASH_ADDR_CONFIG, + &(fw->data[fw->sec0_size]), fw->sec1_size); + if (res < OK) { + logError(1, "%s load config ERROR %02X\n", + tag, ERROR_FLASH_BURN_FAILED); + return (res | ERROR_FLASH_BURN_FAILED); + } + logError(0, "%s load config DONE!\n", tag); + + logError(0, "%s Flash burn COMPLETED!\n\n", tag); + + logError(0, "%s 8) SYSTEM RESET:\n", tag); + res = fts_system_reset(); + if (res < 0) { + logError(1, "%s system reset FAILED! ERROR %02X\n", + tag, ERROR_FLASH_BURN_FAILED); + return (res | ERROR_FLASH_BURN_FAILED); + } + logError(0, "%s system reset COMPLETED!\n\n", tag); + + + logError(0, "%s 9) FINAL CHECK:\n", tag); + res = readChipInfo(0); + if (res < 0) { + logError(1, "%s %s:Unable to retrieve Chip INFO!:%02X\n", + tag, __func__, ERROR_FLASH_BURN_FAILED); + return (res | ERROR_FLASH_BURN_FAILED); + } + + if ((ftsInfo.u16_fwVer != fw->fw_ver) + && (ftsInfo.u16_cfgId != fw->config_id)) { + pr_err("Firmware is different from the old!\n"); + logError(1, "%s fw: %x != %x, conf: %x != %x\n", + tag, ftsInfo.u16_fwVer, fw->fw_ver, + ftsInfo.u16_cfgId, fw->config_id); + return ERROR_FLASH_BURN_FAILED; + } + + logError(0, "%s Final check OK! fw: %02X , conf: %02X\n", + tag, ftsInfo.u16_fwVer, ftsInfo.u16_cfgId); + + return OK; +} + +#endif diff --git a/qcom/opensource/touch-drivers/st/fts_lib/ftsFlash.h b/qcom/opensource/touch-drivers/st/fts_lib/ftsFlash.h new file mode 100644 index 0000000000..844c5da532 --- /dev/null +++ b/qcom/opensource/touch-drivers/st/fts_lib/ftsFlash.h @@ -0,0 +1,109 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * FTS Capacitive touch screen controller (FingerTipS) + * + * Copyright (C) 2016-2019, STMicroelectronics Limited. + * Authors: AMG(Analog Mems Group) + * + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +/** + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS API for Flashing the IC * + * * + ************************************************************************** + ************************************************************************** + * + */ + +#ifndef __FTS_FLASH_H +#define __FTS_FLASH_H + + +#include "ftsSoftware.h" + +//Flash possible status +#define FLASH_READY 0 +#define FLASH_BUSY 1 +#define FLASH_UNKNOWN -1 +#define FLASH_STATUS_BYTES 1 + + +//Flash timing parameters +#define FLASH_RETRY_COUNT 1000 +#define FLASH_WAIT_BEFORE_RETRY 50 //ms +#define FLASH_WAIT_TIME 200 //ms + + +//PATHS FW FILES +//#define PATH_FILE_FW "fw.memh" +#ifdef FTM3_CHIP +#define PATH_FILE_FW "st_fts.bin" +#else +#define PATH_FILE_FW "st_fts.ftb"//new bin file structure +#endif + +#ifndef FTM3_CHIP +#define FLASH_CHUNK (64 * 1024) +#define DMA_CHUNK (2 * 1024) +#endif + + +struct Firmware { + u8 *data; + u16 fw_ver; + u16 config_id; + u8 externalRelease[EXTERNAL_RELEASE_INFO_SIZE]; + int data_size; +#ifndef FTM3_CHIP + u32 sec0_size; + u32 sec1_size; + u32 sec2_size; + u32 sec3_size; +#endif +}; + +#ifdef FTM3_CHIP +int flash_status(void); +int flash_status_ready(void); +int wait_for_flash_ready(void); +#else +int wait_for_flash_ready(u8 type); +int fts_warm_boot(void); +int flash_erase_unlock(void); +int flash_full_erase(void); +int flash_erase_page_by_page(int keep_cx); +//int flash_erase_page_by_page_info(int page); +int start_flash_dma(void); +int fillFlash(u32 address, u8 *data, int size); +#endif + +int flash_unlock(void); +int fillMemory(u32 address, u8 *data, int size); +int getFirmwareVersion(u16 *fw_vers, u16 *config_id); +int getFWdata(const char *pathToFile, u8 **data, int *size, int from); +int getFWdata_nocheck(const char *pathToFile, u8 **data, int *size, int from); +int parseBinFile(u8 *fw_data, int fw_size, struct Firmware *fw, int keep_cx); +int readFwFile(const char *path, struct Firmware *fw, int keep_cx); +int flash_burn(struct Firmware *fw, int force_burn, int keep_cx); +int flashProcedure(const char *path, int force, int keep_cx); + +#endif diff --git a/qcom/opensource/touch-drivers/st/fts_lib/ftsFrame.c b/qcom/opensource/touch-drivers/st/fts_lib/ftsFrame.c new file mode 100644 index 0000000000..ffcf34925e --- /dev/null +++ b/qcom/opensource/touch-drivers/st/fts_lib/ftsFrame.c @@ -0,0 +1,519 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * FTS Capacitive touch screen controller (FingerTipS) + * + * Copyright (C) 2016-2019, STMicroelectronics Limited. + * Authors: AMG(Analog Mems Group) + * + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +/** + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS functions for getting frames * + * * + ************************************************************************** + ************************************************************************** + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include + +#include "ftsCrossCompile.h" +#include "ftsCompensation.h" +#include "ftsError.h" +#include "ftsFrame.h" +#include "ftsHardware.h" +#include "ftsIO.h" +#include "ftsSoftware.h" +#include "ftsTool.h" +#include "ftsTime.h" +#include "../fts.h" + +static char tag[8] = "[ FTS ]\0"; +static int sense_len, force_len; + +int getOffsetFrame(u16 address, u16 *offset) +{ + u8 data[2]; + u8 cmd = { FTS_CMD_FRAMEBUFFER_R }; + char *temp = NULL; + + if (readCmdU16(cmd, address, data, OFFSET_LENGTH, + DUMMY_FRAMEBUFFER) < 0) { + logError(1, "%s %S: ERROR %02X\n", tag, __func__, ERROR_I2C_R); + return ERROR_I2C_R; + } + + u8ToU16(data, offset); + temp = printHex("Offest = ", data, OFFSET_LENGTH); + if (temp != NULL) + logError(0, "%s %s", tag, temp); + kfree(temp); + return OK; +} + +int getChannelsLength(void) +{ + int ret; + u8 *data = (u8 *)kmalloc_array(2, sizeof(u8), GFP_KERNEL); + + if (data == NULL) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_ALLOC); + return ERROR_ALLOC; + } + + ret = readB2(ADDR_SENSE_LEN, data, 2); + if (ret < OK) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_READ_B2); + kfree(data); + return (ret|ERROR_READ_B2); + } + + sense_len = (int)data[0]; + force_len = (int)data[1]; + + logError(0, "%s Force_len = %d Sense_Len = %d\n", + tag, force_len, sense_len); + kfree(data); + + return OK; +} + + +int getFrameData(u16 address, int size, short **frame) +{ + int i, j, ret; + u8 *data = (u8 *)kmalloc_array(size, sizeof(u8), GFP_KERNEL); + + if (data == NULL) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_ALLOC); + return ERROR_ALLOC; + } + ret = readCmdU16(FTS_CMD_FRAMEBUFFER_R, address, + data, size, DUMMY_FRAMEBUFFER); + if (ret < OK) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_I2C_R); + kfree(data); + return ERROR_I2C_R; + } + j = 0; + for (i = 0; i < size; i += 2) { + (*frame)[j] = (short)((data[i + 1] << 8) + data[i]); + j++; + } + kfree(data); + return OK; +} + + +int getMSFrame(u16 type, struct MutualSenseFrame *frame, int keep_first_row) +{ + u16 offset; + int ret; + + if (getSenseLen() == 0 || getForceLen() == 0) { + ret = getChannelsLength(); + if (ret < OK) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_CH_LEN); + return (ret|ERROR_CH_LEN); + } + } + + ret = getOffsetFrame(type, &offset); + if (ret < OK) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_GET_OFFSET); + return (ret | ERROR_GET_OFFSET); + } + + switch (type) { + case ADDR_RAW_TOUCH: + case ADDR_FILTER_TOUCH: + case ADDR_NORM_TOUCH: + case ADDR_CALIB_TOUCH: + if (keep_first_row == 1) { + frame->node_data_size = ((force_len + 1) * sense_len); + frame->header.force_node = force_len + 1; + } else { + frame->node_data_size = ((force_len) * sense_len); + offset += (sense_len * BYTES_PER_NODE); + frame->header.force_node = force_len; + } + frame->header.sense_node = sense_len; + break; + case ADDR_NORM_MS_KEY: + case ADDR_RAW_MS_KEY: + frame->header.force_node = 1; + frame->header.sense_node = ftsInfo.u8_msKeyLen; + frame->node_data_size = ftsInfo.u8_msKeyLen; + break; + default: + logError(1, "%s %s: ERROR % 02X\n", + tag, __func__, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + frame->node_data = (short *)kmalloc_array(frame->node_data_size, + sizeof(short), GFP_KERNEL); + if (frame->node_data == NULL) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_ALLOC); + return ERROR_ALLOC; + } + + ret = getFrameData(offset, + frame->node_data_size * BYTES_PER_NODE, + &(frame->node_data)); + if (ret < OK) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_GET_FRAME_DATA); + kfree(frame->node_data); + return (ret | ERROR_GET_FRAME_DATA); + } + // if you want to access one node i,j, + //you should compute the offset like: + //offset = i * columns + j => frame[i, j] + logError(0, "%s Frame acquired!\n", tag); + //return the number of data put inside frame + return frame->node_data_size; +} + +int getSenseLen(void) +{ + int ret; + + if (sense_len != 0) + return sense_len; + + if (ftsInfo.u8_scrSenseLen != 0) { + sense_len = ftsInfo.u8_scrSenseLen; + } else { + ret = getChannelsLength(); + if (ret < OK) + return ret; + } + return sense_len; +} + +int getForceLen(void) +{ + int ret; + + if (force_len != 0) + return force_len; + + if (ftsInfo.u8_scrForceLen != 0) { + force_len = ftsInfo.u8_scrForceLen; + } else { + ret = getChannelsLength(); + if (ret < OK) + return ret; + } + return force_len; +} + +int requestFrame(u16 type) +{ + int retry = 0; + int ret; + u16 answer; + char *temp = NULL; + + int event_to_search[1]; + u8 readEvent[FIFO_EVENT_SIZE]; + + u8 cmd[3] = { FTS_CMD_REQU_FRAME_DATA, 0x00, 0x00}; + // B7 is the command for asking frame data + event_to_search[0] = (int)EVENTID_FRAME_DATA_READ; + + + u16ToU8(type, &cmd[1]); + + while (retry < FRAME_DATA_READ_RETRY) { + temp = printHex("Command = ", cmd, 3); + if (temp != NULL) + logError(0, "%s %s", tag, temp); + kfree(temp); + + //send the request to the chip to load in memory the Frame Data + ret = fts_writeFwCmd(cmd, 3); + if (ret < OK) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_I2C_W); + return ERROR_I2C_W; + } + ret = pollForEvent(event_to_search, + 1, + readEvent, + TIMEOUT_REQU_COMP_DATA); + if (ret < OK) { + logError(0, "%s Event did not Found at %d attemp!\n", + tag, retry + 1); + retry += 1; + } else { + retry = 0; + break; + } + } + if (retry == FRAME_DATA_READ_RETRY) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_TIMEOUT); + return ERROR_TIMEOUT; + } + u8ToU16_le(&readEvent[1], &answer); + + if (answer == type) + return OK; + + logError(1, "%s The event found has a different type of ", tag); + logError(1, "Frame data:%02X\n", ERROR_DIFF_COMP_TYPE); + return ERROR_DIFF_COMP_TYPE; + +} + + +int readFrameDataHeader(u16 type, struct DataHeader *header) +{ + u16 offset = ADDR_FRAMEBUFFER_DATA; + u16 answer; + u8 data[FRAME_DATA_HEADER]; + + if (readCmdU16(FTS_CMD_FRAMEBUFFER_R, offset, data, + FRAME_DATA_HEADER, DUMMY_FRAMEBUFFER) < 0) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_I2C_R); + return ERROR_I2C_R; + } + logError(0, "%s Read Data Header done!\n", tag); + + if (data[0] != FRAME_HEADER_SIGNATURE) { + logError(1, "%s %s %02X Wrong Header Signature !%02X != %02X\n", + tag, __func__, ERROR_WRONG_COMP_SIGN, data[0], + HEADER_SIGNATURE); + return ERROR_WRONG_COMP_SIGN; + } + + u8ToU16_le(&data[1], &answer); + + if (answer != type) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_DIFF_COMP_TYPE); + return ERROR_DIFF_COMP_TYPE; + } + + logError(0, "%s Type of Frame data OK!\n", tag); + + header->type = type; + header->force_node = (int)data[4]; + header->sense_node = (int)data[5]; + + return OK; +} + +int getMSFrame2(u16 type, struct MutualSenseFrame *frame) +{ + u16 offset = ADDR_FRAMEBUFFER_DATA+FRAME_DATA_HEADER; + int size, ret; + + frame->node_data = NULL; + + if (!(type == MS_TOUCH_ACTIVE || type == MS_TOUCH_LOW_POWER + || type == MS_TOUCH_ULTRA_LOW_POWER + || type == MS_KEY)) { + logError(1, "%s %s:Choose a MS type of frame data ERROR %02X\n", + tag, __func__, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + ret = requestFrame(type); + if (ret < 0) { + logError(1, "%s readMutualSenseCompensation:ERROR %02X\n", + tag, ERROR_REQU_COMP_DATA); + return (ret | ERROR_REQU_COMP_DATA); + } + + ret = readFrameDataHeader(type, &(frame->header)); + if (ret < 0) { + logError(1, "%s readMutualSenseCompensationData:ERROR %02X\n", + tag, ERROR_COMP_DATA_HEADER); + return (ret | ERROR_COMP_DATA_HEADER); + } + + switch (type) { + case MS_TOUCH_ACTIVE: + case MS_TOUCH_LOW_POWER: + case MS_TOUCH_ULTRA_LOW_POWER: + size = frame->header.force_node * frame->header.sense_node; + break; + case MS_KEY: + //or use directly the number in the ftsChip + if (frame->header.force_node > frame->header.sense_node) + size = frame->header.force_node; + else + size = frame->header.sense_node; + frame->header.force_node = 1; + frame->header.sense_node = size; + break; + + default: + logError(1, "%s %s: ERROR % 02X\n", + tag, __func__, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + frame->node_data = (short *)kmalloc_array(size, + sizeof(short), GFP_KERNEL); + if (frame->node_data == NULL) { + logError(1, "%s %s: ERROR %02X\n", tag, __func__, ERROR_ALLOC); + return ERROR_ALLOC; + } + + ret = getFrameData(offset, size*BYTES_PER_NODE, &(frame->node_data)); + if (ret < OK) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_GET_FRAME_DATA); + kfree(frame->node_data); + return (ret | ERROR_GET_FRAME_DATA); + } + // if you want to access one node i,j, + //you should compute the offset like: + //offset = i * columns + j = > frame[i, j] + logError(0, "%s Frame acquired!\n", tag); + frame->node_data_size = size; + return size;//return the number of data put inside frame + +} + +int getSSFrame2(u16 type, struct SelfSenseFrame *frame) +{ + u16 offset = ADDR_FRAMEBUFFER_DATA + FRAME_DATA_HEADER; + int size, ret; + short *temp = NULL; + + frame->force_data = NULL; + frame->sense_data = NULL; + + if (!(type == SS_TOUCH || type == SS_KEY || type == SS_HOVER + || type == SS_PROXIMITY)) { + logError(1, "%s %s:Choose a SS type of frame data ERROR %02X\n", + tag, __func__, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + ret = requestFrame(type); + if (ret < 0) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_REQU_COMP_DATA); + return (ret | ERROR_REQU_COMP_DATA); + } + + ret = readFrameDataHeader(type, &(frame->header)); + if (ret < 0) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_COMP_DATA_HEADER); + return (ret | ERROR_COMP_DATA_HEADER); + } + + switch (type) { + case SS_TOUCH: + case SS_HOVER: + case SS_PROXIMITY: + size = frame->header.force_node + frame->header.sense_node; + break; + + default: + logError(1, "%s %s: ERROR % 02X\n", + tag, __func__, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + + temp = (short *)kmalloc_array(size, sizeof(short), GFP_KERNEL); + if (temp == NULL) { + logError(1, "%s %s: temp ERROR %02X\n", + tag, __func__, ERROR_ALLOC); + return ERROR_ALLOC; + } + + ret = getFrameData(offset, size*BYTES_PER_NODE, &temp); + if (ret < OK) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_GET_FRAME_DATA); + kfree(temp); + return (ret | ERROR_GET_FRAME_DATA); + } + + frame->force_data = (short *)kmalloc_array(frame->header.force_node, + sizeof(short), GFP_KERNEL); + if (frame->force_data == NULL) { + logError(1, "%s %s: frame->force_data ERROR %02X\n", + tag, __func__, ERROR_ALLOC); + kfree(temp); + return ERROR_ALLOC; + } + + memcpy(frame->force_data, temp, + frame->header.force_node * sizeof(short)); + + frame->sense_data = (short *)kmalloc_array(frame->header.sense_node, + sizeof(short), GFP_KERNEL); + if (frame->sense_data == NULL) { + logError(1, "%s %s: frame->sense_data ERROR %02X\n", + tag, __func__, ERROR_ALLOC); + kfree(temp); + kfree(frame->force_data); + return ERROR_ALLOC; + } + + memcpy(frame->sense_data, &temp[frame->header.force_node], + frame->header.sense_node * sizeof(short)); + + logError(0, "%s Frame acquired!\n", tag); + kfree(temp); + return size; //return the number of data put inside frame +} diff --git a/qcom/opensource/touch-drivers/st/fts_lib/ftsFrame.h b/qcom/opensource/touch-drivers/st/fts_lib/ftsFrame.h new file mode 100644 index 0000000000..6945a619eb --- /dev/null +++ b/qcom/opensource/touch-drivers/st/fts_lib/ftsFrame.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * FTS Capacitive touch screen controller (FingerTipS) + * + * Copyright (C) 2016-2019, STMicroelectronics Limited. + * Authors: AMG(Analog Mems Group) + * + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +/** + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS functions for getting frames * + * * + ************************************************************************** + ************************************************************************** + */ + +#ifndef __FTS_FRAME_H +#define __FTS_FRAME_H + +#include "ftsSoftware.h" + +//Number of data bytes for each node +#define BYTES_PER_NODE 2 +#define OFFSET_LENGTH 2 +#define FRAME_DATA_HEADER 8 +#define FRAME_HEADER_SIGNATURE 0xB5 +#define FRAME_DATA_READ_RETRY 2 + +struct MutualSenseFrame { + struct DataHeader header; + short *node_data; + int node_data_size; +}; + +struct SelfSenseFrame { + struct DataHeader header; + short *force_data; + short *sense_data; +}; + +int getOffsetFrame(u16 address, u16 *offset); +int getChannelsLength(void); +int getFrameData(u16 address, int size, short **frame); +int getMSFrame(u16 type, struct MutualSenseFrame *frame, int keep_first_row); +//int getMSKeyFrame(u16 type, short **frame); +//int getSSFrame(u16 type, short **frame); +//int getNmsFrame(u16 type, short ***frames, int * sizes, +//int keep_first_row, int fs, int n); +int getSenseLen(void); +int getForceLen(void); +int requestFrame(u16 type); +int readFrameDataHeader(u16 type, struct DataHeader *header); +int getMSFrame2(u16 type, struct MutualSenseFrame *frame); +int getSSFrame2(u16 type, struct SelfSenseFrame *frame); + +#endif + diff --git a/qcom/opensource/touch-drivers/st/fts_lib/ftsGesture.c b/qcom/opensource/touch-drivers/st/fts_lib/ftsGesture.c new file mode 100644 index 0000000000..dfd2dae00c --- /dev/null +++ b/qcom/opensource/touch-drivers/st/fts_lib/ftsGesture.c @@ -0,0 +1,651 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * FTS Capacitive touch screen controller (FingerTipS) + * + * Copyright (C) 2016-2019, STMicroelectronics Limited. + * Authors: AMG(Analog Mems Group) + * + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +/** + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS Gesture Utilities * + * * + ************************************************************************** + ************************************************************************** + */ + +#include "ftsSoftware.h" +#include "ftsError.h" +#include "ftsGesture.h" +#include "ftsIO.h" +#include "ftsTool.h" + + +static char tag[8] = "[ FTS ]\0"; + +static u8 gesture_mask[GESTURE_MASK_SIZE] = { 0 }; +static u8 custom_gestures[GESTURE_CUSTOM_NUMBER][GESTURE_CUSTOM_POINTS]; +static u8 custom_gesture_index[GESTURE_CUSTOM_NUMBER] = { 0 }; +static int refreshGestureMask; + +u16 gesture_coordinates_x[GESTURE_COORDS_REPORT_MAX] = {0}; +u16 gesture_coordinates_y[GESTURE_COORDS_REPORT_MAX] = {0}; +int gesture_coords_reported = ERROR_OP_NOT_ALLOW; +struct mutex gestureMask_mutex; + + +int updateGestureMask(u8 *mask, int size, int en) +{ + u8 temp; + int i; + + if (mask == NULL) { + logError(1, "%s %s: Mask NULL! ERROR %08X\n", + tag, __func__, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + if (size > GESTURE_MASK_SIZE) { + logError(1, "%s %s:Size not valid! %d > %d ERROR %08X\n", + tag, __func__, size, GESTURE_MASK_SIZE); + return ERROR_OP_NOT_ALLOW; + } + + if (en == FEAT_ENABLE) { + mutex_lock(&gestureMask_mutex); + logError(0, "%s %s:setting gesture mask to enable..\n", + tag, __func__); + if (mask != NULL) { + for (i = 0; i < size; i++) { + //back up the gesture enabled + gesture_mask[i] = gesture_mask[i] | mask[i]; + } + } + refreshGestureMask = 1; + logError(0, "%s %s:gesture mask to enable SET!\n", + tag, __func__); + mutex_unlock(&gestureMask_mutex); + return OK; + } + if (en == FEAT_DISABLE) { + mutex_lock(&gestureMask_mutex); + logError(0, "%s %s:setting gesture ", tag, __func__); + logError(0, "mask to disable...\n"); + for (i = 0; i < size; i++) { + // enabled XOR disabled + temp = gesture_mask[i] ^ mask[i]; + gesture_mask[i] = temp & gesture_mask[i]; + } + logError(0, "%s %s:gesture mask to disable SET!\n", + tag, __func__); + refreshGestureMask = 1; + mutex_unlock(&gestureMask_mutex); + return OK; + } + logError(1, "%s%s:Enable parameter Invalid%d!=%d or%d%:08X", + tag, __func__, FEAT_DISABLE, + FEAT_ENABLE, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + +} + +int enableGesture(u8 *mask, int size) +{ + u8 cmd[GESTURE_MASK_SIZE + 2]; + u8 readData[FIFO_EVENT_SIZE] = {0}; + int i, res; + int event_to_search[4] = { EVENTID_GESTURE, + EVENT_TYPE_ENB, 0x00, GESTURE_ENABLE }; + + logError(0, "%s Trying to enable gesture...\n", tag); + cmd[0] = FTS_CMD_GESTURE_CMD; + cmd[1] = GESTURE_ENABLE; + + + if (size > GESTURE_MASK_SIZE) { + logError(1, "%s %s: Size not valid! %d > %d ERROR %08X\n", + tag, __func__, size, GESTURE_MASK_SIZE); + return ERROR_OP_NOT_ALLOW; + } + + mutex_lock(&gestureMask_mutex); + if (mask != NULL) { + for (i = 0; i < size; i++) { + cmd[i + 2] = mask[i]; + //back up of the gesture enabled + gesture_mask[i] = gesture_mask[i] | mask[i]; + } + while (i < GESTURE_MASK_SIZE) { + cmd[i + 2] = gesture_mask[i]; + i++; + } + } else { + for (i = 0; i < GESTURE_MASK_SIZE; i++) + cmd[i + 2] = gesture_mask[i]; + } + + res = fts_writeFwCmd(cmd, GESTURE_MASK_SIZE + 2); + if (res < OK) { + logError(1, "%s %s: ERROR %08X\n", tag, __func__, res); + goto END; + } + + res = pollForEvent(event_to_search, 4, + readData, GENERAL_TIMEOUT); + if (res < OK) { + logError(1, "%s %s: pollForEvent ERROR %08X\n", + tag, __func__, res); + goto END; + } + + if (readData[4] != 0x00) { + logError(1, "%s %s: ERROR %08X\n", + tag, __func__, ERROR_GESTURE_ENABLE_FAIL); + res = ERROR_GESTURE_ENABLE_FAIL; + goto END; + } + + logError(0, "%s %s: DONE!\n", tag, __func__); + res = OK; + +END: + mutex_unlock(&gestureMask_mutex); + return res; +} + + +int disableGesture(u8 *mask, int size) +{ + u8 cmd[2 + GESTURE_MASK_SIZE]; + u8 readData[FIFO_EVENT_SIZE] = {0}; + u8 temp; + int i, res; + int event_to_search[4] = { EVENTID_GESTURE, + EVENT_TYPE_ENB, 0x00, GESTURE_DISABLE }; + + logError(0, "%s Trying to disable gesture...\n", tag); + cmd[0] = FTS_CMD_GESTURE_CMD; + cmd[1] = GESTURE_DISABLE; + + if (size > GESTURE_MASK_SIZE) { + logError(1, "%s %s: Size not valid! %d > %d ERROR %08X\n", + tag, __func__, size, GESTURE_MASK_SIZE); + return ERROR_OP_NOT_ALLOW; + } + mutex_lock(&gestureMask_mutex); + if (mask != NULL) { + for (i = 0; i < size; i++) { + cmd[i + 2] = mask[i]; + // enabled XOR disabled + temp = gesture_mask[i] ^ mask[i]; + gesture_mask[i] = temp & gesture_mask[i]; + } + while (i < GESTURE_MASK_SIZE) { + //cmd[i + 2] = gesture_mask[i]; + //gesture_mask[i] = 0x00; + + cmd[i + 2] = 0x00; + //leave untouched the gestures not specified + + i++; + } + } else { + for (i = 0; i < GESTURE_MASK_SIZE; i++) { + //cmd[i + 2] = gesture_mask[i]; + cmd[i + 2] = 0xFF; + } + } + + res = fts_writeFwCmd(cmd, 2 + GESTURE_MASK_SIZE); + if (res < OK) { + logError(1, "%s %s:ERROR %08X\n", tag, __func__, res); + goto END; + } + + res = pollForEvent(event_to_search, 4, readData, GENERAL_TIMEOUT); + if (res < OK) { + logError(1, "%s %s: pollForEvent ERROR %08X\n", + tag, __func__, res); + goto END; + } + + if (readData[4] != 0x00) { + logError(1, "%s %s:ERROR %08X\n", + tag, __func__, ERROR_GESTURE_ENABLE_FAIL); + res = ERROR_GESTURE_ENABLE_FAIL; + goto END; + } + + logError(0, "%s %s: DONE!\n", tag, __func__); + res = OK; +END: + mutex_unlock(&gestureMask_mutex); + return res; + +} + +int startAddCustomGesture(u8 gestureID) +{ + u8 cmd[3] = { FTS_CMD_GESTURE_CMD, GESTURE_START_ADD, gestureID }; + int res; + u8 readData[FIFO_EVENT_SIZE] = {0}; + int event_to_search[4] = { EVENTID_GESTURE, EVENT_TYPE_ENB, + gestureID, GESTURE_START_ADD }; + + res = fts_writeFwCmd(cmd, 3); + if (res < OK) { + logError(1, + "%s%s:Impossible to start adding custom gesture ID:%02X %08X\n", + tag, __func__, gestureID, res); + return res; + } + + res = pollForEvent(event_to_search, 4, readData, GENERAL_TIMEOUT); + if (res < OK) { + logError(1, "%s %s:start add event not found! ERROR %08X\n", + tag, __func__, res); + return res; + } + //check of gestureID is redundant + if (readData[2] != gestureID || readData[4] != 0x00) { + logError(1, "%s %s:start add event status not OK! ERROR %08X\n", + tag, __func__, readData[4]); + return ERROR_GESTURE_START_ADD; + } + + return OK; +} + +int finishAddCustomGesture(u8 gestureID) +{ + u8 cmd[3] = { FTS_CMD_GESTURE_CMD, + GESTURE_FINISH_ADD, gestureID }; + int res; + u8 readData[FIFO_EVENT_SIZE] = {0}; + int event_to_search[4] = { EVENTID_GESTURE, EVENT_TYPE_ENB, + gestureID, GESTURE_FINISH_ADD }; + + res = fts_writeFwCmd(cmd, 3); + if (res < OK) { + logError(1, + "%s%s:Impossible to finish adding custom gestureID:%02X %08X\n", + tag, __func__, gestureID, res); + return res; + } + + res = pollForEvent(event_to_search, 4, readData, GENERAL_TIMEOUT); + if (res < OK) { + logError(1, "%s %s: finish add event not found! ERROR %08X\n", + tag, __func__, res); + return res; + } + //check of gestureID is redundant + if (readData[2] != gestureID || readData[4] != 0x00) { + logError(1, + "%s %s:finish add event status not OK! ERROR %08X\n", + tag, __func__, readData[4]); + return ERROR_GESTURE_FINISH_ADD; + } + + return OK; +} + +int loadCustomGesture(u8 *template, u8 gestureID) +{ + int res, i, wheel; + int remaining = GESTURE_CUSTOM_POINTS; + int toWrite, offset = 0; + u8 cmd[TEMPLATE_CHUNK + 5]; + int event_to_search[4] = { EVENTID_GESTURE, EVENT_TYPE_ENB, + gestureID, GESTURE_DATA_ADD }; + u8 readData[FIFO_EVENT_SIZE] = {0}; + + logError(0, "%s Starting adding custom gesture procedure...\n", tag); + + res = startAddCustomGesture(gestureID); + if (res < OK) { + logError(1, "%s %s:unable to start adding procedure %08X\n", + tag, __func__, res); + return res; + } + + cmd[0] = FTS_CMD_GESTURE_CMD; + cmd[1] = GESTURE_DATA_ADD; + cmd[2] = gestureID; + wheel = 0; + while (remaining > 0) { + if (remaining > TEMPLATE_CHUNK) + toWrite = TEMPLATE_CHUNK; + else + toWrite = remaining; + + cmd[3] = toWrite; + cmd[4] = offset; + for (i = 0; i < toWrite; i++) + cmd[i + 5] = template[wheel++]; + + res = fts_writeFwCmd(cmd, toWrite + 5); + if (res < OK) { + logError(1, "%s %s:unable to start ", tag, __func__); + logError(1, "adding procedure %08X\n", res); + return res; + } + + res = pollForEvent(event_to_search, + 4, + readData, + GENERAL_TIMEOUT); + if (res < OK) { + logError(1, "%s %s: add event not found! ERROR %08X\n", + tag, __func__, res); + return res; + } + //check of gestureID is redundant + if (readData[2] != gestureID || readData[4] != 0x00) { + logError(1, "%s %s:add event status not OK! ", + tag, __func__); + logError(1, "ERROR %08X\n", readData[4]); + return ERROR_GESTURE_DATA_ADD; + } + + remaining -= toWrite; + offset += toWrite / 2; + } + + res = finishAddCustomGesture(gestureID); + if (res < OK) { + logError(1, "%s %s:unable to finish adding procedure! ", + tag, __func__); + logError(1, "ERROR %08X\n", res); + return res; + } + + logError(0, "%s Adding custom gesture procedure DONE!\n", tag); + return OK; + +} + + +int reloadCustomGesture(void) +{ + int res, i; + + logError(0, "%s Starting reload Gesture Template...\n", tag); + + for (i = 0; i < GESTURE_CUSTOM_NUMBER; i++) { + if (custom_gesture_index[i] == 1) { + res = loadCustomGesture(custom_gestures[i], + GESTURE_CUSTOM_OFFSET + i); + if (res < OK) { + logError(1, "%s %s:Impossible load gesture ", + tag, __func__); + logError(1, "D:%02X %08X\n", + GESTURE_CUSTOM_OFFSET + i, res); + return res; + } + } + } + + logError(0, "%s Reload Gesture Template DONE!\n", tag); + return OK; + +} + +int enterGestureMode(int reload) +{ + u8 cmd = FTS_CMD_GESTURE_MODE; + int res, ret; + + res = fts_disableInterrupt(); + if (res < OK) { + logError(1, "%s %s: ERROR %08X\n", + tag, __func__, res | ERROR_DISABLE_INTER); + return res | ERROR_DISABLE_INTER; + } + + if (reload == 1 || refreshGestureMask == 1) { + if (reload == 1) { + res = reloadCustomGesture(); + if (res < OK) { + logError(1, "%s %s:impossible reload ", + tag, __func__); + logError(1, "custom gesture %08X\n", res); + goto END; + } + } + + /** + * mandatory steps to set the correct gesture + * mask defined by the user + */ + res = disableGesture(NULL, 0); + if (res < OK) { + logError(1, "%s %s:disableGesture ERROR %08X\n", + tag, res); + goto END; + } + + res = enableGesture(NULL, 0); + if (res < OK) { + logError(1, "%s %s:enableGesture ERROR %08X\n", + tag, __func__, res); + goto END; + } + + refreshGestureMask = 0; + /**************************************************/ + } + + res = fts_writeFwCmd(&cmd, 1); + if (res < OK) { + logError(1, "%s %s:enter gesture mode ERROR %08X\n", + tag, __func__, res); + goto END; + } + + res = OK; +END: + ret = fts_enableInterrupt(); + if (ret < OK) { + logError(1, "%s %s:fts_enableInterrupt ERROR %08X\n", + tag, __func__, res | ERROR_ENABLE_INTER); + res |= ret | ERROR_ENABLE_INTER; + } + + return res; +} + +int addCustomGesture(u8 *data, int size, u8 gestureID) +{ + int index, res, i; + + index = gestureID - GESTURE_CUSTOM_OFFSET; + + logError(0, "%s Starting Custom Gesture Adding procedure...\n", tag); + if (size != GESTURE_CUSTOM_POINTS || (gestureID != GES_ID_CUST1 + && gestureID != GES_ID_CUST2 && gestureID != GES_ID_CUST3 + && gestureID != GES_ID_CUST4 && gestureID != GES_ID_CUST5)) { + logError(1, "%s %s:Invalid size(%d) or Custom GestureID ", + tag, __func__, size); + logError(1, "(%02X)!ERROR%08X\n", + size, gestureID, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + for (i = 0; i < GESTURE_CUSTOM_POINTS; i++) + custom_gestures[index][i] = data[i]; + + res = loadCustomGesture(custom_gestures[index], gestureID); + if (res < OK) { + logError(1, "%s %s:impossible to load the custom gesture! ", + tag, __func__); + logError(1, "ERROR %08X\n", res); + return res; + } + + custom_gesture_index[index] = 1; + logError(0, "%s Custom Gesture Adding procedure DONE!\n", tag); + return OK; +} + +int removeCustomGesture(u8 gestureID) +{ + int res, index; + u8 cmd[3] = { FTS_CMD_GESTURE_CMD, GETURE_REMOVE_CUSTOM, gestureID }; + int event_to_search[4] = { EVENTID_GESTURE, EVENT_TYPE_ENB, + gestureID, GETURE_REMOVE_CUSTOM }; + u8 readData[FIFO_EVENT_SIZE] = {0}; + + index = gestureID - GESTURE_CUSTOM_OFFSET; + + logError(0, "%s Starting Custom Gesture Removing procedure...\n", tag); + if (gestureID != GES_ID_CUST1 && gestureID != GES_ID_CUST2 && + gestureID != GES_ID_CUST3 && gestureID != + GES_ID_CUST4 && gestureID != GES_ID_CUST5) { + logError(1, "%s %s:Invalid Custom GestureID (%02X)! ", + tag, __func__, gestureID); + logError(1, "ERROR %08X\n", ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + //when a gesture is removed, it is also disabled automatically + res = fts_writeFwCmd(cmd, 3); + if (res < OK) { + logError(1, "%s %s:Impossible to remove custom ", + tag, __func__); + logError(1, "%gesture ID:%02X %08X\n", gestureID, res); + return res; + } + + res = pollForEvent(event_to_search, 4, readData, GENERAL_TIMEOUT); + if (res < OK) { + logError(1, "%s %s:remove event not found! ERROR %08X\n", + tag, __func__, res); + return res; + } + //check of gestureID is redundant + if (readData[2] != gestureID || readData[4] != 0x00) { + logError(1, "%s %s:remove event status not OK! ERROR %08X\n", + tag, __func__, readData[4]); + return ERROR_GESTURE_REMOVE; + } + + custom_gesture_index[index] = 0; + logError(0, "%s Custom Gesture Remove procedure DONE!\n", tag); + return OK; + +} + +int isAnyGestureActive(void) +{ + int res = 0; + /*-1 because in any case the last gesture mask byte will*/ + /*be evaluated with the following if*/ + while (res < (GESTURE_MASK_SIZE - 1) && gesture_mask[res] == 0) + res++; + + if (gesture_mask[res] == 0) { + logError(0, "%s %s: All Gestures Disabled!\n", tag, __func__); + return FEAT_DISABLE; + } + + logError(0, "%s %s:Active Gestures Found! gesture_mask[%d] = %02X!\n", + tag, __func__, res, gesture_mask[res]); + return FEAT_ENABLE; +} + +int gestureIDtoGestureMask(u8 id, u8 *mask) +{ + logError(0, "%s %s: Index = %d Position = %d!\n", + tag, __func__, ((int)((id) / 8)), (id % 8)); + mask[((int)((id) / 8))] |= 0x01 << (id % 8); + return OK; +} + + +int readGestureCoords(u8 *event) +{ + int i = 0; + u8 rCmd[3] = {FTS_CMD_FRAMEBUFFER_R, 0x00, 0x00 }; + int res; + unsigned char val[GESTURE_COORDS_REPORT_MAX * 4 + 1]; + + //the max coordinates to read are GESTURE_COORDS_REPORT_MAX*4 + //(because each coordinate is a short(*2) and we have x and y) + //+ dummy byte + if (event[0] != EVENTID_GESTURE + || event[1] != EVENT_TYPE_GESTURE_DTC2) { + + logError(1, "%s %s:The event passsed as argument is invalid! ", + tag, __func__); + logError(1, "ERROR %08X\n", ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + rCmd[1] = event[4]; // Offset address L + rCmd[2] = event[3]; // Offset address H + //number of coords reported L + gesture_coords_reported = event[6]; + //number of coords reported H + gesture_coords_reported = (gesture_coords_reported << 8) | event[5]; + if (gesture_coords_reported > GESTURE_COORDS_REPORT_MAX) { + logError(1, "%s%s:FW reported more than:%d points ", + tag, __func__, gesture_coords_reported); + logError(1, "for gestures!\n"); + logError(1, " Decreasing to %d\n", GESTURE_COORDS_REPORT_MAX); + gesture_coords_reported = GESTURE_COORDS_REPORT_MAX; + } + + logError(1, "%s %s: Offset: %02X %02X points = %d\n", tag, + __func__, rCmd[1], rCmd[2], gesture_coords_reported); + res = fts_readCmd(rCmd, 3, (unsigned char *)val, + 1 + (gesture_coords_reported * 2)); + if (res < OK) { + logError(1, "%s %s: Cannot read the coordinates! ERROR %08X\n", + tag, __func__, res); + gesture_coords_reported = ERROR_OP_NOT_ALLOW; + return res; + } + //all the points of the gesture are stored in val + for (i = 0; i < gesture_coords_reported; i++) { + //ignore first byte data because it is a dummy byte + gesture_coordinates_x[i] = (((u16) val[i * 2 + 1 + 1]) + & 0x0F) << 8 | (((u16) val[i * 2 + 1]) & 0xFF); + gesture_coordinates_y[i] = + (((u16)val[gesture_coords_reported * 2 + + i * 2 + 1 + 1]) & 0x0F) << 8 + | (((u16)val[gesture_coords_reported * 2 + + i * 2 + 1]) & 0xFF); + } + + logError(1, "%s %s: Reading Gesture Coordinates DONE!\n", + tag, __func__); + return OK; +} + +int getGestureCoords(u16 *x, u16 *y) +{ + x = gesture_coordinates_x; + y = gesture_coordinates_y; + logError(1, "%s %s:Number of gesture coordinates returned = %d\n", + tag, __func__, gesture_coords_reported); + return gesture_coords_reported; +} diff --git a/qcom/opensource/touch-drivers/st/fts_lib/ftsGesture.h b/qcom/opensource/touch-drivers/st/fts_lib/ftsGesture.h new file mode 100644 index 0000000000..c5c2a6abbf --- /dev/null +++ b/qcom/opensource/touch-drivers/st/fts_lib/ftsGesture.h @@ -0,0 +1,123 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * FTS Capacitive touch screen controller (FingerTipS) + * + * Copyright (C) 2016-2019, STMicroelectronics Limited. + * Authors: AMG(Analog Mems Group) + * + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +/** + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS Gesture Utilities * + * * + ************************************************************************** + ************************************************************************** + */ + +#ifndef _FTS_GESTURE_H_ +#define _FTS_GESTURE_H_ + +#include "ftsHardware.h" + +#define GESTURE_MASK_SIZE 8 + +//max number of gestures coordinates reported +#define GESTURE_COORDS_REPORT_MAX 32 + +// for each custom gesture should be provided 30 +//points (each point is a couple of x,y) +#define GESTURE_CUSTOM_POINTS (30 * 2) + +//fw support up to 5 custom gestures +#define GESTURE_CUSTOM_NUMBER 5 + +#ifdef FTM3 + +//number of points to transfer with each I2C transaction +#define TEMPLATE_CHUNK (10 * 2) +#else +//number of points to transfer with each I2C transaction +#define TEMPLATE_CHUNK (5 * 2) +#endif + + +//Gesture IDs +#define GES_ID_UNKNOWN 0x00 //no meaningful gesture +#define GES_ID_DBLTAP 0x01 //Double Tap +#define GES_ID_O 0x02 //'O' +#define GES_ID_C 0x03 //'C' +#define GES_ID_M 0x04 //'M' +#define GES_ID_W 0x05 //'W' +#define GES_ID_E 0x06 //'e' +#define GES_ID_HFLIP_L2R 0x07 //Left to right line +#define GES_ID_HFLIP_R2L 0x08 //Right to left line +#define GES_ID_VFLIP_T2D 0x09 //Top to bottom line +#define GES_ID_VFLIP_D2T 0x0A //Bottom to Top line +#define GES_ID_L 0x0B //'L' +#define GES_ID_F 0x0C //'F' +#define GES_ID_V 0x0D //'V' +#define GES_ID_AT 0x0E //'@' +#define GES_ID_S 0x0F //'S' +#define GES_ID_Z 0x10 //'Z' +#define GES_ID_CUST1 0x11 //Custom gesture 1 +#define GES_ID_CUST2 0x12 //Custom gesture 2 +#define GES_ID_CUST3 0x13 //Custom gesture 3 +#define GES_ID_CUST4 0x14 //Custom gesture 4 +#define GES_ID_CUST5 0x15 //Custom gesture 5 +#define GES_ID_LEFTBRACE 0x20 //'<' +#define GES_ID_RIGHTBRACE 0x21 //'>' + +#define GESTURE_CUSTOM_OFFSET GES_ID_CUST1 + +//Command sub-type +#define GESTURE_ENABLE 0x01 +#define GESTURE_DISABLE 0x02 +#define GESTURE_ENB_CHECK 0x03 +#define GESTURE_START_ADD 0x10 +#define GESTURE_DATA_ADD 0x11 +#define GESTURE_FINISH_ADD 0x12 +#define GETURE_REMOVE_CUSTOM 0x13 +#define GESTURE_CHECK_CUSTOM 0x14 + + +//Event sub-type +#define EVENT_TYPE_ENB 0x04 +#define EVENT_TYPE_CHECK_ENB 0x03 +#define EVENT_TYPE_GESTURE_DTC1 0x01 +#define EVENT_TYPE_GESTURE_DTC2 0x02 + +int updateGestureMask(u8 *mask, int size, int en); +int disableGesture(u8 *mask, int size); +int enableGesture(u8 *mask, int size); +int startAddCustomGesture(u8 gestureID); +int finishAddCustomGesture(u8 gestureID); +int loadCustomGesture(u8 *template, u8 gestureID); +int reloadCustomGesture(void); +int enterGestureMode(int reload); +int addCustomGesture(u8 *data, int size, u8 gestureID); +int removeCustomGesture(u8 gestureID); +int isAnyGestureActive(void); +int gestureIDtoGestureMask(u8 id, u8 *mask); +int readGestureCoords(u8 *event); +int getGestureCoords(u16 *x, u16 *y); + +#endif diff --git a/qcom/opensource/touch-drivers/st/fts_lib/ftsHardware.h b/qcom/opensource/touch-drivers/st/fts_lib/ftsHardware.h new file mode 100644 index 0000000000..553cf6d576 --- /dev/null +++ b/qcom/opensource/touch-drivers/st/fts_lib/ftsHardware.h @@ -0,0 +1,213 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * FTS Capacitive touch screen controller (FingerTipS) + * + * Copyright (C) 2016-2019, STMicroelectronics Limited. + * Authors: AMG(Analog Mems Group) + * + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +/** + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * HW related data ** + * * + ************************************************************************** + ************************************************************************** + */ + +#ifndef __FTS_HARDWARE_H +#define __FTS_HARDWARE_H + +//DUMMY BYTES DATA +#define DUMMY_HW_REG 1 +#define DUMMY_FRAMEBUFFER 1 +#define DUMMY_MEMORY 1 + +//DIGITAL CHIP INFO +#ifdef FTM3_CHIP +#define DCHIP_ID_0 0x39 +#define DCHIP_ID_1 0x6C +#else +#define DCHIP_ID_0 0x36 +#define DCHIP_ID_1 0x70 +#endif + +#ifdef FTM3_CHIP +#define DCHIP_ID_ADDR 0x0007 +#define DCHIP_FW_VER_ADDR 0x000A +#else +#define DCHIP_ID_ADDR 0x0004 +#define DCHIP_FW_VER_ADDR 0x0008 +#endif + +#define DCHIP_FW_VER_BYTE 2 + +//CHUNKS +#define READ_CHUNK (2 * 1024) +#define WRITE_CHUNK (2 * 1024) +#define MEMORY_CHUNK (2 * 1024) + +//PROTOCOL INFO +#ifdef FTM3_CHIP +#define I2C_SAD 0x49 +#else +#define I2C_SAD 0x49 +#endif + +#define I2C_INTERFACE //comment if the chip use SPI +#define ICR_ADDR 0x0024 +#define ICR_SPI_VALUE 0x02 + +//SYSTEM RESET INFO +#ifdef FTM3_CHIP +#define SYSTEM_RESET_ADDRESS 0x0023 +#define SYSTEM_RESET_VALUE 0x01 +#else +#define SYSTEM_RESET_ADDRESS 0x0028 +#define SYSTEM_RESET_VALUE 0x80 +#endif + +//INTERRUPT INFO +#ifdef FTM3_CHIP +#define IER_ADDR 0x001C +#else +#define IER_ADDR 0x002C +#endif + +#define IER_ENABLE 0x41 +#define IER_DISABLE 0x00 + +//FLASH COMMAND + +#define FLASH_CMD_UNLOCK 0xF7 + + +#ifdef FTM3_CHIP +#define FLASH_CMD_WRITE_LOWER_64 0xF0 +#define FLASH_CMD_WRITE_UPPER_64 0xF1 +#define FLASH_CMD_BURN 0xF2 +#define FLASH_CMD_ERASE 0xF3 +#define FLASH_CMD_READSTATUS 0xF4 +#else +#define FLASH_CMD_WRITE_64K 0xF8 +#define FLASH_CMD_READ_REGISTER 0xF9 +#define FLASH_CMD_WRITE_REGISTER 0xFA +#endif + +//FLASH UNLOCK PARAMETER +#define FLASH_UNLOCK_CODE0 0x74 +#define FLASH_UNLOCK_CODE1 0x45 + +#ifndef FTM3_CHIP +//FLASH ERASE and DMA PARAMETER +#define FLASH_ERASE_UNLOCK_CODE0 0x72 +#define FLASH_ERASE_UNLOCK_CODE1 0x03 +#define FLASH_ERASE_UNLOCK_CODE2 0x02 +#define FLASH_ERASE_CODE0 0x02 +#define FLASH_ERASE_CODE1 0xC0 +#define FLASH_DMA_CODE0 0x05 +#define FLASH_DMA_CODE1 0xC0 +#define FLASH_DMA_CONFIG 0x06 +#define FLASH_ERASE_START 0x80 +#define FLASH_NUM_PAGE 64//number of pages +#define FLASH_CX_PAGE_START 61 +#define FLASH_CX_PAGE_END 62 +#endif + + +//FLASH ADDRESS +#ifdef FTM3_CHIP +#define FLASH_ADDR_SWITCH_CMD 0x00010000 +#define FLASH_ADDR_CODE 0x00000000 +#define FLASH_ADDR_CONFIG 0x0001E800 +#define FLASH_ADDR_CX 0x0001F000 +#else +#define ADDR_WARM_BOOT 0x001E +#define WARM_BOOT_VALUE 0x38 +#define FLASH_ADDR_CODE 0x00000000 +#define FLASH_ADDR_CONFIG 0x0000FC00 +#endif + + +//CRC ADDR +#ifdef FTM3_CHIP +#define ADDR_CRC_BYTE0 0x00 +#define ADDR_CRC_BYTE1 0x86 +#define CRC_MASK 0x02 +#else +#define ADDR_CRC_BYTE0 0x00 +#define ADDR_CRC_BYTE1 0x74 +#define CRC_MASK 0x03 +#endif + +//SIZES FW, CODE, CONFIG, MEMH +#ifdef FTM3_CHIP +#define FW_HEADER_SIZE 32 +#define FW_SIZE (int)(128*1024) +#define FW_CODE_SIZE (int)(122*1024) +#define FW_CONFIG_SIZE (int)(2*1024) +#define FW_CX_SIZE (int)(FW_SIZE-FW_CODE_SIZE-FW_CONFIG_SIZE) +#define FW_VER_MEMH_BYTE1 193 +#define FW_VER_MEMH_BYTE0 192 +#define FW_OFF_CONFID_MEMH_BYTE1 2 +#define FW_OFF_CONFID_MEMH_BYTE0 1 +#define FW_BIN_VER_OFFSET 4 +#define FW_BIN_CONFIG_VER_OFFSET (FW_HEADER_SIZE+FW_CODE_SIZE+1) +#else +#define FW_HEADER_SIZE 64 +#define FW_HEADER_SIGNATURE 0xAA55AA55 +#define FW_FTB_VER 0x00000001 +#define FW_BYTES_ALIGN 4 +#define FW_BIN_VER_OFFSET 16 +#define FW_BIN_CONFIG_VER_OFFSET 20 +#endif + +//FIFO +#define FIFO_EVENT_SIZE 8 + +#ifdef FTM3_CHIP +#define FIFO_DEPTH 32 +#else +#define FIFO_DEPTH 64 +#endif + +#define FIFO_CMD_READONE 0x85 +#define FIFO_CMD_READALL 0x86 +#define FIFO_CMD_LAST 0x87 +#define FIFO_CMD_FLUSH 0xA1 + + +//CONSTANT TOTAL CX +#ifdef FTM3_CHIP +#define CX1_WEIGHT 4 +#define CX2_WEIGHT 1 +#else +#define CX1_WEIGHT 8 +#define CX2_WEIGHT 1 +#endif + +//OP CODES FOR MEMORY (based on protocol) + +#define FTS_CMD_HW_REG_R 0xB6 +#define FTS_CMD_HW_REG_W 0xB6 +#define FTS_CMD_FRAMEBUFFER_R 0xD0 +#define FTS_CMD_FRAMEBUFFER_W 0xD0 + +#endif diff --git a/qcom/opensource/touch-drivers/st/fts_lib/ftsIO.c b/qcom/opensource/touch-drivers/st/fts_lib/ftsIO.c new file mode 100644 index 0000000000..ba0fb18e33 --- /dev/null +++ b/qcom/opensource/touch-drivers/st/fts_lib/ftsIO.c @@ -0,0 +1,526 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * FTS Capacitive touch screen controller (FingerTipS) + * + * Copyright (C) 2016-2019, STMicroelectronics Limited. + * Authors: AMG(Analog Mems Group) + * + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +/** + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * I2C/SPI Communication ** + * * + ************************************************************************** + ************************************************************************** + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ftsSoftware.h" +#include "ftsCrossCompile.h" +#include "ftsError.h" +#include "ftsHardware.h" +#include "ftsIO.h" +#include "ftsTool.h" +#include "../fts.h" + +static char tag[8] = "[ FTS ]\0"; +static struct i2c_client *client; +static u16 I2CSAD; + +int openChannel(struct i2c_client *clt) +{ + client = clt; + I2CSAD = clt->addr; + logError(0, "%s %s: SAD: %02X\n", tag, __func__, I2CSAD); + return OK; +} + +struct device *getDev() +{ + if (client != NULL) + return &(client->dev); + else + return NULL; +} + +struct i2c_client *getClient() +{ + if (client != NULL) + return client; + else + return NULL; +} + +int fts_readCmd(u8 *cmd, int cmdLength, u8 *outBuf, int byteToRead) +{ + int ret = -1; + int retry = 0; + struct i2c_msg I2CMsg[2]; + struct fts_ts_info *info = i2c_get_clientdata(client); + uint8_t *buf = info->i2c_data; + + /* + * Reassign memory for i2c_data in case length is greater than 32 bytes + */ + if (info->i2c_data_len < cmdLength + byteToRead + 1) { + kfree(info->i2c_data); + info->i2c_data = kmalloc(cmdLength + byteToRead + 1, + GFP_KERNEL); + if (!info->i2c_data) { + info->i2c_data_len = 0; + return -ENOMEM; + } + info->i2c_data_len = cmdLength + byteToRead + 1; + buf = info->i2c_data; + } + + //write msg + I2CMsg[0].addr = (__u16)I2CSAD; + I2CMsg[0].flags = (__u16)0; + I2CMsg[0].len = (__u16)cmdLength; + I2CMsg[0].buf = buf; + + //read msg + I2CMsg[1].addr = (__u16)I2CSAD; + I2CMsg[1].flags = I2C_M_RD; + I2CMsg[1].len = byteToRead; + I2CMsg[1].buf = buf + cmdLength; + + memcpy(buf, cmd, cmdLength); + + if (client == NULL) + return ERROR_I2C_O; + while (retry < I2C_RETRY && ret < OK) { + ret = i2c_transfer(client->adapter, I2CMsg, 2); + retry++; + if (ret < OK) + msleep(I2C_WAIT_BEFORE_RETRY); + } + if (ret < 0) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_I2C_R); + return ERROR_I2C_R; + } + + memcpy(outBuf, buf + cmdLength, byteToRead); + + return OK; +} + +int fts_writeCmd(u8 *cmd, int cmdLength) +{ + int ret = -1; + int retry = 0; + struct i2c_msg I2CMsg[1]; + struct fts_ts_info *info = i2c_get_clientdata(client); + uint8_t *buf = info->i2c_data; + + /* + * Reassign memory for i2c_data in case length is greater than 32 bytes + */ + if (info->i2c_data_len < cmdLength + 1) { + kfree(info->i2c_data); + info->i2c_data = kmalloc(cmdLength + 1, GFP_KERNEL); + if (!info->i2c_data) { + info->i2c_data_len = 0; + return -ENOMEM; + } + info->i2c_data_len = cmdLength + 1; + buf = info->i2c_data; + } + + I2CMsg[0].addr = (__u16)I2CSAD; + I2CMsg[0].flags = (__u16)0; + I2CMsg[0].len = (__u16)cmdLength; + I2CMsg[0].buf = buf; + + memcpy(buf, cmd, cmdLength); + + if (client == NULL) + return ERROR_I2C_O; + while (retry < I2C_RETRY && ret < OK) { + ret = i2c_transfer(client->adapter, I2CMsg, 1); + retry++; + if (ret < OK) + msleep(I2C_WAIT_BEFORE_RETRY); + //logError(1,"%s fts_writeCmd: attempt %d\n", tag, retry); + } + if (ret < 0) { + logError(1, "%s %s: ERROR %02X\n", tag, __func__, ERROR_I2C_W); + return ERROR_I2C_W; + } + return OK; +} + +int fts_writeFwCmd(u8 *cmd, int cmdLength) +{ + int ret = -1; + int ret2 = -1; + int retry = 0; + struct i2c_msg I2CMsg[1]; + struct fts_ts_info *info = i2c_get_clientdata(client); + uint8_t *buf = info->i2c_data; + + /* + * Reassign memory for i2c_data in case length is greater than 32 bytes + */ + if (info->i2c_data_len < cmdLength + 1) { + kfree(info->i2c_data); + info->i2c_data = kmalloc(cmdLength + 1, GFP_KERNEL); + if (!info->i2c_data) { + info->i2c_data_len = 0; + return -ENOMEM; + } + info->i2c_data_len = cmdLength + 1; + buf = info->i2c_data; + } + + I2CMsg[0].addr = (__u16)I2CSAD; + I2CMsg[0].flags = (__u16)0; + I2CMsg[0].len = (__u16)cmdLength; + I2CMsg[0].buf = buf; + + memcpy(buf, cmd, cmdLength); + + if (client == NULL) + return ERROR_I2C_O; + while (retry < I2C_RETRY && (ret < OK || ret2 < OK)) { + ret = i2c_transfer(client->adapter, I2CMsg, 1); + retry++; + if (ret >= 0) + ret2 = checkEcho(cmd, cmdLength); + if (ret < OK || ret2 < OK) + msleep(I2C_WAIT_BEFORE_RETRY); + //logError(1,"%s fts_writeCmd: attempt %d\n", tag, retry); + } + if (ret < 0) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_I2C_W); + return ERROR_I2C_W; + } + if (ret2 < OK) { + logError(1, "%s %s: check echo ERROR %02X\n", + tag, __func__, ret2); + return (ret | ERROR_I2C_W); + } + return OK; +} + + +int writeReadCmd(u8 *writeCmd1, int writeCmdLength, u8 *readCmd1, + int readCmdLength, u8 *outBuf, int byteToRead) +{ + int ret = -1; + int retry = 0; + struct i2c_msg I2CMsg[3]; + struct fts_ts_info *info = i2c_get_clientdata(client); + uint8_t *buf = info->i2c_data; + uint8_t wr_len = writeCmdLength + readCmdLength + byteToRead; + + /* + * Reassign memory for i2c_data in case length is greater than 32 bytes + */ + if (info->i2c_data_len < wr_len + 1) { + kfree(info->i2c_data); + info->i2c_data = kmalloc(wr_len + 1, GFP_KERNEL); + if (!info->i2c_data) { + info->i2c_data_len = 0; + return -ENOMEM; + } + info->i2c_data_len = wr_len + 1; + buf = info->i2c_data; + } + + //write msg + I2CMsg[0].addr = (__u16)I2CSAD; + I2CMsg[0].flags = (__u16)0; + I2CMsg[0].len = (__u16)writeCmdLength; + I2CMsg[0].buf = buf; + + //write msg + I2CMsg[1].addr = (__u16)I2CSAD; + I2CMsg[1].flags = (__u16)0; + I2CMsg[1].len = (__u16)readCmdLength; + I2CMsg[1].buf = buf + writeCmdLength; + + //read msg + I2CMsg[2].addr = (__u16)I2CSAD; + I2CMsg[2].flags = I2C_M_RD; + I2CMsg[2].len = byteToRead; + I2CMsg[2].buf = buf + writeCmdLength + readCmdLength; + + memcpy(buf, writeCmd1, writeCmdLength); + memcpy(buf + writeCmdLength, readCmd1, readCmdLength); + + if (client == NULL) + return ERROR_I2C_O; + while (retry < I2C_RETRY && ret < OK) { + ret = i2c_transfer(client->adapter, I2CMsg, 3); + retry++; + if (ret < OK) + msleep(I2C_WAIT_BEFORE_RETRY); + } + + if (ret < 0) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_I2C_WR); + return ERROR_I2C_WR; + } + + memcpy(outBuf, buf + writeCmdLength + readCmdLength, byteToRead); + + return OK; +} + + +int readCmdU16(u8 cmd, u16 address, u8 *outBuf, int byteToRead, + int hasDummyByte) +{ + int remaining = byteToRead; + int toRead = 0; + u8 rCmd[3] = { cmd, 0x00, 0x00 }; + + u8 *buff = (u8 *)kmalloc_array(READ_CHUNK + 1, sizeof(u8), GFP_KERNEL); + + if (buff == NULL) { + logError(1, "%s %s: ERROR %02X\n", tag, __func__, ERROR_ALLOC); + return ERROR_ALLOC; + } + + while (remaining > 0) { + if (remaining >= READ_CHUNK) { + toRead = READ_CHUNK; + remaining -= READ_CHUNK; + } else { + toRead = remaining; + remaining = 0; + } + + rCmd[1] = (u8)((address & 0xFF00) >> 8); + rCmd[2] = (u8)(address & 0xFF); + + if (hasDummyByte) { + if (fts_readCmd(rCmd, 3, buff, toRead + 1) < 0) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_I2C_R); + kfree(buff); + return ERROR_I2C_R; + } + memcpy(outBuf, buff + 1, toRead); + } else { + if (fts_readCmd(rCmd, 3, buff, toRead) < 0) + return ERROR_I2C_R; + memcpy(outBuf, buff, toRead); + } + + address += toRead; + outBuf += toRead; + } + kfree(buff); + + return OK; +} + + +int writeCmdU16(u8 WriteCmd, u16 address, u8 *dataToWrite, int byteToWrite) +{ + int remaining = byteToWrite; + int toWrite = 0; + + u8 *buff = (u8 *)kmalloc_array(WRITE_CHUNK + 3, sizeof(u8), GFP_KERNEL); + + if (buff == NULL) { + logError(1, "%s %s: ERROR %02X\n", tag, __func__, ERROR_ALLOC); + return ERROR_ALLOC; + } + + buff[0] = WriteCmd; + + while (remaining > 0) { + if (remaining >= WRITE_CHUNK) { + toWrite = WRITE_CHUNK; + remaining -= WRITE_CHUNK; + } else { + toWrite = remaining; + remaining = 0; + } + + buff[1] = (u8)((address & 0xFF00) >> 8); + buff[2] = (u8)(address & 0xFF); + memcpy(buff + 3, dataToWrite, toWrite); + if (fts_writeCmd(buff, 3 + toWrite) < 0) { + logError(1, "%s %s: ERROR %02\n", + tag, __func__, ERROR_I2C_W); + kfree(buff); + return ERROR_I2C_W; + } + address += toWrite; + dataToWrite += toWrite; + } + + kfree(buff); + return OK; +} + +int writeCmdU32(u8 writeCmd1, u8 writeCmd2, u32 address, u8 *dataToWrite, + int byteToWrite) +{ + int remaining = byteToWrite; + int toWrite = 0; + int ret; + + u8 buff1[3] = { writeCmd1, 0x00, 0x00 }; + u8 *buff2 = (u8 *)kmalloc_array(WRITE_CHUNK + 3, + sizeof(u8), GFP_KERNEL); + + if (buff2 == NULL) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_ALLOC); + return ERROR_ALLOC; + } + buff2[0] = writeCmd2; + + + while (remaining > 0) { + if (remaining >= WRITE_CHUNK) { + toWrite = WRITE_CHUNK; + remaining -= WRITE_CHUNK; + } else { + toWrite = remaining; + remaining = 0; + } + + buff1[1] = (u8)((address & 0xFF000000) >> 24); + buff1[2] = (u8)((address & 0x00FF0000) >> 16); + buff2[1] = (u8)((address & 0x0000FF00) >> 8); + buff2[2] = (u8)(address & 0xFF); + memcpy(buff2 + 3, dataToWrite, toWrite); + + if (fts_writeCmd(buff1, 3) < 0) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_I2C_W); + ret = ERROR_I2C_W; + goto END; + } + if (fts_writeCmd(buff2, 3 + toWrite) < 0) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_I2C_W); + ret = ERROR_I2C_W; + goto END; + } + + address += toWrite; + dataToWrite += toWrite; + } + + ret = OK; +END: + kfree(buff2); + return ret; +} + +int writeReadCmdU32(u8 wCmd, u8 rCmd, u32 address, u8 *outBuf, + int byteToRead, int hasDummyByte) +{ + int remaining = byteToRead; + int toRead = 0; + u8 reaCmd[3]; + u8 wriCmd[3]; + + u8 *buff = (u8 *)kmalloc_array(READ_CHUNK + 1, sizeof(u8), GFP_KERNEL); + + if (buff == NULL) { + logError(1, "%s writereadCmd32: ERROR %02X\n", + tag, ERROR_ALLOC); + return ERROR_ALLOC; + } + + reaCmd[0] = rCmd; + wriCmd[0] = wCmd; + + while (remaining > 0) { + if (remaining >= READ_CHUNK) { + toRead = READ_CHUNK; + remaining -= READ_CHUNK; + } else { + toRead = remaining; + remaining = 0; + } + + wriCmd[1] = (u8)((address & 0xFF000000) >> 24); + wriCmd[2] = (u8)((address & 0x00FF0000) >> 16); + + reaCmd[1] = (u8)((address & 0x0000FF00) >> 8); + reaCmd[2] = (u8)(address & 0x000000FF); + + if (hasDummyByte) { + if (writeReadCmd(wriCmd, 3, reaCmd, 3, + buff, toRead + 1) < 0) { + logError(1, "%s writeCmdU32: ERROR %02X\n", + tag, ERROR_I2C_WR); + kfree(buff); + return ERROR_I2C_WR; + } + memcpy(outBuf, buff + 1, toRead); + } else { + if (writeReadCmd(wriCmd, 3, reaCmd, + 3, buff, toRead) < 0) + return ERROR_I2C_WR; + memcpy(outBuf, buff, toRead); + } + + address += toRead; + outBuf += toRead; + } + + kfree(buff); + return OK; +} diff --git a/qcom/opensource/touch-drivers/st/fts_lib/ftsIO.h b/qcom/opensource/touch-drivers/st/fts_lib/ftsIO.h new file mode 100644 index 0000000000..efba899ea2 --- /dev/null +++ b/qcom/opensource/touch-drivers/st/fts_lib/ftsIO.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * FTS Capacitive touch screen controller (FingerTipS) + * + * Copyright (C) 2016-2019, STMicroelectronics Limited. + * Authors: AMG(Analog Mems Group) + * + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +/** + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * I2C/SPI Communication ** + * * + ************************************************************************** + ************************************************************************** + */ + +#ifndef __FTS_IO_H +#define __FTS_IO_H + +#include +#include + +#include "ftsSoftware.h" +#include "ftsCrossCompile.h" + +#define I2C_RETRY 3 //number of retry +#define I2C_WAIT_BEFORE_RETRY 2 //ms + +int openChannel(struct i2c_client *clt); +struct device *getDev(void); +struct i2c_client *getClient(void); +int fts_readCmd(u8 *cmd, int cmdLenght, u8 *outBuf, int byteToRead); +int fts_writeCmd(u8 *cmd, int cmdLenght); +int fts_writeFwCmd(u8 *cmd, int cmdLenght); +int writeReadCmd(u8 *writeCmd, int writeCmdLenght, u8 *readCmd, + int readCmdLenght, u8 *outBuf, int byteToRead); +int readCmdU16(u8 cmd, u16 address, u8 *outBuf, int byteToRead, + int hasDummyByte); +int writeCmdU16(u8 WriteCmd, u16 address, u8 *dataToWrite, int byteToWrite); +int writeCmdU32(u8 writeCmd1, u8 writeCmd2, u32 address, u8 *dataToWrite, + int byteToWrite); +int writeReadCmdU32(u8 wCmd, u8 rCmd, u32 address, u8 *outBuf, int byteToRead, + int hasDummyByte); + +#endif diff --git a/qcom/opensource/touch-drivers/st/fts_lib/ftsSoftware.h b/qcom/opensource/touch-drivers/st/fts_lib/ftsSoftware.h new file mode 100644 index 0000000000..0c52982856 --- /dev/null +++ b/qcom/opensource/touch-drivers/st/fts_lib/ftsSoftware.h @@ -0,0 +1,191 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * FTS Capacitive touch screen controller (FingerTipS) + * + * Copyright (C) 2016-2019, STMicroelectronics Limited. + * Authors: AMG(Analog Mems Group) + * + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +/** + * + ************************************************************************* + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FW related data * + * * + ************************************************************************** + ************************************************************************** + * + */ + +#ifndef __FTS_SOFTWARE_H +#define __FTS_SOFTWARE_H + +#include + +#include "ftsHardware.h" + +#define ECHO_ENABLED 0x00000001 + +//FTS FW COMMAND +#define FTS_CMD_MS_MT_SENSE_OFF 0x92 +#define FTS_CMD_MS_MT_SENSE_ON 0x93 +#define FTS_CMD_SS_HOVER_OFF 0x94 +#define FTS_CMD_SS_HOVER_ON 0x95 +#define FTS_CMD_LP_TIMER_CALIB 0x97 +#define FTS_CMD_MS_KEY_OFF 0x9A +#define FTS_CMD_MS_KEY_ON 0x9B +#define FTS_CMD_MS_COMP_TUNING 0xA3 +#define FTS_CMD_SS_COMP_TUNING 0xA4 +#define FTS_CMD_FULL_INITIALIZATION 0xA5 +#define FTS_CMD_ITO_CHECK 0xA7 +#define FTS_CMD_RELEASE_INFO 0xAA +#define FTS_CMD_GESTURE_MODE 0xAD +#define FTS_CMD_REQU_FW_CONF 0xB2 +#define FTS_CMD_REQU_FRAME_DATA 0xB7 +#define FTS_CMD_REQU_COMP_DATA 0xB8 +#define FTS_CMD_WRITE_MP_FLAG 0xC0 +#define FTS_CMD_FEATURE_ENABLE 0xC1 +#define FTS_CMD_FEATURE_DISABLE 0xC2 +#define FTS_CMD_GESTURE_CMD 0xC3 +#define FTS_CMD_LOCKDOWN_CMD 0xC4 +#define FTS_CMD_NOISE_WRITE 0xC7 +#define FTS_CMD_NOISE_READ 0xC8 +#define FTS_CMD_LOCKDOWN_FILL 0xCA +#define FTS_CMD_LOCKDOWN_WRITE 0xCB +#define FTS_CMD_LOCKDOWN_READ 0xCC +#define FTS_CMD_SAVE_CX_TUNING 0xFC + +//Event ID +#define EVENTID_NO_EVENT 0x00 +#define EVENTID_ERROR_EVENT 0x0F +#define EVENTID_CONTROL_READY 0x10 +#define EVENTID_FW_CONFIGURATION 0x12 +#define EVENTID_COMP_DATA_READ 0x13 +#define EVENTID_STATUS_UPDATE 0x16 +#define EVENTID_RELEASE_INFO 0x1C +#define EVENTID_LOCKDOWN_INFO 0x1E +#define EVENTID_ENTER_POINTER 0x03 +#define EVENTID_LEAVE_POINTER 0x04 +#define EVENTID_MOTION_POINTER 0x05 +#define EVENTID_HOVER_ENTER_POINTER 0x07 +#define EVENTID_HOVER_LEAVE_POINTER 0x08 +#define EVENTID_HOVER_MOTION_POINTER 0x09 +#define EVENTID_PROXIMITY_ENTER 0x0B +#define EVENTID_PROXIMITY_LEAVE 0x0C +#define EVENTID_KEY_STATUS 0x0E +#define EVENTID_LOCKDOWN_INFO_READ 0x1E +#define EVENTID_NOISE_READ 0x17 +#define EVENTID_NOISE_WRITE 0x18 +#define EVENTID_GESTURE 0x22 +#define EVENTID_FRAME_DATA_READ 0x25 +#define EVENTID_ECHO 0xEC +#define EVENTID_LAST (EVENTID_FRAME_DATA_READ+1) + +//EVENT TYPE +#define EVENT_TYPE_MS_TUNING_CMPL 0x01 +#define EVENT_TYPE_SS_TUNING_CMPL 0x02 +#define EVENT_TYPE_COMP_DATA_SAVED 0x04 +#define EVENT_TYPE_ITO 0x05 +#define EVENT_TYPE_FULL_INITIALIZATION 0x07 +#define EVENT_TYPE_LPTIMER_TUNING_CMPL 0x20 +#define EVENT_TYPE_ESD_ERROR 0x0A +#define EVENT_TYPE_WATCHDOG_ERROR 0x01 +#define EVENT_TYPE_CHECKSUM_ERROR 0x03 +#define EVENT_TYPE_LOCKDOWN 0x0A +#define EVENT_TYPE_LOCKDOWN_WRITE 0x08 +#define EVENT_TYPE_LOCKDOWN_ERROR 0x0B + +//CRC type +#define CRC_CONFIG_SIGNATURE 0x01 +#define CRC_CONFIG 0x02 +#define CRC_CX_MEMORY 0x03 + +// CONFIG ID INFO +#define CONFIG_ID_ADDR 0x0001 +#define CONFIG_ID_BYTE 2 + +//ADDRESS OFFSET IN SYSINFO +#define ADDR_RAW_TOUCH 0x0000 +#define ADDR_FILTER_TOUCH 0x0002 +#define ADDR_NORM_TOUCH 0x0004 +#define ADDR_CALIB_TOUCH 0x0006 +#define ADDR_RAW_HOVER_FORCE 0x000A +#define ADDR_RAW_HOVER_SENSE 0x000C +#define ADDR_FILTER_HOVER_FORCE 0x000E +#define ADDR_FILTER_HOVER_SENSE 0x0010 +#define ADDR_NORM_HOVER_FORCE 0x0012 +#define ADDR_NORM_HOVER_SENSE 0x0014 +#define ADDR_CALIB_HOVER_FORCE 0x0016 +#define ADDR_CALIB_HOVER_SENSE 0x0018 +#define ADDR_RAW_PRX_FORCE 0x001A +#define ADDR_RAW_PRX_SENSE 0x001C +#define ADDR_FILTER_PRX_FORCE 0x001E +#define ADDR_FILTER_PRX_SENSE 0x0020 +#define ADDR_NORM_PRX_FORCE 0x0022 +#define ADDR_NORM_PRX_SENSE 0x0024 +#define ADDR_CALIB_PRX_FORCE 0x0026 +#define ADDR_CALIB_PRX_SENSE 0x0028 +#define ADDR_RAW_MS_KEY 0x0032 +#define ADDR_NORM_MS_KEY 0x0036 +#define ADDR_COMP_DATA 0x0050 +#define ADDR_FRAMEBUFFER_DATA 0x8000 + +//ADDRESS FW REGISTER +#define ADDR_SENSE_LEN 0x0014 +#define ADDR_FORCE_LEN 0x0015 +#define ADDR_MS_TUNING_VER 0x0729 +#define ADDR_SS_TUNING_VER 0x074E + +//B2 INFO +#define B2_DATA_BYTES 4 +//number of bytes +#define B2_CHUNK ((FIFO_DEPTH/2)*B2_DATA_BYTES) + +//FEATURES +#define FEAT_GLOVE 0x00000001 +#define FEAT_STYLUS 0x00000002 +#define FEAT_COVER 0x00000004 +#define FEAT_CHARGER 0x00000008 +#define FEAT_VR 0x00000010 +#define FEAT_EDGE_REJECTION 0x00000020 +#define FEAT_CORNER_REJECTION 0x00000040 +#define FEAT_EDGE_PALM_REJECTION 0x00000100 + +//TOUCH SIZE +#define STYLUS_SIZE 0x01 +#define FINGER_SIZE 0x02 +#define LARGE_SIZE 0x03 + +//C7/C8 parameters +#define NOISE_PARAMETERS 0x01 + +//MP_FLAG_VALUE +#define INIT_MP 0xA5A5A501 +#define INIT_FIELD 0xA5A5A502 + +//NOISE PARAMS +#define NOISE_PARAMETERS_SIZE 4 //bytes + +//ERROR INFO +#define ERROR_INFO_SIZE (20*4) // bytes +#define ERROR_SIGNATURE 0xFA5005AF +#define ERROR_SIGN_HEAD 0xA5 + +#endif diff --git a/qcom/opensource/touch-drivers/st/fts_lib/ftsTest.c b/qcom/opensource/touch-drivers/st/fts_lib/ftsTest.c new file mode 100644 index 0000000000..baa0772cdb --- /dev/null +++ b/qcom/opensource/touch-drivers/st/fts_lib/ftsTest.c @@ -0,0 +1,3844 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * FTS Capacitive touch screen controller (FingerTipS) + * + * Copyright (C) 2016-2019, STMicroelectronics Limited. + * Authors: AMG(Analog Mems Group) + * + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +/* + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS API for MP test *** + * * + ************************************************************************** + ************************************************************************** + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include + +#include "ftsCrossCompile.h" +#include "ftsCompensation.h" +#include "ftsError.h" +#include "ftsFrame.h" +#include "ftsHardware.h" +#include "ftsIO.h" +#include "ftsSoftware.h" +#include "ftsTest.h" +#include "ftsTime.h" +#include "ftsTool.h" +#include "../fts.h" + +#ifdef LIMITS_H_FILE +#include <../fts_limits.h> +#endif + +static char tag[8] = "[ FTS ]\0"; + +int computeAdjHoriz(u8 *data, int row, int column, u8 **result) +{ + int i, j; + int size = row * (column - 1); + + if (column < 2) { + logError(1, "%s %s: ERROR % 02X\n", + tag, __func__, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + *result = (u8 *) kmalloc_array(size, sizeof(u8), GFP_KERNEL); + if (*result == NULL) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_ALLOC); + return ERROR_ALLOC; + } + + for (i = 0; i < row; i++) { + for (j = 1; j < column; j++) { + *(*result + (i * (column - 1) + (j - 1))) = + abs(data[i * column + j] - + data[i * column + (j - 1)]); + } + } + return OK; +} + +int computeAdjHorizTotal(u16 *data, int row, int column, u16 **result) +{ + int i, j; + int size = row * (column - 1); + + if (column < 2) { + logError(1, "%s %s: ERROR % 02X\n", + tag, __func__, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + *result = (u16 *)kmalloc_array(size, sizeof(u16), GFP_KERNEL); + if (*result == NULL) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_ALLOC); + return ERROR_ALLOC; + } + + for (i = 0; i < row; i++) { + for (j = 1; j < column; j++) { + *(*result + (i * (column - 1) + (j - 1))) = + abs(data[i * column + j] - + data[i * column + (j - 1)]); + } + } + + return OK; +} + +int computeAdjVert(u8 *data, int row, int column, u8 **result) +{ + int i, j; + int size = (row - 1) * (column); + + if (row < 2) { + logError(1, "%s %s: ERROR % 02X\n", + tag, __func__, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + *result = (u8 *)kmalloc_array(size, sizeof(u8), GFP_KERNEL); + if (*result == NULL) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_ALLOC); + return ERROR_ALLOC; + } + + for (i = 1; i < row; i++) { + for (j = 0; j < column; j++) { + *(*result + ((i - 1) * column + j)) = + abs(data[i * column + j] - + data[(i - 1) * column + j]); + } + } + + return OK; +} + +int computeAdjVertTotal(u16 *data, int row, int column, u16 **result) +{ + int i, j; + int size = (row - 1) * (column); + + if (row < 2) { + logError(1, "%s %s: ERROR % 02X\n", + tag, __func__, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + *result = (u16 *)kmalloc_array(size, sizeof(u16), GFP_KERNEL); + if (*result == NULL) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_ALLOC); + return ERROR_ALLOC; + } + + for (i = 1; i < row; i++) { + for (j = 0; j < column; j++) { + *(*result + ((i - 1) * column + j)) = + abs(data[i * column + j] - + data[(i - 1) * column + j]); + } + } + + return OK; +} + +int computeTotal(u8 *data, u8 main, int row, int column, + int m, int n, u16 **result) +{ + int i, j; + int size = (row) * (column); + + *result = (u16 *)kmalloc_array(size, sizeof(u16), GFP_KERNEL); + if (*result == NULL) { + logError(1, "%s %s : ERROR %02X\n", + tag, __func__, ERROR_ALLOC); + return ERROR_ALLOC; + } + + for (i = 0; i < row; i++) { + for (j = 0; j < column; j++) { + *(*result + (i * column + j)) = + m * main + n * data[i * column + j]; + } + } + + return OK; +} + +int checkLimitsMinMax(short *data, int row, int column, int min, int max) +{ + int i, j; + int count = 0; + + for (i = 0; i < row; i++) { + for (j = 0; j < column; j++) { + if (data[i * column + j] < min + || data[i * column + j] > max) { + logError(1, "%s %s:Node[%d,%d] = %d ", tag, + __func__, i, j, data[i * column + j]); + logError(1, "exceed limit [%d,%d]\n", min, max); + count++; + } + } + } + + return count;//if count is 0 = OK, test completed successfully +} + +int checkLimitsGap(short *data, int row, int column, int threshold) +{ + int i, j; + int min_node; + int max_node; + + if (row == 0 || column == 0) { + logError(1, "%s %s:invalid number of rows = %d ", + tag, __func__, row); + logError(1, "or columns = %d %02\n", + column, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + min_node = data[0]; + max_node = data[0]; + + for (i = 0; i < row; i++) { + for (j = 0; j < column; j++) { + if (data[i * column + j] < min_node) { + min_node = data[i * column + j]; + } else { + if (data[i * column + j] > max_node) + max_node = data[i * column + j]; + } + } + } + + if (max_node - min_node > threshold) { + logError(1, "%s %s: GAP = %d exceed limit %d\n", + tag, __func__, max_node - min_node, threshold); + return ERROR_TEST_CHECK_FAIL; + } + return OK; +} + +int checkLimitsMap(u8 *data, int row, int column, int *min, int *max) +{ + int i, j; + int count = 0; + + for (i = 0; i < row; i++) { + for (j = 0; j < column; j++) { + if (data[i * column + j] < min[i * column + j] + || data[i * column + j] > max[i * column + j]) { + logError(1, "%s %s: Node[%d,%d] = %d ", + tag, __func__, + i, j, + data[i * column + j]); + logError(1, "exceed limit [%d, %d]\n", + min[i * column + j], + max[i * column + j]); + count++; + } + } + } + + return count; //if count is 0 = OK, test completed successfully +} + +int checkLimitsMapTotal(u16 *data, int row, int column, int *min, int *max) +{ + int i, j; + int count = 0; + + for (i = 0; i < row; i++) { + for (j = 0; j < column; j++) { + if (data[i * column + j] < min[i * column + j] + || data[i * column + j] > max[i * column + j]) { + logError(1, "%s %s:Node[%d,%d] = %d\n", + tag, __func__, i, j, + data[i * column + j]); + logError(1, "exceed limit [%d, %d]\n", + min[i * column + j], + max[i * column + j]); + count++; + } + } + } + + return count; //if count is 0 = OK, test completed successfully +} + +int checkLimitsMapAdj(u8 *data, int row, int column, int *max) +{ + int i, j; + int count = 0; + + for (i = 0; i < row; i++) { + for (j = 0; j < column; j++) { + if (data[i * column + j] > max[i * column + j]) { + logError(1, "%s %s:Node[%d,%d] = %d ", + tag, __func__, i, j); + logError(1, "exceed limit > %d\n", + data[i * column + j], + max[i * column + j]); + count++; + } + } + } + //if count is 0 = OK, test completed successfully + return count; +} + +int checkLimitsMapAdjTotal(u16 *data, int row, int column, int *max) +{ + int i, j; + int count = 0; + + for (i = 0; i < row; i++) { + for (j = 0; j < column; j++) { + if (data[i * column + j] > max[i * column + j]) { + logError(1, "%s %s:Node[%d,%d] = %d ", + tag, __func__, i, j); + logError(1, "exceed limit > %d\n", + data[i * column + j], + max[i * column + j]); + count++; + } + } + } + //if count is 0 = OK, test completed successfully + return count; +} + +int production_test_ito(void) +{ + int res = OK; + u8 cmd; + u8 readData[FIFO_EVENT_SIZE] = {0}; + //look for ito event + int eventToSearch[2] = {EVENTID_ERROR_EVENT, EVENT_TYPE_ITO}; + + logError(0, "%s ITO Production test is starting...\n", tag); + + res = fts_system_reset(); + if (res < 0) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_PROD_TEST_ITO); + return (res | ERROR_PROD_TEST_ITO); + } + + cmd = FTS_CMD_ITO_CHECK; + + logError(0, "%s ITO Check command sent...\n", tag); + if (fts_writeFwCmd(&cmd, 1) < 0) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, (ERROR_I2C_W | ERROR_PROD_TEST_ITO)); + return (ERROR_I2C_W | ERROR_PROD_TEST_ITO); + } + + logError(0, "%s Looking for ITO Event...\n", tag); + res = pollForEvent(eventToSearch, 2, + readData, TIMEOUT_ITO_TEST_RESULT); + if (res < 0) { + logError(1, "%s %s: ITO Production test failed ERROR %02X\n", + tag, __func__, ERROR_PROD_TEST_ITO); + return (res | ERROR_PROD_TEST_ITO); + } + + if (readData[2] != 0x00 || readData[3] != 0x00) { + logError(0, "%s ITO Production testes finished! ERROR %02X\n", + tag, (ERROR_TEST_CHECK_FAIL | ERROR_PROD_TEST_ITO)); + res = (ERROR_TEST_CHECK_FAIL | ERROR_PROD_TEST_ITO); + } else { + logError(0, "%s ITO Production test finished!..OK\n", tag); + res = OK; + } + + res |= fts_system_reset(); + if (res < 0) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_PROD_TEST_ITO); + res = (res | ERROR_PROD_TEST_ITO); + } + return res; +} + +int production_test_initialization(void) +{ + int res; + u8 cmd; + u8 readData[FIFO_EVENT_SIZE] = {0}; + + int eventToSearch[2] = {EVENTID_STATUS_UPDATE, + EVENT_TYPE_FULL_INITIALIZATION}; + logError(0, "%s INITIALIZATION Production test is starting\n", tag); + + res = fts_system_reset(); + if (res < 0) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_PROD_TEST_INITIALIZATION); + return (res | ERROR_PROD_TEST_INITIALIZATION); + } + + logError(0, "%s INITIALIZATION command sent...\n", tag); + cmd = FTS_CMD_FULL_INITIALIZATION; + if (fts_writeFwCmd(&cmd, 1) < 0) { + logError(1, "%s %s: ERROR %02X\n", tag, __func__, + (ERROR_I2C_W | ERROR_PROD_TEST_INITIALIZATION)); + return (ERROR_I2C_W | ERROR_PROD_TEST_INITIALIZATION); + } + + logError(0, "%s Looking for INITIALIZATION Event...\n", tag); + res = pollForEvent(eventToSearch, 2, readData, + TIMEOUT_INITIALIZATION_TEST_RESULT); + if (res < 0) { + logError(1, "%s %s: INITIALIZATION Production ", tag, __func__); + logError(1, "test failed %02X\n", + ERROR_PROD_TEST_INITIALIZATION); + return (res | ERROR_PROD_TEST_INITIALIZATION); + } + + if (readData[2] != 0x00) { + logError(0, "%sINITIALIZATION Production ", tag); + logError(0, "testes finished! FAILED %02X\n", + (ERROR_TEST_CHECK_FAIL | ERROR_PROD_TEST_INITIALIZATION)); + res = (ERROR_TEST_CHECK_FAIL | ERROR_PROD_TEST_INITIALIZATION); + } else { + logError(0, "%s INITIALIZATION Production test...OK\n", tag); + res = OK; + } + + logError(0, "%s Refresh Chip Info...\n", tag); + //need to update the chipInfo in order to refresh the tuning_versione + res |= readChipInfo(1); + + if (res < 0) { + logError(1, "%s %s: read chip info ERROR %02X\n", + tag, __func__, ERROR_PROD_TEST_INITIALIZATION); + res = (res | ERROR_PROD_TEST_INITIALIZATION); + } + + return res; +} + +int ms_compensation_tuning(void) +{ + int res; + u8 cmd; + u8 readData[FIFO_EVENT_SIZE] = {0}; + int eventToSearch[2] = {EVENTID_STATUS_UPDATE, + EVENT_TYPE_MS_TUNING_CMPL}; + + logError(0, "%s MS INITIALIZATION command sent...\n", tag); + cmd = FTS_CMD_MS_COMP_TUNING; + if (fts_writeFwCmd(&cmd, 1) < 0) { + logError(1, "%s %s 2: ERROR %02X\n", + tag, __func__, (ERROR_I2C_W | ERROR_MS_TUNING)); + return (ERROR_I2C_W | ERROR_MS_TUNING); + } + + logError(0, "%s Looking for MS INITIALIZATION Event...\n", tag); + res = pollForEvent(eventToSearch, 2, readData, + TIMEOUT_INITIALIZATION_TEST_RESULT); + if (res < 0) { + logError(1, "%s %s:MS INITIALIZATION Production\n", + tag, __func__); + logError(1, "test failed %02X\n", ERROR_MS_TUNING); + return (res | ERROR_MS_TUNING); + } + + if (readData[2] != 0x00 || readData[3] != 0x00) { + logError(0, "%s MS INITIALIZATION Production ", tag); + logError(0, "test finished! FAILED %02X\n", ERROR_MS_TUNING); + res = ERROR_MS_TUNING; + } else { + logError(0, + "%s MS INITIALIZATION Production test finished! OK\n", + tag); + res = OK; + } + + return res; +} + +int ss_compensation_tuning(void) +{ + int res; + u8 cmd; + u8 readData[FIFO_EVENT_SIZE] = {0}; + + int eventToSearch[2] = {EVENTID_STATUS_UPDATE, + EVENT_TYPE_SS_TUNING_CMPL}; + + logError(0, "%s SS INITIALIZATION command sent...\n", tag); + cmd = FTS_CMD_SS_COMP_TUNING; + if (fts_writeFwCmd(&cmd, 1) < 0) { + logError(1, "%s %s 2: ERROR %02X\n", + tag, __func__, (ERROR_I2C_W | ERROR_SS_TUNING)); + return (ERROR_I2C_W | ERROR_SS_TUNING); + } + + logError(0, "%s Looking for SS INITIALIZATION Event...\n", tag); + res = pollForEvent(eventToSearch, + 2, + readData, + TIMEOUT_INITIALIZATION_TEST_RESULT); + if (res < 0) { + logError(1, "%s %s:SS INITIALIZATION Production ", + tag, __func__); + logError(1, "test failed %02X\n", ERROR_SS_TUNING); + return (res | ERROR_SS_TUNING); + } + logError(0, "%s SS INITIALIZATION Production test finished!", tag); + if (readData[2] != 0x00 || readData[3] != 0x00) { + logError(0, "%s.................FAILED ERROR %02X\n", + tag, ERROR_SS_TUNING); + res = ERROR_SS_TUNING; + } else { + logError(0, "%s.................OK\n", tag); + res = OK; + } + + return res; +} + +int lp_timer_calibration(void) +{ + int res; + u8 cmd; + u8 readData[FIFO_EVENT_SIZE] = {0}; + int eventToSearch[2] = {EVENTID_STATUS_UPDATE, + EVENT_TYPE_LPTIMER_TUNING_CMPL}; + + logError(0, "%s LP TIMER CALIBRATION command sent...\n", tag); + cmd = FTS_CMD_LP_TIMER_CALIB; + if (fts_writeFwCmd(&cmd, 1) < 0) { + logError(1, "%s %s 2:ERROR %02X\n", tag, __func__, + (ERROR_I2C_W | ERROR_LP_TIMER_TUNING)); + return (ERROR_I2C_W | ERROR_LP_TIMER_TUNING); + } + + logError(0, "%s Looking for LP TIMER CALIBRATION Event...\n", tag); + res = pollForEvent(eventToSearch, + 2, + readData, + TIMEOUT_INITIALIZATION_TEST_RESULT); + + if (res < 0) { + logError(1, "%s:LP TIMER CALIBRATION Production test failed\n", + tag); + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_LP_TIMER_TUNING); + return (res | ERROR_LP_TIMER_TUNING); + } + + logError(0, "LP TIMER CALIBRATION Production test finished!"); + if (readData[2] != 0x00 || readData[3] != 0x01) { + logError(0, "%s........FAILED ERROR %02X\n", + tag, ERROR_LP_TIMER_TUNING); + res = ERROR_LP_TIMER_TUNING; + } else { + logError(0, "%s.................OK\n", tag); + res = OK; + } + + return res; +} + +int save_cx_tuning(void) +{ + int res; + u8 cmd; + u8 readData[FIFO_EVENT_SIZE] = {0}; + int eventToSearch[2] = {EVENTID_STATUS_UPDATE, + EVENT_TYPE_COMP_DATA_SAVED}; + + logError(0, "%s SAVE CX command sent...\n", tag); + cmd = FTS_CMD_SAVE_CX_TUNING; + if (fts_writeCmd(&cmd, 1) < 0) { + logError(1, "%s %s 2:ERROR %02X\n", tag, __func__, + (ERROR_I2C_W | ERROR_SAVE_CX_TUNING)); + return (ERROR_I2C_W | ERROR_SAVE_CX_TUNING); + } + + logError(0, "%s Looking for SAVE CX Event...\n", tag); + res = pollForEvent(eventToSearch, + 2, + readData, + TIMEOUT_INITIALIZATION_TEST_RESULT); + if (res < 0) { + logError(1, "%s %s: SAVE CX failed... ERROR %02X\n", + tag, ERROR_SAVE_CX_TUNING); + return (res | ERROR_SAVE_CX_TUNING); + } + + if (readData[2] != 0x00 || readData[3] != 0x00) { + logError(0, "%s SAVE CX finished! FAILED ERROR %02X\n", + tag, ERROR_SAVE_CX_TUNING); + res = ERROR_SAVE_CX_TUNING; + } else { + logError(0, "%s SAVE CX finished!.................OK\n", tag); + res = OK; + } + + return res; +} + +int production_test_split_initialization(int saveToFlash) +{ + int res; + + logError(0, "%s Split Initialization test is starting...\n", tag); + res = fts_system_reset(); + if (res < 0) { + logError(1, "%s %s: ERROR %02X\n", tag, __func__, + ERROR_PROD_TEST_INITIALIZATION); + return (res | ERROR_PROD_TEST_INITIALIZATION); + } + + logError(0, "%s MS INITIALIZATION TEST:\n", tag); + res = ms_compensation_tuning(); + if (res < 0) { + logError(0, "%s %s:MS INITIALIZATION TEST FAILED! ERROR %02X\n", + tag, __func__, ERROR_PROD_TEST_INITIALIZATION); + return (res | ERROR_PROD_TEST_INITIALIZATION); + } + + logError(0, "%s MS INITIALIZATION TEST OK!\n", tag); + logError(0, "%s\n", tag); + logError(0, "%s SS INITIALIZATION TEST:\n", tag); + res = ss_compensation_tuning(); + if (res < 0) { + logError(0, "%s %s: SS INITIALIZATION TEST FAILED! ", + tag, __func__); + logError(0, "ERROR %02X\n", ERROR_PROD_TEST_INITIALIZATION); + return (res | ERROR_PROD_TEST_INITIALIZATION); + } + + logError(0, "%s SS INITIALIZATION TEST OK!\n", tag); + logError(0, "%s\n", tag); + logError(0, "%s LP INITIALIZATION TEST:\n", tag); + res = lp_timer_calibration(); + if (res < 0) { + logError(0, "%s %s: LP INITIALIZATION TEST FAILED! ", + tag, __func__); + logError(0, "ERROR %02X\n", ERROR_PROD_TEST_INITIALIZATION); + return (res | ERROR_PROD_TEST_INITIALIZATION); + } + + logError(0, "%s LP INITIALIZATION TEST OK!\n", tag); + if (saveToFlash) { + logError(0, "%s\n", tag); + logError(0, "%s SAVE CX TEST:\n", tag); + res = save_cx_tuning(); + if (res < 0) { + logError(0, "%s %s: SAVE CX TEST FAILED! ERROR %02X\n", + tag, __func__, res); + return (res | ERROR_PROD_TEST_INITIALIZATION); + } + logError(0, "%s SAVE CX TEST OK!\n", tag); + } + + logError(0, "%s Refresh Chip Info...\n", tag); + res |= readChipInfo(1); + if (res < 0) { + logError(1, "%s %s: read chip info ERROR %02X\n", + tag, __func__, ERROR_PROD_TEST_INITIALIZATION); + res = (res | ERROR_PROD_TEST_INITIALIZATION); + } else { + logError(0, "%s Split Initialization test finished! OK\n", tag); + } + return res; + +} + +int production_test_main(char *pathThresholds, int stop_on_fail, int saveInit, + struct TestToDo *todo, u32 signature) +{ + int res, ret; + + logError(0, "%s MAIN Production test is starting...\n", tag); + logError(0, "%s\n", tag); + logError(0, "%s ITO TEST:\n", tag); + + res = production_test_ito(); + if (res < 0) { + logError(0, "%s Error during ITO TEST! ERROR %08X\n", tag, res); + //in case of ITO TEST failure is no sense keep going + goto END; + } + logError(0, "%s ITO TEST OK!\n", tag); + + logError(0, "%s:\n", tag); + logError(0, "%s INITIALIZATION TEST:\n", tag); + + if (saveInit == 1) { + res = production_test_initialization(); + if (res < 0) { + logError(0, "%s Error during INITIALIZATION TEST!", + tag); + logError(0, "ERROR %08X\n", res); + if (stop_on_fail) + goto END; + } else { + logError(0, "%s INITIALIZATION TEST OK!\n", tag); + } + } else + logError(0, "%s INITIALIZATION TEST:..SKIPPED\n", tag); + + logError(0, "%s\n", tag); + if (saveInit == 1) { + logError(0, "%s Cleaning up...\n", tag); + ret = cleanUp(0); + if (ret < 0) { + logError(1, "%s %s: clean up ERROR %02X\n", + tag, __func__, ret); + res |= ret; + if (stop_on_fail) + goto END; + } + logError(0, "%s\n", tag); + } + + logError(0, "%s PRODUCTION DATA TEST:\n", tag); + ret = production_test_data(pathThresholds, stop_on_fail, todo); + if (ret < 0) { + logError(0, "%sError during PRODUCTION DATA TEST %08X\n", + tag, ret); + } else { + logError(0, "%s PRODUCTION DATA TEST OK!\n", tag); + } + res |= ret; + // the OR is important because if + //the data test is OK but the inizialization + //test fail, the main production + //test result should = FAIL + + if (ret == OK && saveInit == 1) { + logError(0, "%s SAVE FLAG:\n", tag); + ret = save_mp_flag(signature); + if (ret < OK) + logError(0, "%s SAVE FLAG:FAIL! ERROR %08X\n", + tag, ret); + else + logError(0, "%s SAVE FLAG:OK!\n", tag); + res |= ret; + // need to update the MP Flag + ret = readChipInfo(1); + if (ret < OK) + logError(1, "%s %s:read chip info ERROR %08X\n", + tag, __func__, ret); + res |= ret; + } + logError(0, "%s\n", tag); +END: + if (res < 0) { + logError(0, "%s MAIN Production test finished..FAILED\n", tag); + return res; + } + logError(0, "%s MAIN Production test finished..OK\n", tag); + return OK; +} + +int production_test_ms_raw(char *path_limits, int stop_on_fail, + struct TestToDo *todo) +{ + int ret, count_fail = 0; + struct MutualSenseFrame msRawFrame = {0}; + + int *thresholds = NULL; + int trows, tcolumns; + + //****** Mutual Sense Test ************/ + logError(0, "%s\n", tag); + logError(0, "%s MS RAW DATA TEST is starting...\n", tag); + if (todo->MutualRaw == 1 || todo->MutualRawGap == 1) { + ret = getMSFrame2(MS_TOUCH_ACTIVE, &msRawFrame); + if (ret < 0) { + logError(1, "%s %s:getMSFrame failed... ERROR %02X\n", + tag, __func__, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + logError(0, "%s MS RAW MIN MAX TEST:\n", tag); + if (todo->MutualRaw == 1) { + ret = parseProductionTestLimits(path_limits, + MS_RAW_MIN_MAX, + &thresholds, + &trows, + &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 2)) { + logError(0, "%s %s:MS_RAW_MIN_MAX failed...", + tag, __func__); + logError(0, "ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMinMax(msRawFrame.node_data, + msRawFrame.header.force_node, + msRawFrame.header.sense_node, + thresholds[0], + thresholds[1]); + if (ret != OK) { + logError(0, "%s %s:MS RAW failed...", + tag, __func__); + logError(0, "ERROR COUNT = %d\n", ret); + logError(0, "%s MS RAW MIN MAX TEST:...", tag); + logError(0, "FAIL\n\n", tag); + count_fail += 1; + if (stop_on_fail == 1) + goto ERROR; + } else + logError(0, "%s MS RAW MIN MAX TEST:OK\n", tag); + kfree(thresholds); + thresholds = NULL; + } else + logError(0, "%s MS RAW MIN MAX TEST:SKIPPED\n", tag); + + logError(0, "%s\n", tag); + logError(0, "%s MS RAW GAP TEST:\n", tag); + if (todo->MutualRawGap == 1) { + ret = parseProductionTestLimits(path_limits, + MS_RAW_GAP, + &thresholds, + &trows, + &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 1)) { + logError(1, "%s %s: MS_RAW_GAP failed... ", + tag, __func__); + logError(1, "ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsGap(msRawFrame.node_data, + msRawFrame.header.force_node, + msRawFrame.header.sense_node, + thresholds[0]); + if (ret != OK) { + logError(1, "%s %s:checkLimitsGap MS RAW ", + tag, __func__); + logError(1, "failed ERROR:%02X\n", ret); + count_fail += 1; + if (stop_on_fail == 1) + goto ERROR; + } else + logError(0, "%s MS RAW GAP TEST:....OK\n\n", + tag); + kfree(thresholds); + thresholds = NULL; + } else + logError(0, "%s MS RAW GAP TEST:..SKIPPED\n", tag); + } else + logError(0, "%s MS RAW FRAME TEST:.SKIPPED\n", tag); + + logError(0, "%s\n", tag); + logError(0, "%s MS KEY RAW TEST:\n", tag); + if (todo->MutualKeyRaw == 1) { + ret = production_test_ms_key_raw(path_limits); + if (ret < 0) { + logError(1, "%s %s:production_test_ms_key_raw ", + tag, __func__); + logError(1, "failed ERROR:%02X\n", ret); + count_fail += 1; + if (count_fail == 1) { + logError(0, "%s MS RAW DATA TEST:FAIL ", tag); + logError(0, "fails_count:%d\n\n", count_fail); + goto ERROR_LIMITS; + } + } + } else + logError(0, "%s MS KEY RAW TEST:....SKIPPED\n", tag); +ERROR: + logError(0, "%s\n", tag); + if (count_fail == 0) { + kfree(msRawFrame.node_data); + msRawFrame.node_data = NULL; + logError(0, "%s MS RAW DATA TEST finished!.OK\n", tag); + return OK; + } + print_frame_short("MS Raw frame =", + array1dTo2d_short(msRawFrame.node_data, + msRawFrame.node_data_size, + msRawFrame.header.sense_node), + msRawFrame.header.force_node, + msRawFrame.header.sense_node); + kfree(msRawFrame.node_data); + kfree(thresholds); + logError(0, "%s MS RAW DATA TEST: FAIL fails_count = %d\n\n", + tag, count_fail); + return (ERROR_PROD_TEST_DATA | ERROR_TEST_CHECK_FAIL); + +ERROR_LIMITS: + kfree(msRawFrame.node_data); + kfree(thresholds); + return ret; +} + +int production_test_ms_key_raw(char *path_limits) +{ + int ret; + struct MutualSenseFrame msRawFrame; + + int *thresholds = NULL; + int trows, tcolumns; + + //************* Mutual Sense Test ************/ + logError(0, "%s MS KEY RAW DATA TEST is starting...\n", tag); + + ret = getMSFrame2(MS_KEY, &msRawFrame); + if (ret < 0) { + logError(1, "%s %s:getMSKeyFrame failed...ERROR %02X\n", + tag, __func__, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = parseProductionTestLimits(path_limits, + MS_KEY_RAW_MIN_MAX, + &thresholds, + &trows, + &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 2)) { + logError(1, "%s %s: MS_KEY_RAW_MIN_MAX failed...ERROR %02X\n", + tag, __func__, ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMinMax(msRawFrame.node_data, + msRawFrame.header.force_node, + msRawFrame.header.sense_node, + thresholds[0], + thresholds[1]); + if (ret != OK) { + logError(1, "%s %s:checkLimitsMinMax failed..ERROR COUNT:%d\n", + tag, __func__, ret); + goto ERROR; + } else + logError(0, "%s MS KEY RAW TEST:.................OK\n\n", tag); + + kfree(thresholds); + thresholds = NULL; + + kfree(msRawFrame.node_data); + msRawFrame.node_data = NULL; + return OK; +ERROR: + print_frame_short("MS Key Raw frame =", + array1dTo2d_short(msRawFrame.node_data, + msRawFrame.node_data_size, + msRawFrame.header.sense_node), + msRawFrame.header.force_node, + msRawFrame.header.sense_node); + kfree(msRawFrame.node_data); + kfree(thresholds); + logError(0, "%s MS KEY RAW TEST:......FAIL\n\n", tag); + return (ERROR_PROD_TEST_DATA | ERROR_TEST_CHECK_FAIL); +ERROR_LIMITS: + kfree(msRawFrame.node_data); + kfree(thresholds); + return ret; +} + +int production_test_ms_cx(char *path_limits, + int stop_on_fail, struct TestToDo *todo) +{ + int ret; + int count_fail = 0; + + int *thresholds = NULL; + int *thresholds_min = NULL; + int *thresholds_max = NULL; + int trows, tcolumns; + + struct MutualSenseData msCompData; + + u8 *adjhor = NULL; + + u8 *adjvert = NULL; + + u16 container; + u16 *total_cx = NULL; + u16 *total_adjhor = NULL; + u16 *total_adjvert = NULL; + + //MS CX TEST + logError(0, "%s\n", tag); + logError(0, "%s MS CX Testes are starting...\n", tag); + //read MS compensation data + ret = readMutualSenseCompensationData(MS_TOUCH_ACTIVE, &msCompData); + if (ret < 0) { + logError(1, "%s %s:readMutualSenseCompensationData ", + tag, __func__); + logError(1, "failed %02X\n", ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + logError(0, "%s MS CX1 TEST:\n", tag); + if (todo->MutualCx1 == 1) { + ret = parseProductionTestLimits(path_limits, + MS_CX1_MIN_MAX, + &thresholds, + &trows, + &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 2)) { + logError(1, "%s %s:parseProductionTestLimits failed ", + tag, __func__); + logError(1, "ERROR %02X\n", ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + container = (u16)msCompData.cx1; + ret = checkLimitsMinMax(&container, + 1, + 1, + thresholds[0], + thresholds[1]); //check the limits + if (ret != OK) { + logError(1, "%s %s:checkLimitsMinMax MS CX1 failed ", + tag, __func__); + logError(1, "ERROR COUNT:%d\n", ret); + logError(0, "%s MS CX1 TEST:.........FAIL\n\n", tag); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + logError(0, "%s MS CX1 TEST:..........OK\n\n", tag); + } else + logError(0, "%s MS CX1 TEST:.......SKIPPED\n\n", tag); + + kfree(thresholds); + thresholds = NULL; + + logError(0, "%s MS CX2 MIN MAX TEST:\n", tag); + if (todo->MutualCx2 == 1) { + ret = parseProductionTestLimits(path_limits, + MS_CX2_MAP_MIN, + &thresholds_min, + &trows, + &tcolumns); //load min thresholds + if (ret < 0 || (trows != msCompData.header.force_node + || tcolumns != msCompData.header.sense_node)) { + logError(1, "%s %s:parseProductionTestLimits ", + tag, __func__); + logError(1, "failed %02X\n", ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = parseProductionTestLimits(path_limits, + MS_CX2_MAP_MAX, + &thresholds_max, + &trows, + &tcolumns); //load max thresholds + if (ret < 0 || (trows != msCompData.header.force_node + || tcolumns != msCompData.header.sense_node)) { + logError(1, "%s %s: MS_CX2_MAP_MAX failed ERROR %02X\n", + tag, __func__, ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMap(msCompData.node_data, + msCompData.header.force_node, + msCompData.header.sense_node, + thresholds_min, + thresholds_max);//check the limits + if (ret != OK) { + logError(1, "%s %s:checkLimitsMap MS CX2 MIN MAX ", + tag, __func__); + logError(1, "failed ERR_COUNT:%d\n", ret); + logError(0, "%s MS CX2 MIN MAX TEST:......FAIL\n\n", + tag); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + logError(0, "%s MS CX2 MIN MAX TEST:....OK\n\n", tag); + + kfree(thresholds_min); + thresholds_min = NULL; + kfree(thresholds_max); + thresholds_max = NULL; + } else + logError(0, "%s MS CX2 MIN MAX TEST:....SKIPPED\n\n", tag); + + logError(0, "%s MS CX2 ADJ TEST:\n", tag); + if (todo->MutualCx2Adj == 1) { + //MS CX2 ADJ HORIZ + logError(0, "%s MS CX2 ADJ HORIZ TEST:\n", tag); + + ret = computeAdjHoriz(msCompData.node_data, + msCompData.header.force_node, + msCompData.header.sense_node, + &adjhor); + if (ret < 0) { + logError(1, "%s %s:computeAdjHoriz failed...", + tag, __func__); + logError(1, "ERROR %02X\n", ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + logError(0, "%s MS CX2 ADJ HORIZ computed!\n", tag); + + ret = parseProductionTestLimits(path_limits, + MS_CX2_ADJH_MAP_MAX, + &thresholds_max, &trows, + &tcolumns); + if (ret < 0 || (trows != msCompData.header.force_node + || tcolumns != msCompData.header.sense_node - 1)) { + + logError(1, "%s %s: MS_CX2_ADJH_MAP_MAX failed...", + tag, __func__); + logError(1, "ERROR %02X\n", ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapAdj(adjhor, + msCompData.header.force_node, + msCompData.header.sense_node - 1, + thresholds_max); + if (ret != OK) { + logError(1, "%s %s:checkLimitsMapAdj CX2 ADJH failed ", + tag, __func__); + logError(1, "ERROR COUNT:%d\n", ret); + logError(0, "%s MS CX2 ADJ HORIZ TEST:..FAIL\n\n", tag); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + logError(0, "%s MS CX2 ADJ HORIZ TEST:..OK\n\n", tag); + + kfree(thresholds_max); + thresholds_max = NULL; + kfree(adjhor); + adjhor = NULL; + + //MS CX2 ADJ VERT + logError(0, "%s MS CX2 ADJ VERT TEST:\n", tag); + ret = computeAdjVert(msCompData.node_data, + msCompData.header.force_node, + msCompData.header.sense_node, + &adjvert); + if (ret < 0) { + logError(1, "%s %s:computeAdjVert failed... ", + tag, __func__); + logError(1, "ERROR %02X\n", ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + logError(0, "%s MS CX2 ADJ VERT computed!\n", tag); + + ret = parseProductionTestLimits(path_limits, + MS_CX2_ADJV_MAP_MAX, + &thresholds_max, + &trows, + &tcolumns); + if (ret < 0 || (trows != msCompData.header.force_node - 1 + || tcolumns != msCompData.header.sense_node)) { + logError(1, "%s %s:MS_CX2_ADJV_MAP_MAX failed ", + tag, __func__); + logError(1, "ERROR %02X\n", ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapAdj(adjvert, + msCompData.header.force_node - 1, + msCompData.header.sense_node - 1, + thresholds_max); + if (ret != OK) { + logError(1, "%s %s:checkLimitsMapAdj CX2 ADJV failed ", + tag, __func__); + logError(1, "COUNT:%d\n", ret); + logError(0, "%s MS CX2 ADJ HORIZ TEST:FAIL\n\n", tag); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + logError(0, "%s MS CX2 ADJ VERT TEST:OK\n\n", tag); + + kfree(thresholds_max); + thresholds_max = NULL; + kfree(adjvert); + adjvert = NULL; + } else + logError(0, "%s MS CX2 ADJ TEST:SKIPPED\n\n", tag); + + //START OF TOTAL CHECK + logError(0, "%s MS TOTAL CX TEST:\n", tag); + + if (todo->MutualCxTotal == 1 || todo->MutualCxTotalAdj == 1) { + ret = computeTotal(msCompData.node_data, + msCompData.cx1, + msCompData.header.force_node, + msCompData.header.sense_node, + CX1_WEIGHT, + CX2_WEIGHT, + &total_cx); + if (ret < 0) { + logError(1, "%s %s:computeTotalCx failed...", + tag, __func__); + logError(1, "ERROR %02X\n", ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + logError(0, "%s MS TOTAL CX MIN MAX TEST:\n", tag); + if (todo->MutualCxTotal == 1) { + ret = parseProductionTestLimits(path_limits, + MS_TOTAL_CX_MAP_MIN, + &thresholds_min, + &trows, + &tcolumns);//load min thresholds + if (ret < 0 || (trows != msCompData.header.force_node + || tcolumns != msCompData.header.sense_node)) { + logError(1, "%s %s:parseProductionTestLimits ", + tag, __func__); + logError(1, "failed %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + //load max thresholds + ret = parseProductionTestLimits(path_limits, + MS_TOTAL_CX_MAP_MAX, + &thresholds_max, + &trows, + &tcolumns); + if (ret < 0 || (trows != msCompData.header.force_node + || tcolumns != msCompData.header.sense_node)) { + logError(1, "%s %s:MS_TOTAL_CX_MAP_MAX failed", + tag, __func__); + logError(1, "...ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapTotal(total_cx, + msCompData.header.force_node, + msCompData.header.sense_node, + thresholds_min, + thresholds_max);//check the limits + if (ret != OK) { + logError(1, "%s %s:MS TOTAL CX TEST failed ", + tag, __func__); + logError(1, "COUNT:%d\n", ret); + logError(0, "%s MS TOTAL CX MIN MAX ", tag); + logError(0, "TEST:FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else { + logError(0, "%s MS TOTAL CX MIN MAX TEST", tag); + logError(0, ":OK\n\n"); + } + + kfree(thresholds_min); + thresholds_min = NULL; + kfree(thresholds_max); + thresholds_max = NULL; + } else + logError(0, "%s MS TOTAL CX MIN MAX TEST:SKIPPED\n\n", + tag); + + + logError(0, "%s MS TOTAL CX ADJ TEST:\n", tag); + if (todo->MutualCxTotalAdj == 1) { + //MS TOTAL CX ADJ HORIZ + logError(0, "%s MS TOTAL CX ADJ HORIZ TEST:\n", tag); + + //thresholds_max = NULL; + ret = computeAdjHorizTotal(total_cx, + msCompData.header.force_node, + msCompData.header.sense_node, + &total_adjhor); + if (ret < 0) { + logError(1, "%s production_test_data: ", tag); + logError(1, "computeAdjHoriz failed... "); + logError(1, "ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + logError(0, "%s MS TOTAL CX ADJ HORIZ computed!\n", + tag); + ret = parseProductionTestLimits(path_limits, + MS_TOTAL_CX_ADJH_MAP_MAX, + &thresholds_max, &trows, + &tcolumns); + if (ret < 0 || (trows != msCompData.header.force_node + || tcolumns != msCompData.header.sense_node - 1)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "MS_TOTAL_CX_ADJH_MAP_MAX "); + logError(1, "failed...RROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapAdjTotal(total_adjhor, + msCompData.header.force_node, + msCompData.header.sense_node - 1, + thresholds_max); + if (ret != OK) { + logError(1, "%s production_test_data: ", tag); + logError(1, "checkLimitsMapAdj MS TOTAL "); + logError(1, "CX ADJH failed... "); + logError(1, "ERROR COUNT = %d\n", ret); + logError(0, "%s MS TOTAL CX ADJ HORIZ ", tag); + logError(0, "TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else { + logError(0, "%s MS TOTAL CX ADJ HORIZ ", tag); + logError(0, "TEST:.................OK\n\n"); + } + + kfree(thresholds_max); + thresholds_max = NULL; + kfree(total_adjhor); + total_adjhor = NULL; + + //MS TOTAL CX ADJ VERT + logError(0, "%s MS TOTAL CX ADJ VERT TEST:\n", tag); + + ret = computeAdjVertTotal(total_cx, + msCompData.header.force_node, + msCompData.header.sense_node, + &total_adjvert); + if (ret < 0) { + logError(1, "%s production_test_data: ", tag); + logError(1, "computeAdjVert failed... "); + logError(1, "ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + logError(0, "%s MS TOTAL CX ADJ VERT computed!\n", + tag); + + ret = parseProductionTestLimits(path_limits, + MS_TOTAL_CX_ADJV_MAP_MAX, + &thresholds_max, + &trows, + &tcolumns); + if (ret < 0 || + (trows != msCompData.header.force_node - 1 + || tcolumns != msCompData.header.sense_node)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "MS_TOTAL_CX_ADJV_MAP_MAX failed"); + logError(1, "... ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapAdjTotal(total_adjvert, + msCompData.header.force_node - 1, + msCompData.header.sense_node - 1, + thresholds_max); + if (ret != OK) { + logError(1, "%s production_test_data: ", tag); + logError(1, "checkLimitsMapAdj MS TOTAL "); + logError(1, "CX ADJV failed... "); + logError(1, "ERROR COUNT = %d\n", ret); + logError(0, "%s MS TOTAL CX ADJ HORIZ ", tag); + logError(0, "TEST:.................FAIL\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else { + logError(0, "%s MS TOTAL CX ADJ VERT ", tag); + logError(0, "TEST:.................OK\n"); + } + + kfree(thresholds_max); + thresholds_max = NULL; + kfree(total_adjvert); + total_adjvert = NULL; + } else { + logError(0, "%s MS TOTAL CX ADJ ", tag); + logError(0, "TEST:.................SKIPPED\n"); + } + + kfree(total_cx); + total_cx = NULL; + } else + logError(0, "%s MS TOTAL CX TEST:.................SKIPPED\n", + tag); + + if ((todo->MutualKeyCx1 + | todo->MutualKeyCx2 | todo->MutualKeyCxTotal) == 1) { + ret = production_test_ms_key_cx(path_limits, + stop_on_fail, + todo); + if (ret < 0) { + count_fail += 1; + logError(1, "%s production_test_data: ", tag); + logError(1, "production_test_ms_key_cx failed..."); + logError(1, "ERROR = %02X\n", ret); + logError(0, "%s MS CX testes finished!", tag); + logError(0, ".................FAILED "); + logError(0, "fails_count = %d\n\n", count_fail); + return ret; + } + } else + logError(0, "%s MS KEY CX TEST:.................SKIPPED\n", + tag); + + if ((todo->MutualKeyCx1 | todo->MutualKeyCx2 + | todo->MutualKeyCxTotal) == 1) { + ret = production_test_ms_key_cx(path_limits, + stop_on_fail, + todo); + + if (ret < 0) { + count_fail += 1; + logError(1, "%s %s:production_test_ms_key_cx ", + tag, __func__); + logError(1, "failed :%02X\n", ret); + logError(0, "%s MS CX testes finished! ", tag); + logError(0, "fails_count = %d\n\n", count_fail); + return ret; + } + } else + logError(0, "%s MS KEY CX TEST:..SKIPPED\n", tag); +ERROR: + logError(0, "%s\n", tag); + if (count_fail == 0) { + logError(0, "%s MS CX testes finished! OK\n", tag); + kfree(msCompData.node_data); + msCompData.node_data = NULL; + return OK; + } + print_frame_u8("MS Init Data (Cx2) =", + array1dTo2d_u8(msCompData.node_data, + msCompData.node_data_size, + msCompData.header.sense_node), + msCompData.header.force_node, + msCompData.header.sense_node); + logError(0, "%s MS CX testes finished! fails_count = %d\n\n", + tag, count_fail); + kfree(thresholds); + kfree(thresholds_min); + kfree(thresholds_max); + kfree(adjhor); + kfree(adjvert); + kfree(total_cx); + kfree(total_adjhor); + kfree(total_adjvert); + kfree(msCompData.node_data); + + return (ERROR_TEST_CHECK_FAIL | ERROR_PROD_TEST_DATA); + +ERROR_LIMITS: + kfree(thresholds); + kfree(thresholds_min); + kfree(thresholds_max); + kfree(adjhor); + kfree(adjvert); + kfree(total_cx); + kfree(total_adjhor); + kfree(total_adjvert); + kfree(msCompData.node_data); + return ret; +} + +int production_test_ms_key_cx(char *path_limits, int stop_on_fail, + struct TestToDo *todo) +{ + int ret; + int count_fail = 0; + int num_keys = 0; + + int *thresholds = NULL; + int *thresholds_min = NULL; + int *thresholds_max = NULL; + int trows, tcolumns; + + struct MutualSenseData msCompData; + + u16 container; + u16 *total_cx = NULL; + + + //MS CX TEST + logError(0, "%s MS KEY CX Testes are starting...\n", tag); + + //read MS compensation data + ret = readMutualSenseCompensationData(MS_KEY, &msCompData); + if (ret < 0) { + logError(0, "%s production_test_data: ", tag); + logError(0, "readMutualSenseCompensationData failed... "); + logError(0, "ERROR %02X\n", ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + //the meaningful data are only in the first row, the other rows are + // only a copy of the first one + if (msCompData.header.force_node > msCompData.header.sense_node) + num_keys = msCompData.header.force_node; + else + num_keys = msCompData.header.sense_node; + + logError(0, "%s MS KEY CX1 TEST:\n", tag); + if (todo->MutualKeyCx1 == 1) { + + ret = parseProductionTestLimits(path_limits, + MS_KEY_CX1_MIN_MAX, + &thresholds, + &trows, + &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 2)) { + logError(0, "%s production_test_data: ", tag); + logError(0, "parseProductionTestLimits "); + logError(0, "MS_KEY_CX1_MIN_MAX failed... "); + logError(0, "ERROR %02X\n", ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + container = (u16) msCompData.cx1; + ret = checkLimitsMinMax(&container, + 1, + 1, + thresholds[0], + thresholds[1]); //check the limits + if (ret != OK) { + logError(1, "%s production_test_data: ", tag); + logError(1, "checkLimitsMinMax MS CX1 failed... "); + logError(1, "ERROR COUNT = %d\n", ret); + logError(0, "%s MS KEY CX1 TEST:................", tag); + logError(0, ".FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else { + logError(0, "%s MS KEY CX1 TEST:................", tag); + logError(0, ".OK\n\n"); + } + } else + logError(0, "%s MS KEY CX1 TEST:.................SKIPPED\n\n", + tag); + + kfree(thresholds); + thresholds = NULL; + + logError(0, "%s MS KEY CX2 TEST:\n", tag); + if (todo->MutualKeyCx2 == 1) { + //load min thresholds + ret = parseProductionTestLimits(path_limits, + MS_KEY_CX2_MAP_MIN, + &thresholds_min, + &trows, + &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != num_keys)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "MS_KEY_CX2_MAP_MIN failed... "); + logError(1, "ERROR %02X\n", ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + //load max thresholds + ret = parseProductionTestLimits(path_limits, + MS_KEY_CX2_MAP_MAX, + &thresholds_max, + &trows, + &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != num_keys)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "MS_KEY_CX2_MAP_MAX failed... "); + logError(1, "ERROR %02X\n", ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + //check the limits + ret = checkLimitsMap(msCompData.node_data, + 1, + num_keys, + thresholds_min, + thresholds_max); + if (ret != OK) { + logError(1, "%s production_test_data: ", tag); + logError(1, "checkLimitsMap MS KEY CX2 failed... "); + logError(1, "ERROR COUNT = %d\n", ret); + logError(0, "%s MS KEY CX2 TEST:................", tag); + logError(0, ".FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else { + logError(0, "%s MS KEY CX2 TEST:...............", tag); + logError(0, "..OK\n\n"); + } + + kfree(thresholds_min); + thresholds_min = NULL; + kfree(thresholds_max); + thresholds_max = NULL; + } else + logError(0, "%s MS CX2 TEST:.................SKIPPED\n\n", + tag); + + //START OF TOTAL CHECK + logError(0, "%s MS KEY TOTAL CX TEST:\n", tag); + + if (todo->MutualKeyCxTotal == 1) { + ret = computeTotal(msCompData.node_data, + msCompData.cx1, 1, + num_keys, + CX1_WEIGHT, + CX2_WEIGHT, + &total_cx); + if (ret < 0) { + logError(1, "%s production_test_data: ", tag); + logError(1, "computeTotalCx failed... "); + logError(1, "ERROR %02X\n", ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + //load min thresholds + ret = parseProductionTestLimits(path_limits, + MS_KEY_TOTAL_CX_MAP_MIN, + &thresholds_min, + &trows, + &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != num_keys)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "%parseProductionTestLimits "); + logError(1, "MS_KEY_TOTAL_CX_MAP_MIN failed... "); + logError(1, "ERROR %02X\n", ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + //load max thresholds + ret = parseProductionTestLimits(path_limits, + MS_KEY_TOTAL_CX_MAP_MAX, + &thresholds_max, + &trows, + &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != num_keys)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "MS_KEY_TOTAL_CX_MAP_MAX failed... "); + logError(1, "ERROR %02X\n", ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + //check the limits + ret = checkLimitsMapTotal(total_cx, + 1, + num_keys, + thresholds_min, + thresholds_max); + if (ret != OK) { + logError(1, "%s production_test_data: ", tag); + logError(1, "checkLimitsMap MS TOTAL "); + logError(1, "KEY CX TEST failed... ERROR COUNT = %d\n", + ret); + logError(0, "%s MS KEY TOTAL CX TEST:...........", tag); + logError(0, "......FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else { + logError(0, "%s MS KEY TOTAL CX TEST:...........", tag); + logError(0, "......OK\n\n"); + } + + kfree(thresholds_min); + thresholds_min = NULL; + kfree(thresholds_max); + thresholds_max = NULL; + + kfree(total_cx); + total_cx = NULL; + } else { + logError(0, "%s MS KEY TOTAL CX TEST:.................", tag); + logError(0, "SKIPPED\n"); + } + +ERROR: + logError(0, "%s\n", tag); + if (count_fail == 0) { + logError(0, + "%s MS KEY CX testes finished! OK\n", tag); + kfree(msCompData.node_data); + msCompData.node_data = NULL; + return OK; + } + print_frame_u8("MS Key Init Data (Cx2) =", + array1dTo2d_u8(msCompData.node_data, + msCompData.node_data_size, + msCompData.header.sense_node), + 1, + msCompData.header.sense_node); + logError(0, "%s MS Key CX testes finished!..............", tag); + logError(0, "...FAILED fails_count = %d\n\n", count_fail); + kfree(thresholds); + kfree(thresholds_min); + kfree(thresholds_max); + kfree(msCompData.node_data); + kfree(total_cx); + return (ERROR_TEST_CHECK_FAIL | ERROR_PROD_TEST_DATA); +ERROR_LIMITS: + kfree(thresholds); + kfree(thresholds_min); + kfree(thresholds_max); + kfree(msCompData.node_data); + kfree(total_cx); + return ret; +} + +int production_test_ss_raw(char *path_limits, + int stop_on_fail, struct TestToDo *todo) +{ + int ret; + int count_fail = 0; + int rows, columns; + + //short *ssRawFrame = NULL; + struct SelfSenseFrame ssRawFrame; + + int *thresholds = NULL; + int trows, tcolumns; + + //MS SS TEST + logError(0, "%s\n", tag); + logError(0, "%s SS RAW Testes are starting...\n", tag); + + //******* Self Sense Test ***************/ + logError(0, "%s Getting SS Frame...\n", tag); + ret = getSSFrame2(SS_TOUCH, &ssRawFrame); + if (ret < 0) { + logError(1, "%s %s:getSSFrame failed...ERROR %02X\n", + tag, __func__, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + //SS RAW (PROXIMITY) FORCE TEST + logError(0, "%s SS RAW (PROXIMITY) FORCE TEST:\n", tag); + + if (todo->SelfForceRaw == 1 || todo->SelfForceRawGap == 1) { + //there are no data for the sense + //channels due to the fact that + //the force frame is analized + columns = 1; + rows = ssRawFrame.header.force_node; + + logError(0, "%s SS RAW (PROXIMITY) FORCE MIN MAX TEST:\n", tag); + if (todo->SelfForceRaw == 1) { + ret = parseProductionTestLimits(path_limits, + SS_RAW_FORCE_MIN_MAX, + &thresholds, + &trows, + &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 2)) { + logError(1, "%s %s:parseProductionTestLimits ", + tag, __func__); + logError(1, "failed %02X\n", + ERROR_PROD_TEST_DATA); + //return (ret | ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMinMax(ssRawFrame.force_data, + rows, columns, + thresholds[0], + thresholds[1]); + if (ret != OK) { + logError(1, "%s %s:checkLimitsMinMax ", + tag, __func__); + logError(1, "failed ERROR COUNT:%d\n", ret); + logError(0, "%s SS RAW (PROXIMITY) FORCE", tag); + logError(0, " MIN MAX TEST:FAIL\n\n"); + count_fail += 1; + print_frame_short("SS Raw force frame =", + array1dTo2d_short(ssRawFrame.force_data, + rows*columns, columns), + rows, + columns); + if (stop_on_fail) { + ret = ERROR_PROD_TEST_DATA + | ERROR_TEST_CHECK_FAIL; + goto ERROR_LIMITS; + } + } else { + logError(0, "%s SS RAW (PROXIMITY) ", tag); + logError(0, "FORCE MIN MAX TEST:............."); + logError(0, "....OK\n\n"); + } + + kfree(thresholds); + thresholds = NULL; + } else { + logError(0, "%s SS RAW (PROXIMITY) ", tag); + logError(0, "FORCE MIN MAX TEST:................."); + logError(0, "SKIPPED\n\n"); + } + + logError(0, "%s\n", tag); + logError(0, "%s SS RAW (PROXIMITY) FORCE GAP TEST:\n", tag); + if (todo->SelfForceRawGap == 1) { + ret = parseProductionTestLimits(path_limits, + SS_RAW_FORCE_GAP, + &thresholds, + &trows, + &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 1)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "SS_RAW_FORCE_GAP failed... "); + logError(1, "ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsGap(ssRawFrame.force_data, + rows, + columns, + thresholds[0]); + + if (ret != OK) { + logError(1, "%s production_test_data: ", tag); + logError(1, "checkLimitsGap SS RAW "); + logError(1, "(PROXIMITY) FORCE GAP failed..."); + logError(1, "ERROR = %02X\n", ret); + logError(0, "%s SS RAW (PROXIMITY) ", tag); + logError(0, "FORCE GAP TEST:................."); + logError(0, "FAIL\n\n"); + count_fail += 1; + print_frame_short("SS Raw force frame =", + array1dTo2d_short(ssRawFrame.force_data, + rows*columns, columns), + rows, + columns); + if (stop_on_fail) { + ret = ERROR_PROD_TEST_DATA + | ERROR_TEST_CHECK_FAIL; + goto ERROR_LIMITS; + } + } else { + logError(0, "%s SS RAW (PROXIMITY) ", tag); + logError(0, "FORCE GAP TEST:................."); + logError(0, "OK\n\n"); + } + + kfree(thresholds); + thresholds = NULL; + } else { + logError(0, "%s SS RAW (PROXIMITY) ", tag); + logError(0, "FORCE GAP TEST:................."); + logError(0, "SKIPPED\n\n"); + } + + kfree(ssRawFrame.force_data); + ssRawFrame.force_data = NULL; + } else { + logError(0, "%s SS RAW (PROXIMITY) FORCE ", tag); + logError(0, "TEST:.................SKIPPED\n\n"); + } + + logError(0, "%s\n", tag); + //SS RAW (PROXIMITY) SENSE TEST + logError(0, "%s SS RAW (PROXIMITY) SENSE TEST:\n", tag); + + if (todo->SelfSenseRaw == 1 || todo->SelfSenseRawGap == 1) { + columns = ssRawFrame.header.sense_node; + // there are no data for the force channels due + // to the fact that the sense frame is analized + rows = 1; + + logError(0, "%s SS RAW (PROXIMITY) SENSE MIN MAX TEST:\n", tag); + if (todo->SelfSenseRaw == 1) { + ret = parseProductionTestLimits(path_limits, + SS_RAW_SENSE_MIN_MAX, + &thresholds, + &trows, + &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 2)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "SS_RAW_SENSE_MIN_MAX failed... "); + logError(1, "ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMinMax(ssRawFrame.sense_data, + rows, + columns, + thresholds[0], + thresholds[1]); + if (ret != OK) { + logError(1, "%s production_test_data: ", tag); + logError(1, "checkLimitsMinMax SS RAW "); + logError(1, "(PROXIMITY) SENSE failed... "); + logError(1, "ERROR COUNT = %d\n", ret); + logError(0, "%s SS RAW (PROXIMITY) ", tag); + logError(0, "SENSE MIN MAX TEST:............."); + logError(0, "....FAIL\n"); + count_fail += 1; + print_frame_short("SS Raw sense frame =", + array1dTo2d_short(ssRawFrame.sense_data, + rows*columns, columns), + rows, + columns); + if (stop_on_fail) { + ret = ERROR_PROD_TEST_DATA + | ERROR_TEST_CHECK_FAIL; + goto ERROR_LIMITS; + } + } else { + logError(0, "%s SS RAW (PROXIMITY) ", tag); + logError(0, "SENSE MIN MAX TEST:............."); + logError(0, "....OK\n"); + } + + kfree(thresholds); + thresholds = NULL; + } else { + logError(0, "%s SS RAW (PROXIMITY) SENSE MIN MAX", tag); + logError(0, " TEST:.................SKIPPED\n"); + } + + logError(0, "%s\n", tag); + logError(0, "%s SS RAW (PROXIMITY) SENSE GAP TEST:\n", tag); + if (todo->SelfSenseRawGap == 1) { + ret = parseProductionTestLimits(path_limits, + SS_RAW_SENSE_GAP, + &thresholds, + &trows, + &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 1)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "SS_RAW_SENSE_GAP failed... "); + logError(1, "ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsGap(ssRawFrame.sense_data, + rows, + columns, + thresholds[0]); + if (ret != OK) { + logError(1, "%s production_test_data: ", tag); + logError(1, "checkLimitsGap SS RAW "); + logError(1, "(PROXIMITY) SENSE GAP failed... "); + logError(1, "ERROR = %02X\n", ret); + logError(0, "%s SS RAW (PROXIMITY) ", tag); + logError(0, "SENSE GAP TEST:................."); + logError(0, "FAIL\n"); + count_fail += 1; + print_frame_short("SS Raw sense frame =", + array1dTo2d_short(ssRawFrame.sense_data, + rows*columns, columns), + rows, + columns); + if (stop_on_fail) { + ret = ERROR_PROD_TEST_DATA + | ERROR_TEST_CHECK_FAIL; + goto ERROR_LIMITS; + } + } else { + logError(0, "%s SS RAW (PROXIMITY) SENSE", tag); + logError(0, " GAP TEST:.................OK\n"); + } + + kfree(thresholds); + thresholds = NULL; + } else { + logError(0, "%s SS RAW (PROXIMITY) SENSE GAP ", tag); + logError(0, "TEST:.................SKIPPED\n"); + } + + kfree(ssRawFrame.sense_data); + ssRawFrame.sense_data = NULL; + } + + logError(0, "%s\n", tag); + if (count_fail == 0) { + logError(0, "%s SS RAW testes finished!.................OK\n\n", + tag); + return OK; + } + logError(0, "%s SS RAW testes finished!.................", tag); + logError(0, "FAILED fails_count = %d\n\n", count_fail); + return (ERROR_TEST_CHECK_FAIL | ERROR_PROD_TEST_DATA); + +ERROR_LIMITS: + kfree(ssRawFrame.force_data); + kfree(ssRawFrame.sense_data); + kfree(thresholds); + return ret; + +} + +int production_test_ss_ix_cx(char *path_limits, int stop_on_fail, + struct TestToDo *todo) +{ + int ret; + int count_fail = 0; + + int *thresholds = NULL; + int trows, tcolumns; + int *thresholds_min = NULL; + int *thresholds_max = NULL; + + struct SelfSenseData ssCompData; + + u8 *adjhor = NULL; + u8 *adjvert = NULL; + + u16 container; + int *ix1_w = NULL; + int *ix2_w = NULL; + u16 *total_ix = NULL; + u16 *total_cx = NULL; + + u16 *total_adjhor = NULL; + u16 *total_adjvert = NULL; + + logError(0, "%s\n", tag); + logError(0, "%s SS IX CX testes are starting...\n", tag); + + //read the SS compensation data + ret = readSelfSenseCompensationData(SS_TOUCH, &ssCompData); + if (ret < 0) { + logError(1, "%s production_test_data: ", tag); + logError(1, "readSelfSenseCompensationData failed... ", tag); + logError(1, "ERROR %02X\n", ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + //********************** SS FORCE IX *********************************/ + //SS IX1 FORCE TEST + logError(0, "%s SS IX1 FORCE TEST:\n", tag); + if (todo->SelfForceIx1 == 1) { + ret = parseProductionTestLimits(path_limits, + SS_IX1_FORCE_MIN_MAX, + &thresholds, + &trows, + &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 2)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "SS_IX1_FORCE_MIN_MAX failed... "); + logError(1, "ERROR %02X\n", ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + container = (u16) ssCompData.f_ix1; + + //check the limits + ret = checkLimitsMinMax(&container, + 1, + 1, + thresholds[0], + thresholds[1]); + if (ret != OK) { + logError(1, "%s production_test_data: ", tag); + logError(1, "checkLimitsMinMax "); + logError(1, "SS IX1 FORCE TEST failed... "); + logError(1, "ERROR COUNT = %d\n", ret); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + logError(0, "%s SS IX1 FORCE TEST:......OK\n\n", tag); + } + + kfree(thresholds); + thresholds = NULL; + //SS IX2 FORCE TEST + logError(0, "%s SS IX2 FORCE MIN MAX TEST:\n", tag); + if (todo->SelfForceIx2 == 1) { + //load the min thresholds + ret = parseProductionTestLimits(path_limits, + SS_IX2_FORCE_MAP_MIN, + &thresholds_min, + &trows, + &tcolumns); + if (ret < 0 || (trows != ssCompData.header.force_node + || tcolumns != 1)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "SS_IX2_FORCE_MAP_MIN "); + logError(1, "failed... ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + //load the max thresholds + ret = parseProductionTestLimits(path_limits, + SS_IX2_FORCE_MAP_MAX, + &thresholds_max, + &trows, + &tcolumns); + if (ret < 0 || (trows != ssCompData.header.force_node + || tcolumns != 1)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits"); + logError(1, "SS_IX2_FORCE_MAP_MAX failed... "); + logError(1, "ERROR %02X\n", ERROR_PROD_TEST_DATA); + + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + //check the values with thresholds + ret = checkLimitsMap(ssCompData.ix2_fm, + ssCompData.header.force_node, + 1, + thresholds_min, + thresholds_max); + if (ret != OK) { + logError(1, "%s production_test_data: ", tag); + logError(1, "checkLimitsMap SS IX2 FORCE failed... "); + logError(1, "ERROR COUNT = %d\n", ret); + logError(0, "%s SS IX2 FORCE MIN MAX TEST:.........."); + logError(0, "FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else { + logError(0, "%s SS IX2 FORCE MIN MAX TEST:.....", tag); + logError(0, "OK\n\n"); + } + + kfree(thresholds_min); + thresholds_min = NULL; + kfree(thresholds_max); + thresholds_max = NULL; + } else { + logError(0, "%s SS IX2 FORCE MIN MAX TEST:...........", tag); + logError(0, "KIPPED\n\n"); + } + + logError(0, "%s SS IX2 FORCE ADJ TEST:\n", tag); + if (todo->SelfForceIx2Adj == 1) { + //SS IX2 FORCE ADJV TEST + logError(0, "%s SS IX2 FORCE ADJVERT TEST:\n", tag); + ret = computeAdjVert(ssCompData.ix2_fm, + ssCompData.header.force_node, + 1, + &adjvert); + if (ret < 0) { + logError(1, "%s production_test_data: ", tag); + logError(1, "computeAdjVert SS IX2 FORCE ADJV "); + logError(1, "failed... ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + logError(0, "%s SS IX2 FORCE ADJV computed!\n", tag); + + //load the max thresholds + ret = parseProductionTestLimits(path_limits, + SS_IX2_FORCE_ADJV_MAP_MAX, + &thresholds_max, + &trows, + &tcolumns); + if (ret < 0 || (trows != ssCompData.header.force_node - 1 + || tcolumns != 1)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "SS_IX2_FORCE_ADJV_MAP_MAX failed... "); + logError(1, "ERROR %02X\n", ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + //check the values with thresholds + ret = checkLimitsMapAdj(adjvert, + ssCompData.header.force_node - 1, + 1, + thresholds_max); + if (ret != OK) { + logError(1, "%s production_test_data: ", tag); + logError(1, "checkLimitsMap SS IX2 FORCE failed... "); + logError(0, "FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else { + logError(0, "%s SS IX2 FORCE ADJV TEST:", tag); + logError(0, ".................OK\n\n"); + } + + kfree(thresholds_max); + thresholds_max = NULL; + kfree(adjvert); + adjvert = NULL; + + } else { + logError(0, "%s SS IX2 FORCE ADJ TEST:", tag); + logError(0, ".................SKIPPED\n\n"); + } + + //SS TOTAL FORCE IX + logError(0, "%s SS TOTAL IX FORCE TEST:\n", tag); + if (todo->SelfForceIxTotal == 1 || todo->SelfForceIxTotalAdj == 1) { + logError(0, "%s Reading TOTAL IX FORCE Weights...\n", tag); + + //load the IX1 weight + ret = parseProductionTestLimits(path_limits, + SS_IX1_FORCE_W, + &ix1_w, + &trows, + &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 1)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "SS_IX1_FORCE_W failed... ERROR %02X\n", + tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + //load the IX2 weight + ret = parseProductionTestLimits(path_limits, + SS_IX2_FORCE_W, + &ix2_w, + &trows, + &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 1)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "SS_IX1_FORCE_W failed... ERROR %02X\n", + tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + logError(0, "%s Weights: IX1_W = %d IX2_W = %d\n", + tag, *ix1_w, *ix2_w); + ret = computeTotal(ssCompData.ix2_fm, ssCompData.f_ix1, + ssCompData.header.force_node, + 1, + *ix1_w, + *ix2_w, + &total_ix); + if (ret < 0) { + logError(1, "%s production_test_data: ", tag); + logError(1, "computeTotal Ix Force failed... "); + logError(1, "ERROR %02X\n", ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + kfree(ix1_w); + ix1_w = NULL; + kfree(ix2_w); + ix2_w = NULL; + + logError(0, "%s SS TOTAL IX FORCE MIN MAX TEST:\n", tag); + if (todo->SelfForceIxTotal == 1) { + //load the min thresholds + ret = parseProductionTestLimits(path_limits, + SS_TOTAL_IX_FORCE_MAP_MIN, + &thresholds_min, + &trows, + &tcolumns); + if (ret < 0 || (trows != ssCompData.header.force_node + || tcolumns != 1)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "SS_TOTAL_IX_FORCE_MAP_MIN "); + logError(1, "failed... ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + //load the max thresholds + ret = parseProductionTestLimits(path_limits, + SS_TOTAL_IX_FORCE_MAP_MAX, + &thresholds_max, + &trows, + &tcolumns); + if (ret < 0 || (trows != ssCompData.header.force_node + || tcolumns != 1)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "SS_TOTAL_IX_FORCE_MAP_MAX "); + logError(1, "failed... ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + //check the values with thresholds + ret = checkLimitsMapTotal(total_ix, + ssCompData.header.force_node, + 1, + thresholds_min, + thresholds_max); + if (ret != OK) { + logError(1, "%s production_test_data: ", tag); + logError(1, "checkLimitsMap SS TOTAL IX FORCE"); + logError(1, "failed... ERROR COUNT = %d\n", + ret); + logError(0, "%s SS TOTAL IX FORCE MIN MAX ", + tag); + logError(0, "TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else { + logError(0, "%s SS TOTAL IX FORCE MIN MAX ", + tag); + logError(0, "TEST:.................OK\n\n"); + } + + kfree(thresholds_min); + thresholds_min = NULL; + kfree(thresholds_max); + thresholds_max = NULL; + } else { + logError(0, "%s SS TOTAL IX FORCE MIN MAX TEST:", tag); + logError(0, ".................SKIPPED\n"); + } + + logError(0, "%s SS TOTAL IX FORCE ADJ TEST:\n", tag); + if (todo->SelfForceIxTotalAdj == 1) { + //SS TOTAL IX FORCE ADJV TEST + logError(0, "%s SS TOTAL IX FORCE ADJVERT TEST:\n", + tag); + ret = computeAdjVertTotal(total_ix, + ssCompData.header.force_node, + 1, + &total_adjvert); + if (ret < 0) { + logError(1, "%s production_test_data: ", tag); + logError(1, "computeAdjVert SS TOTAL IX "); + logError(1, "FORCE ADJV failed... "); + logError(1, "ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + logError(0, "%s SS TOTAL IX FORCE ADJV computed!\n", + tag); + + //load the max thresholds + ret = parseProductionTestLimits(path_limits, + SS_TOTAL_IX_FORCE_ADJV_MAP_MAX, + &thresholds_max, + &trows, + &tcolumns); + if (ret < 0 + || (trows != ssCompData.header.force_node - 1 + || tcolumns != 1)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "SS_TOTAL_IX_FORCE_ADJV_MAP_MAX"); + logError(1, "... ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + //check the values with thresholds + ret = checkLimitsMapAdjTotal(total_adjvert, + ssCompData.header.force_node - 1, + 1, + thresholds_max); + if (ret != OK) { + logError(1, "%s production_test_data: ", tag); + logError(1, "checkLimitsMap SS TOTAL IX "); + logError(1, "FORCE failed... "); + logError(1, "ERROR COUNT = %d\n", ret); + logError(0, "%s SS TOTAL IX FORCE ADJV TEST:", + tag); + logError(0, ".................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else { + logError(0, "%s SS TOTAL IX FORCE ADJV TEST:", + tag); + logError(0, ".................OK\n\n"); + } + + kfree(thresholds_max); + thresholds_max = NULL; + kfree(total_adjvert); + total_adjvert = NULL; + } else { + logError(0, "%s SS TOTAL IX FORCE ADJ TEST:"); + logError(0, ".................SKIPPED\n"); + } + + kfree(total_ix); + total_ix = NULL; + } else { + logError(0, "%s SS TOTAL IX FORCE TEST:", tag); + logError(0, ".................SKIPPED\n\n"); + } + + + //************** SS SENSE IX *******************/ + //SS IX1 SENSE TEST + logError(0, "%s SS IX1 SENSE TEST:\n", tag); + if (todo->SelfSenseIx1 == 1) { + ret = parseProductionTestLimits(path_limits, + SS_IX1_SENSE_MIN_MAX, + &thresholds, + &trows, + &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 2)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "SS_IX1_SENSE_MIN_MAX failed... "); + logError(1, "ERROR %02X\n", ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + container = (u16) ssCompData.s_ix1; + ret = checkLimitsMinMax(&container, + 1, + 1, + thresholds[0], + thresholds[1]); //check the limits + if (ret != OK) { + logError(1, "%s production_test_data: ", tag); + logError(1, "checkLimitsMinMax SS IX1 SENSE TEST "); + logError(1, "failed... ERROR COUNT = %d\n", ret); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else { + logError(0, "%s SS IX1 SENSE TEST:..............", tag); + logError(0, "...OK\n\n"); + } + } else { + logError(0, "%s SS IX1 SENSE TEST:.................", tag); + logError(0, "SKIPPED\n\n"); + } + + kfree(thresholds); + thresholds = NULL; + //SS IX2 SENSE TEST + logError(0, "%s SS IX2 SENSE MIN MAX TEST:\n", tag); + if (todo->SelfSenseIx2 == 1) { + ret = parseProductionTestLimits(path_limits, + SS_IX2_SENSE_MAP_MIN, + &thresholds_min, + &trows, + &tcolumns); //load the min thresholds + if (ret < 0 || (trows != 1 + || tcolumns != ssCompData.header.sense_node)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "S_IX2_SENSE_MAP_MIN failed... "); + logError(1, "ERROR %02X\n", ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = parseProductionTestLimits(path_limits, + SS_IX2_SENSE_MAP_MAX, + &thresholds_max, + &trows, + &tcolumns); //load the max thresholds + if (ret < 0 || (trows != 1 + || tcolumns != ssCompData.header.sense_node)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "SS_IX2_SENSE_MAP_MAX failed... "); + logError(1, "ERROR %02X\n", ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + //check the values with thresholds + ret = checkLimitsMap(ssCompData.ix2_sn, + 1, + ssCompData.header.sense_node, + thresholds_min, + thresholds_max); + if (ret != OK) { + logError(1, "%s production_test_data: ", tag); + logError(1, "checkLimitsMap SS IX2 SENSE failed... "); + logError(1, "ERROR COUNT = %d\n", ret); + logError(0, "%s SS IX2 SENSE MIN MAX TEST:.....", tag); + logError(0, "............FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else { + logError(0, "%s SS IX2 SENSE MIN MAX TEST:", tag); + logError(0, ".................OK\n\n"); + } + + kfree(thresholds_min); + thresholds_min = NULL; + kfree(thresholds_max); + thresholds_max = NULL; + } else { + logError(0, "%s SS IX2 SENSE MIN MAX TEST:..............", tag); + logError(0, "...SKIPPED\n\n"); + } + + logError(0, "%s SS IX2 SENSE ADJ TEST:\n", tag); + if (todo->SelfSenseIx2Adj == 1) { + //SS IX2 SENSE ADJH TEST + logError(0, "%s SS IX2 SENSE ADJHORIZ TEST:\n", tag); + ret = computeAdjHoriz(ssCompData.ix2_sn, + 1, + ssCompData.header.sense_node, + &adjhor); + if (ret < 0) { + logError(1, "%s production_test_data: ", tag); + logError(1, "computeAdjHoriz SS IX2 SENSE ADJH "); + logError(1, "failed... ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + logError(0, "%s SS IX2 SENSE ADJ HORIZ computed!\n", tag); + + ret = parseProductionTestLimits(path_limits, + SS_IX2_SENSE_ADJH_MAP_MAX, + &thresholds_max, + &trows, + &tcolumns); //load the max thresholds + if (ret < 0 || (trows != 1 + || tcolumns != ssCompData.header.sense_node - 1)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "SS_IX2_SENSE_ADJH_MAP_MAX "); + logError(1, "failed... ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + //check the values with thresholds + ret = checkLimitsMapAdj(adjhor, + 1, + ssCompData.header.sense_node - 1, + thresholds_max); + if (ret != OK) { + logError(1, "%s production_test_data: ", tag); + logError(1, "checkLimitsMapAdj SS IX2 SENSE ADJH "); + logError(1, "failed... ERROR COUNT = %d\n", ret); + logError(0, "%s SS IX2 SENSE ADJH TEST:.......", tag); + logError(0, "..........FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else { + logError(0, "%s SS IX2 SENSE ADJH TEST:........", tag); + logError(0, ".........OK\n\n"); + } + + kfree(thresholds_max); + thresholds_max = NULL; + kfree(adjhor); + adjhor = NULL; + } else { + logError(0, "%s SS IX2 SENSE ADJ TEST:.................", tag); + logError(0, "SKIPPED\n", tag); + } + + //SS TOTAL IX SENSE + logError(0, "%s SS TOTAL IX SENSE TEST:\n", tag); + if (todo->SelfSenseIxTotal == 1 || todo->SelfSenseIxTotalAdj == 1) { + logError(0, "%s Reading TOTAL IX SENSE Weights...\n", tag); + //load the IX1 weight + ret = parseProductionTestLimits(path_limits, + SS_IX1_SENSE_W, + &ix1_w, + &trows, + &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 1)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "SS_IX1_SENSE_W failed... ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + //load the IX2 weight + ret = parseProductionTestLimits(path_limits, + SS_IX2_SENSE_W, + &ix2_w, + &trows, + &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 1)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "SS_IX1_SENSE_W failed... ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + logError(0, "%s Weights: IX1_W = %d IX2_W = %d\n", + tag, *ix1_w, *ix2_w); + + ret = computeTotal(ssCompData.ix2_sn, + ssCompData.s_ix1, + 1, + ssCompData.header.sense_node, + *ix1_w, + *ix2_w, + &total_ix); + if (ret < 0) { + logError(1, "%s production_test_data: ", tag); + logError(1, "computeTotal Ix Sense "); + logError(1, "failed... ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + kfree(ix1_w); + ix1_w = NULL; + kfree(ix2_w); + ix2_w = NULL; + + logError(0, "%s SS TOTAL IX SENSE MIN MAX TEST:\n", tag); + //load the min thresholds + if (todo->SelfSenseIxTotal == 1) { + ret = parseProductionTestLimits(path_limits, + SS_TOTAL_IX_SENSE_MAP_MIN, + &thresholds_min, + &trows, + &tcolumns); + if (ret < 0 || (trows != 1 + || tcolumns != ssCompData.header.sense_node)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "SS_TOTAL_IX_SENSE_MAP_MIN "); + logError(1, "failed... ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + //load the max thresholds + ret = parseProductionTestLimits(path_limits, + SS_TOTAL_IX_SENSE_MAP_MAX, + &thresholds_max, + &trows, + &tcolumns); + if (ret < 0 || (trows != 1 || + tcolumns != ssCompData.header.sense_node)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "SS_TOTAL_IX_SENSE_MAP_MAX "); + logError(1, "failed... ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + //check the values with thresholds + ret = checkLimitsMapTotal(total_ix, + 1, + ssCompData.header.sense_node, + thresholds_min, + thresholds_max); + if (ret != OK) { + logError(1, "%s production_test_data: ", tag); + logError(1, "checkLimitsMap SS TOTAL IX SENSE"); + logError(1, " failed... ERROR COUNT = %d\n", + ret); + logError(0, "%s SS TOTAL IX SENSE MIN MAX ", + tag); + logError(0, "TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else { + logError(0, "%s SS TOTAL IX SENSE MIN MAX ", + tag); + logError(0, "TEST:.................OK\n\n"); + } + + kfree(thresholds_min); + thresholds_min = NULL; + kfree(thresholds_max); + thresholds_max = NULL; + } else { + logError(0, "%s SS TOTAL IX SENSE MIN MAX ", tag); + logError(0, "TEST:.................SKIPPED\n"); + } + + + logError(0, "%s SS TOTAL IX SENSE ADJ TEST:\n", tag); + if (todo->SelfSenseIxTotalAdj == 1) { + //SS TOTAL IX SENSE ADJH TEST + logError(0, "%s SS TOTAL IX SENSE ADJHORIZ TEST:\n", + tag); + ret = computeAdjHorizTotal(total_ix, + 1, + ssCompData.header.sense_node, + &total_adjhor); + if (ret < 0) { + logError(1, "%s production_test_data: ", tag); + logError(1, "computeAdjHoriz SS TOTAL "); + logError(1, "IXSENSE ADJH failed... "); + logError(1, "ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + logError(0, "%s SS TOTAL IX SENSE ADJ HORIZ ", tag); + logError(0, "computed!\n"); + + //load the max thresholds + ret = parseProductionTestLimits(path_limits, + SS_TOTAL_IX_SENSE_ADJH_MAP_MAX, + &thresholds_max, + &trows, + &tcolumns); + if (ret < 0 || (trows != 1 + || tcolumns != ssCompData.header.sense_node - 1)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "SS_TOTAL_IX_SENSE_ADJH_MAP_MAX "); + logError(1, "failed... ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + //check the values with thresholds + ret = checkLimitsMapAdjTotal(total_adjhor, + 1, + ssCompData.header.sense_node - 1, + thresholds_max); + if (ret != OK) { + logError(1, "%s production_test_data: ", tag); + logError(1, "checkLimitsMapAdj SS TOTAL "); + logError(1, "IX SENSE ADJH failed... "); + logError(1, "ERROR COUNT = %d\n", ret); + logError(0, "%s SS TOTAL IX SENSE ADJH ", tag); + logError(0, "TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else { + logError(0, "%s SS TOTAL IX SENSE ADJH ", tag); + logError(0, "TEST:.................OK\n\n"); + } + + kfree(thresholds_max); + thresholds_max = NULL; + kfree(total_adjhor); + total_adjhor = NULL; + } else { + logError(0, "%s SS TOTAL IX SENSE ADJ TEST:.....", tag); + logError(0, "............SKIPPED\n"); + } + kfree(total_ix); + total_ix = NULL; + } else { + logError(0, "%s SS TOTAL IX SENSE TEST:............", tag); + logError(0, ".....SKIPPED\n"); + } + + //************************ SS SENSE CX *******************************/ + //SS CX1 FORCE TEST + logError(0, "%s SS CX1 FORCE TEST:\n", tag); + if (todo->SelfForceCx1 == 1) { + + ret = parseProductionTestLimits(path_limits, + SS_CX1_FORCE_MIN_MAX, + &thresholds, + &trows, + &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 2)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "SS_CX1_FORCE_MIN_MAX "); + logError(1, "failed... ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + //check the limits + container = (u16) ssCompData.f_cx1; + ret = checkLimitsMinMax(&container, + 1, + 1, + thresholds[0], + thresholds[1]); + if (ret != OK) { + logError(1, "%s production_test_data: ", tag); + logError(1, "checkLimitsMinMax SS CX1 FORCE TEST "); + logError(1, "failed... ERROR COUNT = %d\n", ret); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else { + logError(0, "%s SS CX1 FORCE TEST:.............", tag); + logError(0, "....OK\n\n"); + } + kfree(thresholds); + thresholds = NULL; + } else { + logError(0, "%s SS CX1 FORCE TEST:.................", tag); + logError(0, "SKIPPED\n\n"); + } + + //SS CX2 FORCE TEST + logError(0, "%s SS CX2 FORCE MIN MAX TEST:\n", tag); + if (todo->SelfForceCx2 == 1) { + //load the min thresholds + ret = parseProductionTestLimits(path_limits, + SS_CX2_FORCE_MAP_MIN, + &thresholds_min, + &trows, + &tcolumns); + if (ret < 0 || (trows != ssCompData.header.force_node + || tcolumns != 1)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "SS_CX2_FORCE_MAP_MIN "); + logError(1, "failed... ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + //load the max thresholds + ret = parseProductionTestLimits(path_limits, + SS_CX2_FORCE_MAP_MAX, + &thresholds_max, + &trows, + &tcolumns); + if (ret < 0 || (trows != ssCompData.header.force_node + || tcolumns != 1)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "SS_CX2_FORCE_MAP_MAX "); + logError(1, "failed... ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + //check the values with thresholds + ret = checkLimitsMap(ssCompData.cx2_fm, + ssCompData.header.force_node, + 1, + thresholds_min, + thresholds_max); + if (ret != OK) { + logError(1, "%s production_test_data: ", tag); + logError(1, "%checkLimitsMap SS CX2 FORCE "); + logError(1, "%failed... ERROR COUNT = %d\n", ret); + logError(0, "%s SS CX2 FORCE MIN MAX TEST:.....", tag); + logError(0, "............FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else { + logError(0, "%s SS CX2 FORCE MIN MAX TEST:......", tag); + logError(0, "...........OK\n\n"); + } + + kfree(thresholds_min); + thresholds_min = NULL; + kfree(thresholds_max); + thresholds_max = NULL; + } else { + logError(0, "%s SS CX2 FORCE MIN MAX TEST:..............", tag); + logError(0, "...SKIPPED\n"); + } + + logError(0, "%s SS CX2 FORCE ADJ TEST:\n", tag); + if (todo->SelfForceCx2Adj == 1) { + //SS CX2 FORCE ADJV TEST + logError(0, "%s SS CX2 FORCE ADJVERT TEST:\n", tag); + //comepute the ADJV for CX2 FORCE + ret = computeAdjVert(ssCompData.cx2_fm, + ssCompData.header.force_node, + 1, + &adjvert); + if (ret < 0) { + logError(1, "%s production_test_data: ", tag); + logError(1, "computeAdjVert SS CX2 FORCE ADJV "); + logError(1, "failed... ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + logError(0, "%s SS CX2 FORCE ADJV computed!\n", tag); + + //load the max thresholds + ret = parseProductionTestLimits(path_limits, + SS_CX2_FORCE_ADJV_MAP_MAX, + &thresholds_max, + &trows, + &tcolumns); + if (ret < 0 || (trows != ssCompData.header.force_node - 1 + || tcolumns != 1)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "SS_CX2_FORCE_ADJV_MAP_MAX "); + logError(1, "failed... ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + //check the values with thresholds + ret = checkLimitsMapAdj(adjvert, + ssCompData.header.force_node - 1, + 1, + thresholds_max); + if (ret != OK) { + logError(1, "%s production_test_data: ", tag); + logError(1, "checkLimitsMap SS IX2 FORCE "); + logError(1, "failed... ERROR COUNT = %d\n", ret); + logError(0, "%s SS CX2 FORCE ADJV TEST:......", tag); + logError(0, "...........FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else { + logError(0, "%s SS CX2 FORCE ADJV TEST:.....", tag); + logError(0, "............OK\n\n"); + } + + kfree(thresholds_max); + thresholds_max = NULL; + kfree(adjvert); + adjvert = NULL; + } else { + logError(0, "%s SS CX2 FORCE ADJ TEST:.................", tag); + logError(0, "SKIPPED\n\n"); + } + //SS TOTAL CX FORCE + logError(0, "%s SS TOTAL CX FORCE TEST:\n", tag); + if (todo->SelfForceCxTotal == 1 || todo->SelfForceCxTotalAdj == 1) { + ret = computeTotal(ssCompData.cx2_fm, + ssCompData.f_cx1, + ssCompData.header.force_node, + 1, + CX1_WEIGHT, + CX2_WEIGHT, + &total_cx); + if (ret < 0) { + logError(1, "%s production_test_data: ", tag); + logError(1, "computeTotal Cx Force failed... "); + logError(1, "ERROR %02X\n", ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + logError(0, "%s SS TOTAL CX FORCE MIN MAX TEST:\n", tag); + //load the min thresholds + if (todo->SelfForceCxTotal == 1) { + ret = parseProductionTestLimits(path_limits, + SS_TOTAL_CX_FORCE_MAP_MIN, + &thresholds_min, + &trows, + &tcolumns); + if (ret < 0 || (trows != ssCompData.header.force_node + || tcolumns != 1)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "SS_TOTAL_CX_FORCE_MAP_MIN "); + logError(1, "failed... ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + //load the max thresholds + ret = parseProductionTestLimits(path_limits, + SS_TOTAL_CX_FORCE_MAP_MAX, + &thresholds_max, + &trows, + &tcolumns); + if (ret < 0 || (trows != ssCompData.header.force_node + || tcolumns != 1)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "SS_TOTAL_CX_FORCE_MAP_MAX "); + logError(1, "failed... ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + //check the values with thresholds + ret = checkLimitsMapTotal(total_cx, + ssCompData.header.force_node, + 1, + thresholds_min, + thresholds_max); + if (ret != OK) { + logError(1, "%s production_test_data: ", tag); + logError(1, "checkLimitsMap SS TOTAL FORCE "); + logError(1, "failed... ERROR COUNT = %d\n", + ret); + logError(0, "%s SS TOTAL FORCE MIN MAX ", tag); + logError(0, "TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else { + logError(0, "%s SS TOTAL FORCE MIN MAX ", tag); + logError(0, "TEST:.................OK\n\n"); + } + + kfree(thresholds_min); + thresholds_min = NULL; + kfree(thresholds_max); + thresholds_max = NULL; + } else { + logError(0, "%s SS TOTAL CX FORCE MIN MAX TEST:", tag); + logError(0, ".................SKIPPED\n"); + } + + //SS TOTAL CX FORCE ADJV TEST + logError(0, "%s SS TOTAL CX FORCE ADJ TEST:\n", tag); + if (todo->SelfForceCxTotalAdj == 1) { + logError(0, "%s SS TOTAL CX FORCE ADJVERT ", tag); + logError(0, "TEST:\n"); + + //comepute the ADJV for CX2 FORCE + ret = computeAdjVertTotal(total_cx, + ssCompData.header.force_node, + 1, + &total_adjvert); + if (ret < 0) { + logError(1, "%s production_test_data: ", tag); + logError(1, "computeAdjVert SS TOTAL CX FORCE"); + logError(1, " ADJV failed... ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + logError(0, "%s SS TOTAL CX FORCE ADJV computed!\n", + tag); + + ret = parseProductionTestLimits(path_limits, + SS_TOTAL_CX_FORCE_ADJV_MAP_MAX, + &thresholds_max, + &trows, + &tcolumns); //load the max thresholds + if (ret < 0 + || (trows != ssCompData.header.force_node - 1 + || tcolumns != 1)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "SS_TOTAL_CX_FORCE_ADJV_MAP_MAX "); + logError(1, "failed... ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + //check the values with thresholds + ret = checkLimitsMapAdjTotal(total_adjvert, + ssCompData.header.force_node - 1, + 1, + thresholds_max); + if (ret != OK) { + logError(1, "%s production_test_data: ", tag); + logError(1, "checkLimitsMap SS TOTAL CX FORCE"); + logError(1, " failed... ERROR COUNT = %d\n", + ret); + logError(0, "%s SS TOTAL CX FORCE ADJV ", tag); + logError(0, "TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else { + logError(0, "%s SS TOTAL CX FORCE ADJV ", tag); + logError(0, "TEST:.................OK\n\n"); + } + + kfree(thresholds_max); + thresholds_max = NULL; + kfree(total_adjvert); + total_adjvert = NULL; + + } else { + logError(0, "%s SS TOTAL CX FORCE ADJ TEST:......", + tag); + logError(0, "..........SKIPPED\n"); + } + kfree(total_cx); + total_cx = NULL; + } else { + logError(0, "%s SS TOTAL CX FORCE TEST:.................", tag); + logError(0, "SKIPPED\n\n"); + } + + + //**************** SS SENSE CX **************************************/ + //SS CX1 SENSE TEST + logError(0, "%s SS CX1 SENSE TEST:\n", tag); + if (todo->SelfSenseCx1 == 1) { + ret = parseProductionTestLimits(path_limits, + SS_CX1_SENSE_MIN_MAX, + &thresholds, + &trows, + &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 2)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "SS_CX1_SENSE_MIN_MAX failed"); + logError(1, "... ERROR %02X\n", ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + container = (u16) ssCompData.s_cx1; + //check the limits + ret = checkLimitsMinMax(&container, + 1, + 1, + thresholds[0], + thresholds[1]); + if (ret != OK) { + logError(1, "%s production_test_data: ", tag); + logError(1, "checkLimitsMinMax SS CX1 SENSE TEST "); + logError(1, "failed... ERROR COUNT = %d\n", ret); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else { + logError(0, "%s SS CX1 SENSE TEST:..............", tag); + logError(0, "...OK\n\n"); + } + kfree(thresholds); + thresholds = NULL; + } else { + logError(0, "%s SS CX1 SENSE TEST:.................", tag); + logError(0, "SKIPPED\n\n"); + } + + + //SS CX2 SENSE TEST + logError(0, "%s SS CX2 SENSE MIN MAX TEST:\n", tag); + if (todo->SelfSenseCx2 == 1) { + //load the min thresholds + ret = parseProductionTestLimits(path_limits, + SS_CX2_SENSE_MAP_MIN, + &thresholds_min, + &trows, + &tcolumns); + if (ret < 0 || (trows != 1 + || tcolumns != ssCompData.header.sense_node)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "SS_CX2_SENSE_MAP_MIN failed... "); + logError(1, "ERROR %02X\n", ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + //load the max thresholds + ret = parseProductionTestLimits(path_limits, + SS_CX2_SENSE_MAP_MAX, + &thresholds_max, + &trows, + &tcolumns); + if (ret < 0 || (trows != 1 + || tcolumns != ssCompData.header.sense_node)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "SS_CX2_SENSE_MAP_MAX failed... "); + logError(1, "ERROR %02X\n", ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + //check the values with thresholds + ret = checkLimitsMap(ssCompData.cx2_sn, + 1, + ssCompData.header.sense_node, + thresholds_min, + thresholds_max); + if (ret != OK) { + logError(1, "%s production_test_data: ", tag); + logError(1, "checkLimitsMap SS CX2 SENSE failed... "); + logError(1, "ERROR COUNT = %d\n", ret); + logError(0, "%s SS CX2 SENSE MIN MAX TEST:......", tag); + logError(0, "...........FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else { + logError(0, "%s SS CX2 SENSE MIN MAX TEST:", tag); + logError(0, ".................OK\n\n"); + } + + kfree(thresholds_min); + thresholds_min = NULL; + kfree(thresholds_max); + thresholds_max = NULL; + } else { + logError(0, "%s SS CX2 SENSE MIN MAX TEST:.........", tag); + logError(0, "........SKIPPED\n"); + } + logError(0, "%s SS CX2 SENSE ADJ TEST:\n", tag); + if (todo->SelfSenseCx2Adj == 1) { + //SS CX2 SENSE ADJH TEST + logError(0, "%s SS CX2 SENSE ADJHORIZ TEST:\n", tag); + ret = computeAdjHoriz(ssCompData.cx2_sn, + 1, + ssCompData.header.sense_node, + &adjhor); + if (ret < 0) { + logError(1, "%s production_test_data: ", tag); + logError(1, "computeAdjHoriz SS CX2 SENSE ADJH "); + logError(1, "failed... ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + logError(0, "%s SS CX2 SENSE ADJH computed!\n", tag); + + //load the max thresholds + ret = parseProductionTestLimits(path_limits, + SS_CX2_SENSE_ADJH_MAP_MAX, + &thresholds_max, + &trows, + &tcolumns); + if (ret < 0 || (trows != 1 + || tcolumns != ssCompData.header.sense_node - 1)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "SS_IX2_SENSE_MAP_MAX failed... "); + logError(1, "ERROR %02X\n", ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + //check the values with thresholds + ret = checkLimitsMapAdj(adjhor, + 1, + ssCompData.header.sense_node - 1, + thresholds_max); + if (ret != OK) { + logError(1, "%s production_test_data: ", tag); + logError(1, "checkLimitsMapAdj SS CX2 SENSE ADJH "); + logError(1, "failed... ERROR COUNT = %d\n", ret); + logError(0, "%s SS CX2 SENSE ADJH TEST:.........", tag); + logError(0, "........FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else { + logError(0, "%s SS CX2 SENSE ADJH TEST:.........", tag); + logError(0, "........OK\n"); + } + + kfree(thresholds_max); + thresholds_max = NULL; + kfree(adjhor); + adjhor = NULL; + } else { + logError(0, "%s SS CX2 SENSE ADJ TEST:.................", tag); + logError(0, "SKIPPED\n\n"); + } + + //SS TOTAL CX SENSE + logError(0, "%s SS TOTAL CX SENSE TEST:\n", tag); + if (todo->SelfSenseCxTotal == 1 || todo->SelfSenseCxTotalAdj == 1) { + ret = computeTotal(ssCompData.cx2_sn, + ssCompData.s_cx1, + 1, + ssCompData.header.sense_node, + CX1_WEIGHT, + CX2_WEIGHT, + &total_cx); + if (ret < 0) { + logError(1, "%s production_test_data: ", tag); + logError(1, "computeTotal Cx Sense failed... "); + logError(1, "ERROR %02X\n", ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + logError(0, "%s SS TOTAL CX SENSE MIN MAX TEST:\n", tag); + //load the min thresholds + if (todo->SelfSenseCxTotal == 1) { + ret = parseProductionTestLimits(path_limits, + SS_TOTAL_CX_SENSE_MAP_MIN, + &thresholds_min, + &trows, + &tcolumns); + if (ret < 0 || (trows != 1 + || tcolumns != ssCompData.header.sense_node)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "SS_TOTAL_CX_SENSE_MAP_MIN "); + logError(1, "failed... ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + //load the max thresholds + ret = parseProductionTestLimits(path_limits, + SS_TOTAL_CX_SENSE_MAP_MAX, + &thresholds_max, + &trows, + &tcolumns); + if (ret < 0 || (trows != 1 + || tcolumns != ssCompData.header.sense_node)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "SS_TOTAL_CX_SENSE_MAP_MAX "); + logError(1, "failed... ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + //check the values with thresholds + ret = checkLimitsMapTotal(total_cx, + 1, + ssCompData.header.sense_node, + thresholds_min, + thresholds_max); + if (ret != OK) { + logError(1, "%s production_test_data: ", tag); + logError(1, "heckLimitsMap SS TOTAL CX SENSE "); + logError(1, "failed... ERROR COUNT = %d\n", + ret); + logError(0, "%s SS TOTAL CX SENSE MIN ", tag); + logError(0, "MAX TEST:................."); + logError(0, "FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else { + logError(0, "%s SS TOTAL CX SENSE MIN ", tag); + logError(0, "MAX TEST:................OK\n\n"); + } + + kfree(thresholds_min); + thresholds_min = NULL; + kfree(thresholds_max); + thresholds_max = NULL; + } else { + logError(0, "%s SS TOTAL CX SENSE MIN MAX TEST:", tag); + logError(0, ".................SKIPPED\n"); + } + + + //SS TOTAL IX SENSE ADJH TEST + logError(0, "%s SS TOTAL CX SENSE ADJ TEST:\n", tag); + if (todo->SelfSenseCxTotalAdj == 1) { + logError(0, "%s SS TOTAL CX SENSE ADJHORIZ TEST:\n", + tag); + ret = computeAdjHorizTotal(total_cx, + 1, + ssCompData.header.sense_node, + &total_adjhor); + if (ret < 0) { + logError(1, "%s production_test_data: ", tag); + logError(1, "computeAdjHoriz SS TOTAL CX "); + logError(1, "SENSE ADJH failed... "); + logError(1, "ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + logError(0, "%s SS TOTAL CX SENSE ADJ HORIZ ", tag); + logError(0, "computed!\n"); + + ret = parseProductionTestLimits(path_limits, + SS_TOTAL_CX_SENSE_ADJH_MAP_MAX, + &thresholds_max, + &trows, + &tcolumns); //load the max thresholds + if (ret < 0 || (trows != 1 || + tcolumns != ssCompData.header.sense_node - 1)) { + logError(1, "%s production_test_data: ", tag); + logError(1, "parseProductionTestLimits "); + logError(1, "SS_TOTAL_CX_SENSE_ADJH_MAP_MAX "); + logError(1, "failed... ERROR %02X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + //check the values with thresholds + ret = checkLimitsMapAdjTotal(total_adjhor, + 1, + ssCompData.header.sense_node - 1, + thresholds_max); + if (ret != OK) { + logError(1, "%s production_test_data: ", tag); + logError(1, "checkLimitsMapAdj SS TOTAL "); + logError(1, "CX SENSE ADJH failed... "); + logError(1, "ERROR COUNT = %d\n", ret); + logError(0, "%s SS TOTAL CX SENSE ADJH ", tag); + logError(0, "TEST:...FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else { + logError(0, "%s SS TOTAL CX SENSE ADJH TEST:", + tag); + logError(0, ".................OK\n\n"); + } + + kfree(thresholds_max); + thresholds_max = NULL; + kfree(total_adjhor); + total_adjhor = NULL; + } else { + logError(0, "%s SS TOTAL CX SENSE ADJ TEST:.", tag); + logError(0, "SKIPPED\n"); + } + kfree(total_cx); + total_cx = NULL; + } else + logError(0, "%s SS TOTAL CX SENSE TEST:.....SKIPPED\n", tag); + + +ERROR: + logError(0, "%s\n", tag); + if (count_fail == 0) { + kfree(ssCompData.ix2_fm); + ssCompData.ix2_fm = NULL; + kfree(ssCompData.ix2_sn); + ssCompData.ix2_sn = NULL; + kfree(ssCompData.cx2_fm); + ssCompData.cx2_fm = NULL; + kfree(ssCompData.cx2_sn); + ssCompData.cx2_sn = NULL; + logError(0, "%s SS IX CX testes finished!........OK\n\n", tag); + ret = OK; + } else { + //print all kind of data in just one row for readability reason + print_frame_u8("SS Init Data Ix2_fm = ", + array1dTo2d_u8(ssCompData.ix2_fm, + ssCompData.header.force_node, + ssCompData.header.force_node), + 1, + ssCompData.header.force_node); + print_frame_u8("SS Init Data Cx2_fm = ", + array1dTo2d_u8(ssCompData.cx2_fm, + ssCompData.header.force_node, + ssCompData.header.force_node), + 1, + ssCompData.header.force_node); + print_frame_u8("SS Init Data Ix2_sn = ", + array1dTo2d_u8(ssCompData.ix2_sn, + ssCompData.header.sense_node, + ssCompData.header.sense_node), + 1, + ssCompData.header.sense_node); + print_frame_u8("SS Init Data Cx2_sn = ", + array1dTo2d_u8(ssCompData.cx2_sn, + ssCompData.header.sense_node, + ssCompData.header.sense_node), + 1, + ssCompData.header.sense_node); + logError(0, "%s SS IX CX testes finished!.................", + tag); + logError(0, "FAILED fails_count = %d\n\n", count_fail); + kfree(thresholds); + kfree(thresholds_min); + kfree(thresholds_max); + kfree(adjhor); + kfree(adjvert); + kfree(ix1_w); + kfree(ix2_w); + kfree(total_ix); + kfree(total_cx); + kfree(total_adjhor); + kfree(total_adjvert); + kfree(ssCompData.ix2_fm); + kfree(ssCompData.ix2_sn); + kfree(ssCompData.cx2_fm); + kfree(ssCompData.cx2_sn); + ret = (ERROR_TEST_CHECK_FAIL | ERROR_PROD_TEST_DATA); + } + return ret; +ERROR_LIMITS: + kfree(thresholds); + kfree(thresholds_min); + kfree(thresholds_max); + kfree(adjhor); + kfree(adjvert); + kfree(ix1_w); + kfree(ix2_w); + kfree(total_ix); + kfree(total_cx); + kfree(total_adjhor); + kfree(total_adjvert); + kfree(ssCompData.ix2_fm); + kfree(ssCompData.ix2_sn); + kfree(ssCompData.cx2_fm); + kfree(ssCompData.cx2_sn); + return ret; +} + +int production_test_data(char *path_limits, int stop_on_fail, + struct TestToDo *todo) +{ + int res = OK, ret; + + if (todo == NULL) { + logError(0, "%s %s: ", tag, __func__); + logError(0, "No TestToDo specified!! "); + logError(0, "ERROR = %02X\n", + (ERROR_OP_NOT_ALLOW | ERROR_PROD_TEST_DATA)); + return (ERROR_OP_NOT_ALLOW | ERROR_PROD_TEST_DATA); + } + + logError(0, "%s DATA Production test is starting...\n", tag); + ret = production_test_ms_raw(path_limits, stop_on_fail, todo); + res |= ret; + if (ret < 0) { + logError(0, "%s %s: ", tag, __func__); + logError(0, "production_test_ms_raw failed... "); + logError(0, "ERROR = %02X\n", ret); + if (stop_on_fail == 1) + goto END; + } + + ret = production_test_ms_cx(path_limits, stop_on_fail, todo); + res |= ret; + if (ret < 0) { + logError(0, "%s %s: ", tag, __func__); + logError(0, "production_test_ms_cx failed... "); + logError(0, "ERROR = %02X\n", ret); + if (stop_on_fail == 1) + goto END; + } + + ret = production_test_ss_raw(path_limits, stop_on_fail, todo); + res |= ret; + if (ret < 0) { + logError(0, "%s %s: ", tag, __func__); + logError(0, "production_test_ss_raw failed... "); + logError(0, "ERROR = %02X\n", ret); + if (stop_on_fail == 1) + goto END; + } + + ret = production_test_ss_ix_cx(path_limits, stop_on_fail, todo); + res |= ret; + if (ret < 0) { + logError(0, "%s %s: ", tag, __func__); + logError(0, "production_test_ss_ix_cx failed... "); + logError(0, "ERROR = %02X\n", ret); + if (stop_on_fail == 1) + goto END; + } + +END: + if (res < OK) + logError(0, "%s DATA Production test failed!\n", tag); + else + logError(0, "%s DATA Production test finished!\n", tag); + return res; +} + + +int save_mp_flag(u32 signature) +{ + int res = -1; + int i; + u8 cmd[6] = {FTS_CMD_WRITE_MP_FLAG, 0x00, 0x00, 0x00, 0x00, 0x00}; + + u32ToU8(signature, &cmd[2]); + + logError(0, "%s Starting Saving Flag with signature = %08X ...\n", + tag, signature); + + for (i = 0; i < SAVE_FLAG_RETRY && res < OK; i++) { + logError(0, "%s Attempt number %d to save mp flag !\n", + tag, i+1); + logError(0, "%s Command write flag sent...\n", tag); + res = fts_writeFwCmd(cmd, 6); + if (res >= OK) + res = save_cx_tuning(); + } + + if (res < OK) { + logError(1, "%s %s: ERROR %08X ...\n", tag, __func__, res); + } else { + logError(0, "%s Saving Flag DONE!\n", tag); + res = OK; + } + return res; +} + +int parseProductionTestLimits(char *path, char *label, + int **data, int *row, int *column) +{ + int find = 0; + char *token = NULL; + int i = 0; + int j = 0; + int z = 0; + + char *line2 = NULL; + char line[800]; + int fd = -1; + char *buf = NULL; + int n, size, pointer = 0, ret = OK; + char *data_file = NULL; +#ifndef LIMITS_H_FILE + const struct firmware *fw = NULL; + struct device *dev = NULL; + + dev = getDev(); + if (dev != NULL) + fd = request_firmware(&fw, path, dev); +#else + fd = 0; +#endif + + if (fd != 0) { + logError(0, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_FILE_NOT_FOUND); + return ERROR_FILE_NOT_FOUND; + } + +#ifndef LIMITS_H_FILE + size = fw->size; + data_file = (char *)fw->data; + logError(0, "%s Start to reading %s...\n", tag, path); +#else + size = LIMITS_SIZE_NAME; + data_file = (char *)(LIMITS_ARRAY_NAME); +#endif + logError(0, "%s The size of the limits file is %d bytes\n", tag, size); + + while (find == 0) { + //start to look for the wanted label + if (readLine(&data_file[pointer], line, size-pointer, &n) < 0) { + find = -1; + break; + } + pointer += n; + //each header row start with + // *ex. *label, n_row, n_colum + if (line[0] != '*') + continue; + + line2 = kstrdup(line, GFP_KERNEL); + if (line2 == NULL) { + logError(1, "%s %s:kstrdup ERR %02X\n", + tag, __func__, ERROR_ALLOC); + ret = ERROR_ALLOC; + goto END; + } + buf = line2; + line2 += 1; + token = strsep(&line2, ","); + //if the row is the wanted one i + //retrieve rows and columns info + if (strcmp(token, label) == 0) { + find = 1; + token = strsep(&line2, ","); + if (token != NULL) { + ret = kstrtoint(token, 10, row); + if (ret != 0) + return -EINVAL; + logError(0, "%s Row = %d\n", tag, *row); + } else { + logError(1, "%s %s 1:ERROR %02X\n", + tag, __func__, ERROR_FILE_PARSE); + //release_firmware(fw); + //return ERROR_FILE_PARSE; + ret = ERROR_FILE_PARSE; + goto END; + } + token = strsep(&line2, ","); + if (token != NULL) { + ret = kstrtoint(token, 10, column); + if (ret != 0) + return -EINVAL; + logError(0, "%s Column = %d\n", tag, *column); + } else { + logError(1, "%s %s 2: ERROR %02X\n", + tag, __func__, ERROR_FILE_PARSE); + //release_firmware(fw); + //return ERROR_FILE_PARSE; + ret = ERROR_FILE_PARSE; + goto END; + } + kfree(buf); + buf = NULL; + //allocate memory for containing data + *data = (int *)kmalloc_array(((*row) * (*column)), + sizeof(int), GFP_KERNEL); + j = 0; + if (*data == NULL) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_ALLOC); + //release_firmware(fw); + //return ERROR_ALLOC; + ret = ERROR_ALLOC; + goto END; + } + //start to read the data + for (i = 0; i < *row; i++) { + //line = buf; + if (readLine(&data_file[pointer], line, + size-pointer, &n) < 0) { + logError(1, "%s %s : ERROR %02X\n", + tag, __func__, ERROR_FILE_READ); + //release_firmware(fw); + //return ERROR_FILE_READ + ret = ERROR_FILE_READ; + goto END; + } + pointer += n; + line2 = kstrdup(line, GFP_KERNEL); + if (line2 == NULL) { + logError(1, "%s %s: kstrdup ", + tag, __func__); + logError(1, "ERROR %02X\n", + ERROR_ALLOC); + ret = ERROR_ALLOC; + goto END; + } + buf = line2; + token = strsep(&line2, ","); + for (z = 0; + (z < *column) && (token != NULL); z++) { + ret = kstrtoint(token, + 10, + ((*data) + j)); + if (ret != 0) + return -EINVAL; + j++; + token = strsep(&line2, ","); + } + kfree(buf); + buf = NULL; + } + //check that all the data are read + if (j == ((*row) * (*column))) { + logError(0, "%s READ DONE!\n", tag); + //release_firmware(fw); + //return OK; + ret = OK; + goto END; + } + logError(1, "%s %s 3:ERROR %02X\n", + tag, __func__, ERROR_FILE_PARSE); + //release_firmware(fw); + //return ERROR_FILE_PARSE; + ret = ERROR_FILE_PARSE; + goto END; + } + kfree(buf); + buf = NULL; + + } + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_LABEL_NOT_FOUND); + ret = ERROR_LABEL_NOT_FOUND; +END: + kfree(buf); +#ifndef LIMITS_H_FILE + release_firmware(fw); +#endif + return ret; + +} + +int readLine(char *data, char *line, int size, int *n) +{ + int i = 0; + + if (size < 1) + return -EINVAL; + + while (data[i] != '\n' && i < size) { + line[i] = data[i]; + i++; + } + *n = i + 1; + line[i] = '\0'; + + return OK; +} + + diff --git a/qcom/opensource/touch-drivers/st/fts_lib/ftsTest.h b/qcom/opensource/touch-drivers/st/fts_lib/ftsTest.h new file mode 100644 index 0000000000..0e9334d452 --- /dev/null +++ b/qcom/opensource/touch-drivers/st/fts_lib/ftsTest.h @@ -0,0 +1,193 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * FTS Capacitive touch screen controller (FingerTipS) + * + * Copyright (C) 2016-2019, STMicroelectronics Limited. + * Authors: AMG(Analog Mems Group) + * + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +/** + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS API for MP test ** + * * + ************************************************************************** + ************************************************************************** + */ + +#ifndef __FTS_TEST_H +#define __FTS_TEST_H + +#include "ftsSoftware.h" + +#define LIMITS_FILE "stm_fts_production_limits.csv" + +#define WAIT_FOR_FRESH_FRAMES 100 //ms +#define WAIT_AFTER_SENSEOFF 50 //ms + +#define TIMEOUT_ITO_TEST_RESULT 200 //ms +#define TIMEOUT_INITIALIZATION_TEST_RESULT 5000 //ms + +//LABELS PRODUCTION TEST LIMITS FILE +#define MS_RAW_MIN_MAX "MS_RAW_DATA_MIN_MAX" +#define MS_RAW_GAP "MS_RAW_DATA_GAP" +#define MS_CX1_MIN_MAX "MS_TOUCH_ACTIVE_CX1_MIN_MAX" +#define MS_CX2_MAP_MIN "MS_TOUCH_ACTIVE_CX2_MIN" +#define MS_CX2_MAP_MAX "MS_TOUCH_ACTIVE_CX2_MAX" +#define MS_CX2_ADJH_MAP_MAX "MS_TOUCH_ACTIVE_CX2_ADJ_HORIZONTAL" +#define MS_CX2_ADJV_MAP_MAX "MS_TOUCH_ACTIVE_CX2_ADJ_VERTICAL" +#define MS_TOTAL_CX_MAP_MIN "MS_TOUCH_ACTIVE_TOTAL_CX_MIN" +#define MS_TOTAL_CX_MAP_MAX "MS_TOUCH_ACTIVE_TOTAL_CX_MAX" +#define MS_TOTAL_CX_ADJH_MAP_MAX "MS_TOUCH_ACTIVE_TOTAL_CX_ADJ_HORIZONTAL" +#define MS_TOTAL_CX_ADJV_MAP_MAX "MS_TOUCH_ACTIVE_TOTAL_CX_ADJ_VERTICAL" +#define SS_RAW_FORCE_MIN_MAX "SS_RAW_DATA_FORCE_MIN_MAX" +#define SS_RAW_SENSE_MIN_MAX "SS_RAW_DATA_SENSE_MIN_MAX" +#define SS_RAW_FORCE_GAP "SS_RAW_DATA_FORCE_GAP" +#define SS_RAW_SENSE_GAP "SS_RAW_DATA_SENSE_GAP" +#define SS_IX1_FORCE_MIN_MAX "SS_TOUCH_ACTIVE_IX1_FORCE_MIN_MAX" +#define SS_IX1_SENSE_MIN_MAX "SS_TOUCH_ACTIVE_IX1_SENSE_MIN_MAX" +#define SS_CX1_FORCE_MIN_MAX "SS_TOUCH_ACTIVE_CX1_FORCE_MIN_MAX" +#define SS_CX1_SENSE_MIN_MAX "SS_TOUCH_ACTIVE_CX1_SENSE_MIN_MAX" +#define SS_IX2_FORCE_MAP_MIN "SS_TOUCH_ACTIVE_IX2_FORCE_MIN" +#define SS_IX2_FORCE_MAP_MAX "SS_TOUCH_ACTIVE_IX2_FORCE_MAX" +#define SS_IX2_SENSE_MAP_MIN "SS_TOUCH_ACTIVE_IX2_SENSE_MIN" +#define SS_IX2_SENSE_MAP_MAX "SS_TOUCH_ACTIVE_IX2_SENSE_MAX" +#define SS_IX2_FORCE_ADJV_MAP_MAX "SS_TOUCH_ACTIVE_IX2_ADJ_VERTICAL" +#define SS_IX2_SENSE_ADJH_MAP_MAX "SS_TOUCH_ACTIVE_IX2_ADJ_HORIZONTAL" +#define SS_CX2_FORCE_MAP_MIN "SS_TOUCH_ACTIVE_CX2_FORCE_MIN" +#define SS_CX2_FORCE_MAP_MAX "SS_TOUCH_ACTIVE_CX2_FORCE_MAX" +#define SS_CX2_SENSE_MAP_MIN "SS_TOUCH_ACTIVE_CX2_SENSE_MIN" +#define SS_CX2_SENSE_MAP_MAX "SS_TOUCH_ACTIVE_CX2_SENSE_MAX" +#define SS_CX2_FORCE_ADJV_MAP_MAX "SS_TOUCH_ACTIVE_CX2_ADJ_VERTICAL" +#define SS_CX2_SENSE_ADJH_MAP_MAX "SS_TOUCH_ACTIVE_CX2_ADJ_HORIZONTAL" + +// TOTAL SS +#define SS_TOTAL_IX_FORCE_MAP_MIN "SS_TOUCH_ACTIVE_TOTAL_IX_FORCE_MIN" +#define SS_TOTAL_IX_FORCE_MAP_MAX "SS_TOUCH_ACTIVE_TOTAL_IX_FORCE_MAX" +#define SS_TOTAL_IX_SENSE_MAP_MIN "SS_TOUCH_ACTIVE_TOTAL_IX_SENSE_MIN" +#define SS_TOTAL_IX_SENSE_MAP_MAX "SS_TOUCH_ACTIVE_TOTAL_IX_SENSE_MAX" +#define SS_TOTAL_IX_FORCE_ADJV_MAP_MAX "SS_TOUCH_ACTIVE_TOTAL_IX_ADJ_VERTICAL" +#define SS_TOTAL_IX_SENSE_ADJH_MAP_MAX "SS_TOUCH_ACTIVE_TOTAL_IX_ADJ_HORIZONTAL" +#define SS_TOTAL_CX_FORCE_MAP_MIN "SS_TOUCH_ACTIVE_TOTAL_CX_FORCE_MIN" +#define SS_TOTAL_CX_FORCE_MAP_MAX "SS_TOUCH_ACTIVE_TOTAL_CX_FORCE_MAX" +#define SS_TOTAL_CX_SENSE_MAP_MIN "SS_TOUCH_ACTIVE_TOTAL_CX_SENSE_MIN" +#define SS_TOTAL_CX_SENSE_MAP_MAX "SS_TOUCH_ACTIVE_TOTAL_CX_SENSE_MAX" +#define SS_TOTAL_CX_FORCE_ADJV_MAP_MAX "SS_TOUCH_ACTIVE_TOTAL_CX_ADJ_VERTICAL" +#define SS_TOTAL_CX_SENSE_ADJH_MAP_MAX "SS_TOUCH_ACTIVE_TOTAL_CX_ADJ_HORIZONTAL" + +//KEYS +#define MS_KEY_RAW_MIN_MAX "MS_KEY_RAW_DATA_MIN_MAX" +#define MS_KEY_CX1_MIN_MAX "MS_KEY_CX1_MIN_MAX" +#define MS_KEY_CX2_MAP_MIN "MS_KEY_CX2_MIN" +#define MS_KEY_CX2_MAP_MAX "MS_KEY_CX2_MAX" +#define MS_KEY_TOTAL_CX_MAP_MIN "MS_KEY_TOTAL_CX_MIN" +#define MS_KEY_TOTAL_CX_MAP_MAX "MS_KEY_TOTAL_CX_MAX" + +//CONSTANT TOTAL IX +#define SS_IX1_FORCE_W "IX1_FORCE_W" +#define SS_IX2_FORCE_W "IX2_FORCE_W" +#define SS_IX1_SENSE_W "IX1_SENSE_W" +#define SS_IX2_SENSE_W "IX2_SENSE_W" + + +#define SAVE_FLAG_RETRY 3 + + +struct TestToDo { + int MutualRaw; + int MutualRawGap; + int MutualCx1; + int MutualCx2; + int MutualCx2Adj; + int MutualCxTotal; + int MutualCxTotalAdj; + + int MutualKeyRaw; + int MutualKeyCx1; + int MutualKeyCx2; + int MutualKeyCxTotal; + + int SelfForceRaw; + int SelfForceRawGap; + int SelfForceIx1; + int SelfForceIx2; + int SelfForceIx2Adj; + int SelfForceIxTotal; + int SelfForceIxTotalAdj; + int SelfForceCx1; + int SelfForceCx2; + int SelfForceCx2Adj; + int SelfForceCxTotal; + int SelfForceCxTotalAdj; + + int SelfSenseRaw; + int SelfSenseRawGap; + int SelfSenseIx1; + int SelfSenseIx2; + int SelfSenseIx2Adj; + int SelfSenseIxTotal; + int SelfSenseIxTotalAdj; + int SelfSenseCx1; + int SelfSenseCx2; + int SelfSenseCx2Adj; + int SelfSenseCxTotal; + int SelfSenseCxTotalAdj; + +}; + +int computeAdjHoriz(u8 *data, int row, int column, u8 **result); +int computeAdjHorizTotal(u16 *data, int row, int column, u16 **result); +int computeAdjVert(u8 *data, int row, int column, u8 **result); +int computeAdjVertTotal(u16 *data, int row, int column, u16 **result); +int computeTotal(u8 *data, u8 main, int row, int column, int m, + int n, u16 **result); +int checkLimitsMinMax(short *data, int row, int column, int min, int max); +int checkLimitsMap(u8 *data, int row, int column, int *min, int *max); +int checkLimitsMapTotal(u16 *data, int row, int column, int *min, int *max); +int checkLimitsMapAdj(u8 *data, int row, int column, int *max); +int checkLimitsMapAdjTotal(u16 *data, int row, int column, int *max); +int production_test_ito(void); +int production_test_initialization(void); +int ms_compensation_tuning(void); +int ss_compensation_tuning(void); +int lp_timer_calibration(void); +int save_cx_tuning(void); +int production_test_split_initialization(int saveToFlash); +int production_test_main(char *pathThresholds, int stop_on_fail, int saveInit, + struct TestToDo *todo, u32 signature); +int production_test_ms_raw(char *path_limits, int stop_on_fail, + struct TestToDo *todo); +int production_test_ms_cx(char *path_limits, int stop_on_fail, + struct TestToDo *todo); +int production_test_ss_raw(char *path_limits, int stop_on_fail, + struct TestToDo *todo); +int production_test_ss_ix_cx(char *path_limits, int stop_on_fail, + struct TestToDo *todo); +int production_test_data(char *path_limits, int stop_on_fail, + struct TestToDo *todo); +int production_test_ms_key_cx(char *path_limits, int stop_on_fail, + struct TestToDo *todo); +int production_test_ms_key_raw(char *path_limits); +int save_mp_flag(u32 signature); +int parseProductionTestLimits(char *path, char *label, int **data, + int *row, int *column); +int readLine(char *data, char *line, int size, int *n); +#endif diff --git a/qcom/opensource/touch-drivers/st/fts_lib/ftsTime.c b/qcom/opensource/touch-drivers/st/fts_lib/ftsTime.c new file mode 100644 index 0000000000..88d03dc27a --- /dev/null +++ b/qcom/opensource/touch-drivers/st/fts_lib/ftsTime.c @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * FTS Capacitive touch screen controller (FingerTipS) + * + * Copyright (C) 2016-2019, STMicroelectronics Limited. + * Authors: AMG(Analog Mems Group) + * + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +/** + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS Utility for mesuring/handling the time * + * * + ************************************************************************** + *************************************************************************** + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include + +#include "ftsCrossCompile.h" +#include "ftsTime.h" + + +void startStopWatch(struct StopWatch *w) +{ + w->start = ktime_to_timespec64(ktime_get()); +} + +void stopStopWatch(struct StopWatch *w) +{ + w->end = ktime_to_timespec64(ktime_get()); +} + +int elapsedMillisecond(struct StopWatch *w) +{ + int result; + + result = ((w->end.tv_sec - w->start.tv_sec) * 1000) + + (w->end.tv_nsec - w->start.tv_nsec) / 1000000; + return result; +} + +int elapsedNanosecond(struct StopWatch *w) +{ + int result; + + result = ((w->end.tv_sec - w->start.tv_sec) * 1000000000) + + (w->end.tv_nsec - w->start.tv_nsec); + return result; +} + +char *timestamp() +{ + char *result = NULL; + + result = (char *)kmalloc_array(1, sizeof(char), GFP_KERNEL); + if (result == NULL) + return NULL; + result[0] = ' '; + return result; +} + +void stdelay(unsigned long ms) +{ + msleep(ms); +} diff --git a/qcom/opensource/touch-drivers/st/fts_lib/ftsTime.h b/qcom/opensource/touch-drivers/st/fts_lib/ftsTime.h new file mode 100644 index 0000000000..64968e77af --- /dev/null +++ b/qcom/opensource/touch-drivers/st/fts_lib/ftsTime.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * FTS Capacitive touch screen controller (FingerTipS) + * + * Copyright (C) 2016-2019, STMicroelectronics Limited. + * Authors: AMG(Analog Mems Group) + * + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +/** + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS Utility for mesuring/handling the time * + * * + ************************************************************************** + ************************************************************************** + * + */ + +#ifndef __FTS_TIME_H +#define __FTS_TIME_H + +#include + +#include "ftsCrossCompile.h" + +struct StopWatch { + struct timespec64 start, end; +}; + +void startStopWatch(struct StopWatch *w); +void stopStopWatch(struct StopWatch *w); +int elapsedMillisecond(struct StopWatch *w); +int elapsedNanosecond(struct StopWatch *w); +char *timestamp(void); +void stdelay(unsigned long ms); +#endif diff --git a/qcom/opensource/touch-drivers/st/fts_lib/ftsTool.c b/qcom/opensource/touch-drivers/st/fts_lib/ftsTool.c new file mode 100644 index 0000000000..6dac5b1ddf --- /dev/null +++ b/qcom/opensource/touch-drivers/st/fts_lib/ftsTool.c @@ -0,0 +1,1344 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * FTS Capacitive touch screen controller (FingerTipS) + * + * Copyright (C) 2016-2018, STMicroelectronics Limited. + * Authors: AMG(Analog Mems Group) + * + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +/** + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS Utility Functions * + * * + ************************************************************************** + ************************************************************************** + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ftsCompensation.h" +#include "ftsCrossCompile.h" +#include "ftsError.h" +#include "ftsHardware.h" +#include "ftsIO.h" +#include "ftsSoftware.h" +#include "ftsTime.h" +#include "ftsFlash.h" +#include "ftsTool.h" +#include "../fts.h" + +static char tag[8] = "[ FTS ]\0"; +static int reset_gpio = GPIO_NOT_DEFINED; +static int system_resetted_up; +static int system_resetted_down; + +int readB2(u16 address, u8 *outBuf, int len) +{ + int remaining = len; + int toRead = 0; + int retry = 0; + int ret; + int event_to_search[3]; + char *temp = NULL; + u8 *init_outBuf = outBuf; + u16 init_addr = address; + u8 readEvent[FIFO_EVENT_SIZE] = {0}; + u8 cmd[4] = { FTS_CMD_REQU_FW_CONF, 0x00, 0x00, (u8)len }; + + u16ToU8_be(address, &cmd[1]); + temp = printHex("Command B2 = ", cmd, 4); + if (temp != NULL) + logError(0, "%s %s", tag, temp); + kfree(temp); + do { + remaining = len; + ret = fts_writeFwCmd(cmd, 4); + if (ret < 0) { + logError(1, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_I2C_W); + return ret; + } + //ask to the FW the data + logError(0, "%s Command to FW sent!\n", tag); + event_to_search[0] = (int)EVENTID_FW_CONFIGURATION; + while (remaining > OK) { + event_to_search[1] = (int)((address & 0xFF00) >> 8); + event_to_search[2] = (int)(address & 0x00FF); + if (remaining > B2_DATA_BYTES) { + toRead = B2_DATA_BYTES; + remaining -= B2_DATA_BYTES; + } else { + toRead = remaining; + remaining = 0; + } + + ret = pollForEvent(event_to_search, 3, + readEvent, GENERAL_TIMEOUT); + if (ret >= OK) { + //start the polling for reading the reply + memcpy(outBuf, &readEvent[3], toRead); + retry = 0; + outBuf += toRead; + } else { + retry += 1; + break; + } + address += B2_DATA_BYTES; + } + logError(0, "%s %s:B2 failed...attempt = %d\n", + tag, __func__, retry); + outBuf = init_outBuf; + address = init_addr; + } while (retry < B2_RETRY && retry != 0); + + if (retry == B2_RETRY) { + logError(1, "%s %s:ERROR %02X\n", tag, __func__, ERROR_TIMEOUT); + return ERROR_TIMEOUT; + } + logError(0, "%s B2 read %d bytes\n", tag, len); + + return OK; +} + +int readB2U16(u16 address, u8 *outBuf, int byteToRead) +{ + int remaining = byteToRead; + int toRead = 0; + int ret; + + u8 *buff = (u8 *)kmalloc_array((B2_CHUNK + 1), sizeof(u8), GFP_KERNEL); + + if (buff == NULL) { + logError(1, "%s %s: ERROR %02X\n", tag, __func__, ERROR_ALLOC); + return ERROR_ALLOC; + } + + while (remaining > 0) { + if (remaining >= B2_CHUNK) { + toRead = B2_CHUNK; + remaining -= B2_CHUNK; + } else { + toRead = remaining; + remaining = 0; + } + + ret = readB2(address, buff, toRead); + if (ret < 0) { + kfree(buff); + return ret; + } + memcpy(outBuf, buff, toRead); + address += toRead; + outBuf += toRead; + } + kfree(buff); + return OK; +} + + +int releaseInformation(void) +{ + int ret; + u8 cmd[1] = { FTS_CMD_RELEASE_INFO }; + int event_to_search[1]; + u8 readEvent[FIFO_EVENT_SIZE]; + + event_to_search[0] = (int)EVENTID_RELEASE_INFO; + + logError(0, "%s %s: started... Chip INFO:\n", tag, __func__); + + ret = fts_writeFwCmd(cmd, 1); + if (ret < OK) { + logError(1, "%s %s: ERROR %02X\n", tag, __func__, ret); + return ret; + } + + ret = pollForEvent(event_to_search, 1, &readEvent[0], + RELEASE_INFO_TIMEOUT); + //start the polling for reading the reply + if (ret < OK) { + logError(1, "%s %s: ERROR %02X\n", tag, __func__, ret); + return ret; + } + + logError(0, "%s %s: Finished! %d\n", tag, __func__, ret); + return OK; +} + +int lockDownInfo(u8 *data, int len) +{ + int ret; + int i = 0, num_event; + u8 cmd[1] = { FTS_CMD_LOCKDOWN_CMD }; + int event_to_search[3] = {EVENTID_LOCKDOWN_INFO, + EVENT_TYPE_LOCKDOWN, 0x00}; + u8 readEvent[FIFO_EVENT_SIZE]; + + logError(0, "%s %s:started...\n", tag, __func__); + if (len <= 0) + return ERROR_OP_NOT_ALLOW; + + ret = fts_writeFwCmd(cmd, 1); + if (ret < OK) { + logError(1, "%s %s:ERROR %02X\n", tag, __func__, ret); + return ret; + } + + num_event = (len + 3) / 4; + logError(0, "%s %s:num_event = %d\n", tag, __func__, num_event); + + for (i = 0; i < num_event; i++) { + ret = pollForEvent(event_to_search, 3, + &readEvent[0], GENERAL_TIMEOUT); + //start the polling for reading the reply + if (ret < OK) { + logError(1, "%s %s:ERROR %02X\n", tag, __func__, ret); + return ret; + } + data[i * 4] = readEvent[3]; + data[i * 4 + 1] = readEvent[4]; + data[i * 4 + 2] = readEvent[5]; + data[i * 4 + 3] = readEvent[6]; + event_to_search[2] += 4; + //logError(0, "%02X %02X %02X %02X ", readEvent[3], + //readEvent[4], readEvent[5], readEvent[6]); + } + + logError(0, "%s %s:Finished! %d\n", tag, __func__, ret); + return OK; +} + + +int calculateCRC8(u8 *u8_srcBuff, int size, u8 *crc) +{ + u8 u8_remainder; + u8 bit; + int i = 0; + + u8_remainder = 0x00; + logError(0, "%s %s: Start CRC computing...\n", tag, __func__); + + if (size == 0 || u8_srcBuff == NULL) { + logError(1, "Arguments passed not valid!"); + logError(1, "%s %s:Data pointer = NULL ", tag, __func__); + logError(1, "or size = 0 (%d) ERROR %08X\n", + size, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + // Perform modulo-2 division, a byte at a time. + //Bring the next byte into the remainder. + for (i = 0; i < size; i++) { + //Perform modulo-2 division, a bit at a time. + u8_remainder ^= u8_srcBuff[i]; + //Try to divide the current data bit. + for (bit = 8; bit > 0; --bit) { + if (u8_remainder & (0x1 << 7)) + u8_remainder = (u8_remainder << 1) ^ 0x9B; + else + u8_remainder = (u8_remainder << 1); + } + } //The final remainder is the CRC result. + *crc = u8_remainder; + logError(0, "%s %s: CRC value = %02X\n", tag, __func__, *crc); + return OK; +} + +int writeLockDownInfo(u8 *data, int size) +{ + int ret, i, toWrite, retry = 0, offset = size; + u8 cmd[2 + LOCKDOWN_CODE_WRITE_CHUNK] = {FTS_CMD_LOCKDOWN_FILL, 0x00}; + u8 crc = 0; + int event_to_search[2] = {EVENTID_STATUS_UPDATE, + EVENT_TYPE_LOCKDOWN_WRITE}; + u8 readEvent[FIFO_EVENT_SIZE]; + char *temp = NULL; + + logError(0, "%s %s: Writing Lockdown code into the IC...\n", + tag, __func__); + + ret = fts_disableInterrupt(); + if (ret < OK) { + logError(1, "%s %s: ERROR %08X\n", tag, __func__, ret); + ret = (ret | ERROR_LOCKDOWN_CODE); + goto ERROR; + } + if (size > LOCKDOWN_CODE_MAX_SIZE) { + logError(1, "%s %s: Lockdown data to write too big! ", + tag, __func__); + logError(1, "%d>%d ERROR %08X\n", + size, LOCKDOWN_CODE_MAX_SIZE, ret); + ret = (ERROR_OP_NOT_ALLOW | ERROR_LOCKDOWN_CODE); + goto ERROR; + } + + temp = printHex("Lockdown Code = ", data, size); + if (temp != NULL) { + logError(0, "%s %s: %s", tag, __func__, temp); + kfree(temp); + } + + for (retry = 0; retry < LOCKDOWN_CODE_RETRY; retry++) { + logError(0, "%s %s: Filling FW buffer...\n", tag, __func__); + i = 0; + offset = size; + cmd[0] = FTS_CMD_LOCKDOWN_FILL; + while (offset > 0) { + if (offset > LOCKDOWN_CODE_WRITE_CHUNK) + toWrite = LOCKDOWN_CODE_WRITE_CHUNK; + else + toWrite = offset; + memcpy(&cmd[2], &data[i], toWrite); + cmd[1] = i; + + temp = printHex("Commmand = ", cmd, 2 + toWrite); + if (temp != NULL) { + logError(0, "%s %s: %s", tag, __func__, temp); + kfree(temp); + } + ret = fts_writeFwCmd(cmd, 2 + toWrite); + if (ret < OK) { + logError(1, "Unable to write Lockdown data "); + logError(1, "%s %s:Lockdown data at %d ", + tag, __func__, i); + logError(1, "iteration.%08X\n", ret); + ret = (ret | ERROR_LOCKDOWN_CODE); + continue; + } + i += toWrite;//update the offset + offset -= toWrite; + } + logError(0, "%s %s: Compute 8bit CRC...\n", tag, __func__); + ret = calculateCRC8(data, size, &crc); + if (ret < OK) { + logError(1, "%s %s:Unable to compute CRC..ERROR %08X\n", + tag, __func__, ret); + ret = (ret | ERROR_LOCKDOWN_CODE); + continue; + } + cmd[0] = FTS_CMD_LOCKDOWN_WRITE; + cmd[1] = 0x00; + cmd[2] = (u8)size; + cmd[3] = crc; + logError(0, "%s %s: Write Lockdown data...\n", + tag, __func__); + temp = printHex("Commmand = ", cmd, 4); + if (temp != NULL) { + logError(0, "%s %s: %s", tag, __func__, temp); + kfree(temp); + } + ret = fts_writeFwCmd(cmd, 4); + if (ret < OK) { + logError(1, "%s%s:Unable to send Lockdown data ", + tag, __func__); + logError(1, "write command%08X\n", ret); + ret = (ret | ERROR_LOCKDOWN_CODE); + continue; + } + + ret = pollForEvent(event_to_search, + 2, + &readEvent[0], + GENERAL_TIMEOUT); + //start the polling for reading the reply + + if (ret < OK) { + logError(1, "%s%s:Cann't find lockdown code ", + tag, __func__); + logError(1, "%write reply %08X\n", ret); + continue; + } + + if (readEvent[2] != 0x00) { + logError(1, "%s %s:Event check FAIL!%02X != 0x00 ", + tag, __func__, readEvent[2]); + logError(1, "%ERR%08X\n", ERROR_LOCKDOWN_CODE); + ret = ERROR_LOCKDOWN_CODE; + continue; + } else { + logError(0, "%s %s:Lockdown Code write DONE!\n", + tag, __func__); + ret = OK; + break; + } + } + +ERROR: + //ret = fts_enableInterrupt(); + //ensure that the interrupt are always renabled when exit from funct + if (fts_enableInterrupt() < OK) { + logError(1, "%s %s: Error while re-enabling the interrupt!\n", + tag, __func__); + } + return ret; +} + +int rewriteLockDownInfo(u8 *data, int size) +{ + int ret, i, toWrite, retry = 0, offset = size; + u8 cmd[2 + LOCKDOWN_CODE_WRITE_CHUNK] = {FTS_CMD_LOCKDOWN_FILL, 0x00}; + u8 crc = 0; + int event_to_search[2] = {EVENTID_STATUS_UPDATE, + EVENT_TYPE_LOCKDOWN_WRITE}; + u8 readEvent[FIFO_EVENT_SIZE]; + char *temp = NULL; + + logError(0, "%s %s: ReWriting Lockdown code into the IC start ...\n", + tag, __func__); + + ret = fts_disableInterrupt(); + if (ret < OK) { + logError(1, "%s %s: ERROR %08X\n", tag, __func__, ret); + ret = (ret | ERROR_LOCKDOWN_CODE); + goto ERROR; + } + if (size > LOCKDOWN_CODE_MAX_SIZE) { + logError(1, "%s %s: Lockdown data to write too big! ", + tag, __func__); + logError(1, "%d>%d ERROR %08X\n", + size, LOCKDOWN_CODE_MAX_SIZE, ret); + ret = (ERROR_OP_NOT_ALLOW | ERROR_LOCKDOWN_CODE); + goto ERROR; + } + + temp = printHex("Lockdown Code = ", data, size); + if (temp != NULL) { + logError(0, "%s %s: %s", tag, __func__, temp); + kfree(temp); + } + + for (retry = 0; retry < LOCKDOWN_CODE_RETRY; retry++) { + logError(0, "%s %s: Filling FW buffer ...\n", tag, __func__); + i = 0; + offset = size; + cmd[0] = FTS_CMD_LOCKDOWN_FILL; + while (offset > 0) { + if (offset > LOCKDOWN_CODE_WRITE_CHUNK) + toWrite = LOCKDOWN_CODE_WRITE_CHUNK; + else + toWrite = offset; + memcpy(&cmd[2], &data[i], toWrite); + cmd[1] = i; + temp = printHex("Commmand = ", cmd, 2 + toWrite); + if (temp != NULL) { + logError(0, "%s %s: %s", tag, __func__, temp); + kfree(temp); + } + ret = fts_writeFwCmd(cmd, 2 + toWrite); + if (ret < OK) { + logError(1, "Unable to rewrite Lockdown data"); + logError(1, "%s %s: at %d iteration ", + tag, __func__, i); + logError(1, "ERROR %08X\n", ret); + ret = (ret | ERROR_LOCKDOWN_CODE); + continue; + } + i += toWrite;//update the offset + offset -= toWrite; + } + logError(0, "%s %s: Compute 8bit CRC...\n", tag, __func__); + ret = calculateCRC8(data, size, &crc); + if (ret < OK) { + logError(1, "%s %s:Unable to compute CRC.. ", + tag, __func__); + logError(1, "ERROR %08X\n", ret); + ret = (ret | ERROR_LOCKDOWN_CODE); + continue; + } + cmd[0] = FTS_CMD_LOCKDOWN_WRITE; + cmd[1] = 0x01; + cmd[2] = (u8)size; + cmd[3] = crc; + + temp = printHex("Commmand = ", cmd, 4); + if (temp != NULL) { + logError(0, "%s %s: %s", tag, __func__, temp); + kfree(temp); + } + logError(0, "%s %s: ReWrite Lockdown data...\n", tag, __func__); + ret = fts_writeFwCmd(cmd, 4); + if (ret < OK) { + logError(1, "Unable to send Lockdown data"); + logError(1, "%s %s:rewrite command... ERROR %08X\n", + tag, __func__, ret); + ret = (ret | ERROR_LOCKDOWN_CODE); + continue; + } + + //start the polling for reading the reply + ret = pollForEvent(event_to_search, 2, + &readEvent[0], GENERAL_TIMEOUT); + if (ret >= OK) { + if (readEvent[2] < 0x00) { + logError(1, "%s %s:Event check FAIL! ", + tag, __func__); + logError(1, "%02X != 0x00 %08X\n", + readEvent[2], ERROR_LOCKDOWN_CODE); + ret = ERROR_LOCKDOWN_CODE; + continue; + } else { + logError(0, "%s %s: Lockdown Code ", + tag, __func__); + logError(0, "rewrite DONE!\n"); + ret = OK; + break; + } + } else { + logError(1, "Can not find lockdown code write "); + logError(1, "reply event!%s %s: ERROR %08X\n", + tag, __func__, ret); + } + } +ERROR: + //ret = fts_enableInterrupt(); + //ensure that the interrupt are always renabled when exit from funct + if (fts_enableInterrupt() < OK) { + logError(1, "%s %s: Error while re-enabling the interrupt!\n", + tag, __func__); + } + return ret; +} + +int readLockDownInfo(u8 *lockData, int *size) +{ + int ret, retry = 0, toRead = 0, byteToRead; + u8 cmd = FTS_CMD_LOCKDOWN_READ; + int event_to_search[3] = {EVENTID_LOCKDOWN_INFO_READ, -1, 0x00}; + u8 readEvent[FIFO_EVENT_SIZE]; + char *temp = NULL; + + lockData = NULL; + logError(0, "%s %s: Reading Lockdown code from the IC...\n", + tag, __func__); + + ret = fts_disableInterrupt(); + if (ret < OK) { + logError(1, "%s %s: ERROR %08X\n", tag, __func__, ret); + ret = (ret | ERROR_LOCKDOWN_CODE); + goto ERROR; + } + for (retry = 0; retry < LOCKDOWN_CODE_RETRY; retry++) { + event_to_search[2] = 0x00; + logError(0, "%s %s: Read Lockdown data.(%d attempt)\n", + tag, __func__, retry + 1); + ret = fts_writeFwCmd(&cmd, 1); + + if (ret < OK) { + logError(1, "%s%s:Unable to send Lockdown data ", + tag, __func__); + logError(1, "write CMD %08X\n", ret); + ret = (ret | ERROR_LOCKDOWN_CODE); + continue; + } + + //start the polling for reading the reply + ret = pollForEvent(event_to_search, 3, + &readEvent[0], GENERAL_TIMEOUT); + + if (ret < OK) { + logError(1, "Cann't find first lockdown code read"); + logError(1, "%s %s:reply event! ERROR %08X\n", + tag, __func__, ret); + continue; + } + + byteToRead = readEvent[1]; + *size = byteToRead; + logError(0, "%s %s:Lockdown Code size = %d\n", + tag, __func__, *size); + lockData = (u8 *)kmalloc_array((byteToRead), + sizeof(u8), GFP_KERNEL); + if (lockData == NULL) { + logError(1, "%s %s:Unable to allocate lockData %08X\n", + tag, __func__, ERROR_ALLOC); + ret = (ERROR_ALLOC | ERROR_LOCKDOWN_CODE); + continue; + } + while (byteToRead > 0) { + if ((readEvent[1] - readEvent[2]) + > LOCKDOWN_CODE_READ_CHUNK) { + toRead = LOCKDOWN_CODE_READ_CHUNK; + } else { + toRead = readEvent[1] - readEvent[2]; + } + byteToRead -= toRead; + memcpy(&lockData[readEvent[2]], + &readEvent[3], toRead); + event_to_search[2] += toRead; + if (byteToRead <= 0) + continue; + + ret = pollForEvent(event_to_search, + 3, + &readEvent[0], + GENERAL_TIMEOUT); + + //start polling for reading reply + if (ret < OK) { + logError(1, "Can not find lockdow"); + logError(1, "code read reply event "); + logError(1, "%s%s:offset%02X%08X\n", + tag, __func__, event_to_search[2], ret); + ret = (ERROR_ALLOC | ERROR_LOCKDOWN_CODE); + break; + } + } + if (byteToRead != 0) { + logError(1, "%s %s:Read Lockdown code FAIL! ", + tag, __func__); + logError(1, "ERROR %08X\n", ret); + continue; + } else { + logError(0, "%s %s: Lockdown Code read DONE!\n", + tag, __func__); + ret = OK; + temp = printHex("Lockdown Code = ", lockData, *size); + if (temp != NULL) { + logError(0, "%s %s: %s", tag, __func__, temp); + kfree(temp); + } + break; + } + } +ERROR: + //ret = fts_enableInterrupt(); + //ensure that the interrupt are always + //renabled when exit from funct + if (fts_enableInterrupt() < OK) { + logError(1, "%s %s:Error while re-enabling the interrupt!\n", + tag, __func__); + } + return ret; +} + +char *printHex(char *label, u8 *buff, int count) +{ + int i, offset; + char *result = NULL; + size_t len = 0; + + offset = strlen(label); + len = (offset + 3 * count) + 2; + result = (char *)kmalloc_array(len, sizeof(char), GFP_KERNEL); + if (result != NULL) { + strlcpy(result, label, len); + for (i = 0; i < count; i++) + snprintf(&result[offset + i * 3], 4, "%02X ", buff[i]); + strlcat(result, "\n", len); + } + return result; +} + +int pollForEvent(int *event_to_search, int event_bytes, + u8 *readData, int time_to_wait) +{ + int i, find, retry, count_err; + int time_to_count; + int err_handling = OK; + struct StopWatch clock; + + u8 cmd[1] = { FIFO_CMD_READONE }; + char *temp = NULL; + + find = 0; + retry = 0; + count_err = 0; + time_to_count = time_to_wait / TIMEOUT_RESOLUTION; + + startStopWatch(&clock); + while (find != 1 && retry < time_to_count + && fts_readCmd(cmd, 1, readData, FIFO_EVENT_SIZE) >= 0) { + + if (readData[0] == EVENTID_ERROR_EVENT) { + temp = printHex("ERROR EVENT = ", + readData, FIFO_EVENT_SIZE); + if (temp != NULL) + logError(0, "%s %s", tag, temp); + kfree(temp); + count_err++; + err_handling = errorHandler(readData, FIFO_EVENT_SIZE); + if ((err_handling & 0xF0FF0000) + == ERROR_HANDLER_STOP_PROC) { + logError(1, "%s %s: forced to be stopped! ", + tag, __func__); + logError(1, "ERROR %08X\n", err_handling); + return err_handling; + } + } else { + if (readData[0] != EVENTID_NO_EVENT) { + temp = printHex("READ EVENT = ", + readData, FIFO_EVENT_SIZE); + if (temp != NULL) + logError(0, "%s %s", tag, temp); + kfree(temp); + } + if (readData[0] == EVENTID_CONTROL_READY && + event_to_search[0] != EVENTID_CONTROL_READY) { + logError(0, "Unmanned Controller Ready Event!"); + logError(0, "%s %s:Setting reset flags...\n", + tag, __func__); + setSystemResettedUp(1); + setSystemResettedDown(1); + } + } + find = 1; + + for (i = 0; i < event_bytes; i++) { + if (event_to_search[i] != -1 + && (int)readData[i] != event_to_search[i]) { + find = 0; + break; + } + } + + retry++; + msleep(TIMEOUT_RESOLUTION); + } + stopStopWatch(&clock); + if ((retry >= time_to_count) && find != 1) { + logError(0, "%s %s: ERROR %02X\n", + tag, __func__, ERROR_TIMEOUT); + return ERROR_TIMEOUT; + } + if (find == 1) { + temp = printHex("FOUND EVENT = ", readData, FIFO_EVENT_SIZE); + if (temp != NULL) + logError(0, "%s %s", tag, temp); + kfree(temp); + logError(0, "%s Event found in %d ms (%d iterations)!\n", + tag, elapsedMillisecond(&clock), retry); + logError(0, "Number of errors found = %d\n", count_err); + return count_err; + } + logError(0, "%s %s: ERROR %02X\n", tag, __func__, ERROR_I2C_R); + return ERROR_I2C_R; +} + +int flushFIFO(void) +{ + u8 cmd = FIFO_CMD_FLUSH; + + if (fts_writeCmd(&cmd, 1) < 0) { + logError(1, "%s %s: ERROR %02X\n", tag, __func__, ERROR_I2C_W); + return ERROR_I2C_W; + } + + logError(0, "%s FIFO flushed!\n", tag); + return OK; +} + +int fts_disableInterrupt(void) +{ + //disable interrupt + u8 cmd[4] = { FTS_CMD_HW_REG_W, 0x00, 0x00, IER_DISABLE }; + + u16ToU8_be(IER_ADDR, &cmd[1]); + + if (fts_writeCmd(cmd, 4) < OK) { + logError(1, "%s %s: ERROR %02X\n", tag, __func__, ERROR_I2C_W); + return ERROR_I2C_W; + } + logError(0, "%s Interrupt Disabled!\n", tag); + return OK; +} + + +int fts_enableInterrupt(void) +{ + u8 cmd[4] = { FTS_CMD_HW_REG_W, 0x00, 0x00, IER_ENABLE }; + + u16ToU8_be(IER_ADDR, &cmd[1]); + if (fts_writeCmd(cmd, 4) < 0) { + logError(1, "%s %s: ERROR %02X\n", tag, __func__, ERROR_I2C_W); + return ERROR_I2C_W; + } + logError(0, "%s Interrupt Enabled!\n", tag); + return OK; +} + +int u8ToU16n(u8 *src, int src_length, u16 *dst) +{ + int i, j; + u16 *buf; + + if (src_length % 2 != 0) + return -EINVAL; + + j = 0; + buf = (u16 *)kmalloc_array((src_length / 2), sizeof(u16), GFP_KERNEL); + if (!buf) { + dst = NULL; + return -EINVAL; + } + dst = buf; + for (i = 0; i < src_length; i += 2) { + dst[j] = ((src[i+1] & 0x00FF) << 8) + (src[i] & 0x00FF); + j++; + } + + return (src_length / 2); +} + +int u8ToU16(u8 *src, u16 *dst) +{ + *dst = (u16)(((src[1] & 0x00FF) << 8) + (src[0] & 0x00FF)); + return 0; +} + +int u8ToU16_le(u8 *src, u16 *dst) +{ + *dst = (u16)(((src[0] & 0x00FF) << 8) + (src[1] & 0x00FF)); + return 0; +} + +int u16ToU8n(u16 *src, int src_length, u8 *dst) +{ + int i, j; + u8 *buf = (u8 *)kmalloc_array(2 * src_length, sizeof(u8), GFP_KERNEL); + + if (!buf) { + dst = NULL; + return -EINVAL; + } + dst = buf; + j = 0; + for (i = 0; i < src_length; i++) { + dst[j] = (u8) (src[i] & 0xFF00) >> 8; + dst[j+1] = (u8) (src[i] & 0x00FF); + j += 2; + } + + return src_length * 2; +} + +int u16ToU8(u16 src, u8 *dst) +{ + dst[0] = (u8)((src & 0xFF00) >> 8); + dst[1] = (u8)(src & 0x00FF); + return 0; +} + +int u16ToU8_be(u16 src, u8 *dst) +{ + dst[0] = (u8)((src & 0xFF00) >> 8); + dst[1] = (u8)(src & 0x00FF); + return 0; +} + +int u16ToU8_le(u16 src, u8 *dst) +{ + dst[1] = (u8)((src & 0xFF00) >> 8); + dst[0] = (u8)(src & 0x00FF); + return 0; +} + +int u8ToU32(u8 *src, u32 *dst) +{ + *dst = (u32)(((src[3] & 0xFF) << 24) + ((src[2] & 0xFF) << 16) + + ((src[1] & 0xFF) << 8) + (src[0] & 0xFF)); + return 0; +} + +int u32ToU8(u32 src, u8 *dst) +{ + dst[3] = (u8)((src & 0xFF000000) >> 24); + dst[2] = (u8)((src & 0x00FF0000) >> 16); + dst[1] = (u8)((src & 0x0000FF00) >> 8); + dst[0] = (u8)(src & 0x000000FF); + return 0; +} + +int attempt_function(int(*code)(void), unsigned long wait_before_retry, + int retry_count) +{ + int result; + int count = 0; + + do { + result = code(); + count++; + msleep(wait_before_retry); + } while (count < retry_count && result < 0); + + if (count == retry_count) + result |= ERROR_TIMEOUT; + + return result; +} + +void setResetGpio(int gpio) +{ + reset_gpio = gpio; + logError(0, "%s %s: reset_gpio = %d\n", tag, __func__, reset_gpio); +} + +int fts_system_reset(void) +{ + u8 readData[FIFO_EVENT_SIZE]; + int event_to_search; + int res = -1; + int i; + u8 cmd[4] = { FTS_CMD_HW_REG_W, 0x00, 0x00, SYSTEM_RESET_VALUE }; + + event_to_search = (int)EVENTID_CONTROL_READY; + + u16ToU8_be(SYSTEM_RESET_ADDRESS, &cmd[1]); + logError(0, "%s System resetting...\n", tag); + for (i = 0; i < SYSTEM_RESET_RETRY && res < 0; i++) { + if (reset_gpio == GPIO_NOT_DEFINED) { +#ifndef FTM3_CHIP + res |= fts_warm_boot(); +#endif + res = fts_writeCmd(cmd, 4); + } else { + gpio_set_value(reset_gpio, 0); + msleep(20); + gpio_set_value(reset_gpio, 1); + res = OK; + } + if (res < OK) { + logError(1, "%s %s:ERROR %02X\n", + tag, __func__, ERROR_I2C_W); + } else { + res = pollForEvent(&event_to_search, 1, + readData, GENERAL_TIMEOUT); + if (res < OK) { + logError(0, "%s %s: ERROR %02X\n", + tag, __func__, res); + } + } + } + if (res < OK) { + logError(1, "%s %s:failed after 3 attempts: ERROR %02X\n", + tag, __func__, (res | ERROR_SYSTEM_RESET_FAIL)); + res = (res | ERROR_SYSTEM_RESET_FAIL); + } else { + logError(0, "%s System reset DONE!\n", tag); + system_resetted_down = 1; + system_resetted_up = 1; + res = OK; + } + return res; +} + +int isSystemResettedDown(void) +{ + return system_resetted_down; +} + +int isSystemResettedUp(void) +{ + return system_resetted_up; +} + +void setSystemResettedDown(int val) +{ + system_resetted_down = val; +} + +void setSystemResettedUp(int val) +{ + system_resetted_up = val; +} + +int senseOn(void) +{ + int ret; + u8 cmd[1] = { FTS_CMD_MS_MT_SENSE_ON }; + + ret = fts_writeFwCmd(cmd, 1); + if (ret < OK) { + logError(1, "%s %s:ERROR %02X\n", + tag, __func__, ERROR_SENSE_ON_FAIL); + return (ret|ERROR_SENSE_ON_FAIL); + } + logError(0, "%s %s: SENSE ON\n", tag, __func__); + return OK; +} + +int senseOff(void) +{ + int ret; + u8 cmd[1] = { FTS_CMD_MS_MT_SENSE_OFF }; + + ret = fts_writeFwCmd(cmd, 1); + if (ret < OK) { + logError(1, "%s %s:ERROR %02X\n", + tag, __func__, ERROR_SENSE_OFF_FAIL); + return (ret | ERROR_SENSE_OFF_FAIL); + } + logError(0, "%s %s: SENSE OFF\n", tag, __func__); + return OK; +} + +int keyOn(void) +{ + int ret; + u8 cmd[1] = { FTS_CMD_MS_KEY_ON }; + + ret = fts_writeFwCmd(cmd, 1); + if (ret < OK) { + logError(1, "%s %s:ERROR %02X\n", + tag, __func__, ERROR_SENSE_ON_FAIL); + return (ret | ERROR_SENSE_ON_FAIL); + } + + logError(0, "%s %s: KEY ON\n", tag, __func__); + return OK; +} + +int keyOff(void) +{ + int ret; + u8 cmd[1] = { FTS_CMD_MS_KEY_OFF }; + + ret = fts_writeFwCmd(cmd, 1); + if (ret < OK) { + logError(1, "%s %s:ERROR %02X\n", + tag, __func__, ERROR_SENSE_OFF_FAIL); + return (ret | ERROR_SENSE_OFF_FAIL); + } + + logError(0, "%s %s: KEY OFF\n", tag, __func__); + return OK; +} + +int cleanUp(int enableTouch) +{ + int res; + + logError(0, "%s %s: system reset...\n", tag, __func__); + res = fts_system_reset(); + if (res < OK) + return res; + + if (enableTouch) { + logError(0, "%s %s:enabling touches...\n", tag, __func__); + res = senseOn(); + if (res < OK) + return res; +#ifdef PHONE_KEY + res = keyOn(); + if (res < OK) + return res; +#endif + logError(0, "%s %s:enabling interrupts...\n", tag, __func__); + res = fts_enableInterrupt(); + if (res < OK) + return res; + } + return OK; +} + +int checkEcho(u8 *cmd, int size) +{ + int ret, i; + int event_to_search[FIFO_EVENT_SIZE + 1]; + u8 readData[FIFO_EVENT_SIZE]; + + if ((ftsInfo.u32_echoEn & 0x00000001) != ECHO_ENABLED) { + logError(0, "%s ECHO Not Enabled!\n", tag); + return OK; + } + if (size < 1) { + logError(1, "%s:Error Size = %d not valid!", tag, size); + logError(1, " or ECHO not Enabled!%08X\n", ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + if ((size + 2) > FIFO_EVENT_SIZE) + size = FIFO_EVENT_SIZE - 2; + //Echo event EC xx xx xx xx xx xx + //fifo_status therefore for command + //with more than 6 bytes will echo only the first 6 + event_to_search[0] = EVENTID_ECHO; + for (i = 1; i <= size; i++) + event_to_search[i] = cmd[i - 1]; + ret = pollForEvent(event_to_search, size + 1, + readData, GENERAL_TIMEOUT); + if (ret < OK) { + logError(1, "%s %s:Echo Event not found! ERROR %02X\n", + tag, __func__, ret); + return (ret | ERROR_CHECK_ECHO_FAIL); + } + + logError(0, "%s ECHO OK!\n", tag); + ret = OK; + return ret; +} + +int featureEnableDisable(int on_off, u32 feature) +{ + int ret; + u8 cmd[5]; + + if (on_off == FEAT_ENABLE) { + cmd[0] = FTS_CMD_FEATURE_ENABLE; + logError(0, "%s %s: Enabling feature %08X ...\n", + tag, __func__, feature); + } else { + cmd[0] = FTS_CMD_FEATURE_DISABLE; + logError(0, "%s %s: Disabling feature %08X ...\n", + tag, __func__, feature); + } + u32ToU8(feature, &cmd[1]); + + //not use writeFwCmd because this function can be + //called also during interrupt enable and should be fast + ret = fts_writeCmd(cmd, 5); + if (ret < OK) { + logError(1, "%s %s: ERROR %02X\n", tag, __func__, ret); + return (ret | ERROR_FEATURE_ENABLE_DISABLE); + } + + logError(0, "%s %s: DONE!\n", tag, __func__); + return OK; +} + +int writeNoiseParameters(u8 *noise) +{ + int ret, i; + u8 cmd[2+NOISE_PARAMETERS_SIZE]; + u8 readData[FIFO_EVENT_SIZE]; + int event_to_search[2] = {EVENTID_NOISE_WRITE, NOISE_PARAMETERS}; + + logError(0, "%s %s: Writing noise parameters to the IC ...\n", + tag, __func__); + ret = fts_disableInterrupt(); + if (ret < OK) { + logError(1, "%s %s: ERROR %08X\n", tag, __func__, ret); + ret = (ret | ERROR_NOISE_PARAMETERS); + goto ERROR; + } + cmd[0] = FTS_CMD_NOISE_WRITE; + cmd[1] = NOISE_PARAMETERS; + logError(0, "%s %s: Noise parameters = ", tag, __func__); + for (i = 0; i < NOISE_PARAMETERS_SIZE; i++) { + cmd[2 + i] = noise[i]; + logError(0, "%02X", cmd[2 + i]); + } + + logError(0, "\n"); + ret = fts_writeCmd(cmd, NOISE_PARAMETERS_SIZE + 2); + //not use writeFwCmd because this function should be fast + if (ret < OK) { + logError(0, "%s %s:impossible write command... ERROR %02X\n", + tag, __func__, ret); + ret = (ret | ERROR_NOISE_PARAMETERS); + goto ERROR; + } + + ret = pollForEvent(event_to_search, 2, readData, GENERAL_TIMEOUT); + if (ret < OK) { + logError(0, "%s %s: polling FIFO ERROR %02X\n", + tag, __func__, ret); + ret = (ret | ERROR_NOISE_PARAMETERS); + goto ERROR; + } + + if (readData[2] != 0x00) { + logError(1, "%s %s:Event check FAIL! %02X != 0x00 ERROR%02X\n", + tag, __func__, readData[2], ERROR_NOISE_PARAMETERS); + ret = ERROR_NOISE_PARAMETERS; + goto ERROR; + } + + logError(0, "%s %s:DONE!\n", tag, __func__); + ret = OK; +ERROR: + ret = fts_enableInterrupt(); + //ensure that the interrupt are always renabled when exit from funct + if (ret < OK) { + logError(1, "%s %s: ERROR %02X\n", tag, __func__, ret); + return (ret | ERROR_NOISE_PARAMETERS); + } + return ret; +} + +int readNoiseParameters(u8 *noise) +{ + int ret, i; + u8 cmd[2]; + u8 readData[FIFO_EVENT_SIZE]; + int event_to_search[2] = {EVENTID_NOISE_READ, NOISE_PARAMETERS}; + + logError(0, "%s %s:Reading noise parameters from the IC ...\n", + tag, __func__); + ret = fts_disableInterrupt(); + if (ret < OK) { + logError(1, "%s %s: ERROR %02X\n", tag, __func__, ret); + ret = (ret | ERROR_NOISE_PARAMETERS); + goto ERROR; + } + cmd[0] = FTS_CMD_NOISE_READ; + cmd[1] = NOISE_PARAMETERS; + ret = fts_writeCmd(cmd, 2);//not use writeFwCmd should be fast + if (ret < OK) { + logError(0, "%s %s:impossible write command... ERROR %02X\n", + tag, __func__, ret); + ret = (ret | ERROR_NOISE_PARAMETERS); + goto ERROR; + } + + ret = pollForEvent(event_to_search, 2, readData, GENERAL_TIMEOUT); + if (ret < OK) { + logError(0, "%s %s: polling FIFO ERROR %02X\n", + tag, __func__, ret); + ret = (ret | ERROR_NOISE_PARAMETERS); + goto ERROR; + } + + logError(0, "%s %s: Noise parameters = ", tag, __func__); + for (i = 0; i < NOISE_PARAMETERS_SIZE; i++) { + noise[i] = readData[2 + i]; + logError(0, "%02X ", noise[i]); + } + + logError(0, "\n"); + logError(0, "%s %s: DONE!\n", tag, __func__); + ret = OK; +ERROR: + ret = fts_enableInterrupt(); + //ensure that the interrupt are always renabled when exit from funct + if (ret < OK) { + logError(1, "%s %s: ERROR %02X\n", tag, __func__, ret); + return (ret | ERROR_NOISE_PARAMETERS); + } + return ret; +} + +short **array1dTo2d_short(short *data, int size, int columns) +{ + int i; + int count = size / columns; + short **matrix = (short **)kmalloc_array(count, + sizeof(short *), GFP_KERNEL); + if (matrix != NULL) { + for (i = 0; i < count; i++) { + matrix[i] = (short *)kmalloc_array(columns, + sizeof(short), GFP_KERNEL); + } + + for (i = 0; i < size; i++) + matrix[i / columns][i % columns] = data[i]; + } + + return matrix; +} + +u8 **array1dTo2d_u8(u8 *data, int size, int columns) +{ + int i; + int count = size / columns; + u8 **matrix = (u8 **)kmalloc_array(count, + sizeof(u8 *), GFP_KERNEL); + if (matrix != NULL) { + for (i = 0; i < count; i++) { + matrix[i] = (u8 *)kmalloc_array(columns, + sizeof(u8), GFP_KERNEL); + } + + for (i = 0; i < size; i++) + matrix[i / columns][i % columns] = data[i]; + } + + return matrix; +} + +void print_frame_short(char *label, short **matrix, int row, int column) +{ + int i, j; + + logError(0, "%s %s\n", tag, label); + for (i = 0; i < row; i++) { + logError(0, "%s ", tag); + for (j = 0; j < column; j++) + logError(0, "%d", matrix[i][j]); + logError(0, "\n"); + kfree(matrix[i]); + } + kfree(matrix); +} + +void print_frame_u8(char *label, u8 **matrix, int row, int column) +{ + int i, j; + + logError(0, "%s %s\n", tag, label); + for (i = 0; i < row; i++) { + logError(0, "%s ", tag); + for (j = 0; j < column; j++) + logError(0, "%d ", matrix[i][j]); + logError(0, "\n"); + kfree(matrix[i]); + } + kfree(matrix); +} + +void print_frame_u32(char *label, u32 **matrix, int row, int column) +{ + int i, j; + + logError(0, "%s %s\n", tag, label); + for (i = 0; i < row; i++) { + logError(0, "%s ", tag); + for (j = 0; j < column; j++) + logError(0, "%d ", matrix[i][j]); + logError(0, "\n"); + kfree(matrix[i]); + } + kfree(matrix); +} + +void print_frame_int(char *label, int **matrix, int row, int column) +{ + int i, j; + + logError(0, "%s %s\n", tag, label); + for (i = 0; i < row; i++) { + logError(0, "%s ", tag); + for (j = 0; j < column; j++) + logError(0, "%d ", matrix[i][j]); + logError(0, "\n"); + kfree(matrix[i]); + } + kfree(matrix); +} diff --git a/qcom/opensource/touch-drivers/st/fts_lib/ftsTool.h b/qcom/opensource/touch-drivers/st/fts_lib/ftsTool.h new file mode 100644 index 0000000000..350cbd57e1 --- /dev/null +++ b/qcom/opensource/touch-drivers/st/fts_lib/ftsTool.h @@ -0,0 +1,107 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * FTS Capacitive touch screen controller (FingerTipS) + * + * Copyright (C) 2016-2019, STMicroelectronics Limited. + * Authors: AMG(Analog Mems Group) + * + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +/** + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS Utility Functions * + * * + ************************************************************************** + ************************************************************************** + * + */ + +#ifndef __FTS_TOOL_H +#define __FTS_TOOL_H + +#define GPIO_NOT_DEFINED -1 +#define TIMEOUT_RESOLUTION 10 //ms +#define GENERAL_TIMEOUT (50*TIMEOUT_RESOLUTION) //ms +#define RELEASE_INFO_TIMEOUT (15*TIMEOUT_RESOLUTION) //ms + + +#define FEAT_ENABLE 1 +#define FEAT_DISABLE 0 + +#define SYSTEM_RESET_RETRY 3 + +#define B2_RETRY 2 +//for FTM4 can not be greater than 13 bytes +#define LOCKDOWN_CODE_SIZE 10 + +#define LOCKDOWN_CODE_MAX_SIZE 63 +#define LOCKDOWN_CODE_WRITE_CHUNK 12 +#define LOCKDOWN_CODE_READ_CHUNK 4 +#define LOCKDOWN_CODE_RETRY 2 + +int readB2(u16 address, u8 *outBuf, int len); +int readB2U16(u16 address, u8 *outBuf, int byteToRead); +int releaseInformation(void); +int lockDownInfo(u8 *data, int len); +int calculateCRC8(u8 *u8_srcBuff, int size, u8 *crc); +int writeLockDownInfo(u8 *data, int size); +int rewriteLockDownInfo(u8 *data, int size); +int readLockDownInfo(u8 *lockData, int *size); +char *printHex(char *label, u8 *buff, int count); +int pollForEvent(int *event_to_search, int event_bytes, + u8 *readData, int time_to_wait); +int fts_disableInterrupt(void); +int fts_enableInterrupt(void); +int u8ToU16(u8 *src, u16 *dst); +int u8ToU16_le(u8 *src, u16 *dst); +int u8ToU16n(u8 *src, int src_length, u16 *dst); +int u16ToU8(u16 src, u8 *dst); +int u16ToU8_le(u16 src, u8 *dst); +int u16ToU8_be(u16 src, u8 *dst); +int u16ToU8n(u16 *src, int src_length, u8 *dst); +int u8ToU32(u8 *src, u32 *dst); +int u32ToU8(u32 src, u8 *dst); +int attempt_function(int(*code)(void), unsigned long wait_before_retry, + int retry_count); +void setResetGpio(int gpio); +int fts_system_reset(void); +int isSystemResettedUp(void); +int isSystemResettedDown(void); +void setSystemResettedUp(int val); +void setSystemResettedDown(int val); +int senseOn(void); +int senseOff(void); +int keyOn(void); +int keyOff(void); +int featureEnableDisable(int on_off, u32 feature); +int writeNoiseParameters(u8 *noise); +int readNoiseParameters(u8 *noise); +int checkEcho(u8 *cmd, int size); +void print_frame_short(char *label, short **matrix, int row, int column); +short **array1dTo2d_short(short *data, int size, int columns); +u8 **array1dTo2d_u8(u8 *data, int size, int columns); +void print_frame_u8(char *label, u8 **matrix, int row, int column); +void print_frame_u32(char *label, u32 **matrix, int row, int column); +void print_frame_int(char *label, int **matrix, int row, int column); +int cleanUp(int enableTouch); +int flushFIFO(void); + +#endif diff --git a/qcom/opensource/touch-drivers/synaptics_dsx/synaptics_dsx_active_pen.c b/qcom/opensource/touch-drivers/synaptics_dsx/synaptics_dsx_active_pen.c new file mode 100644 index 0000000000..6cd855043b --- /dev/null +++ b/qcom/opensource/touch-drivers/synaptics_dsx/synaptics_dsx_active_pen.c @@ -0,0 +1,606 @@ +/* + * Synaptics DSX touchscreen driver + * + * Copyright (C) 2012-2016 Synaptics Incorporated. All rights reserved. + * + * Copyright (c) 2018-2021 The Linux Foundation. All rights reserved. + * Copyright (C) 2012 Alexandra Chin + * Copyright (C) 2012 Scott Lin + * + * 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 +#include +#include +#include "synaptics_dsx_core.h" + +#define APEN_PHYS_NAME "synaptics_dsx/active_pen" + +#define ACTIVE_PEN_MAX_PRESSURE_16BIT 65535 +#define ACTIVE_PEN_MAX_PRESSURE_8BIT 255 + +struct synaptics_rmi4_f12_query_8 { + union { + struct { + unsigned char size_of_query9; + struct { + unsigned char data0_is_present:1; + unsigned char data1_is_present:1; + unsigned char data2_is_present:1; + unsigned char data3_is_present:1; + unsigned char data4_is_present:1; + unsigned char data5_is_present:1; + unsigned char data6_is_present:1; + unsigned char data7_is_present:1; + } __packed; + }; + unsigned char data[2]; + }; +}; + +struct apen_data_8b_pressure { + union { + struct { + unsigned char status_pen:1; + unsigned char status_invert:1; + unsigned char status_barrel:1; + unsigned char status_reserved:5; + unsigned char x_lsb; + unsigned char x_msb; + unsigned char y_lsb; + unsigned char y_msb; + unsigned char pressure_msb; + unsigned char battery_state; + unsigned char pen_id_0_7; + unsigned char pen_id_8_15; + unsigned char pen_id_16_23; + unsigned char pen_id_24_31; + } __packed; + unsigned char data[11]; + }; +}; + +struct apen_data { + union { + struct { + unsigned char status_pen:1; + unsigned char status_invert:1; + unsigned char status_barrel:1; + unsigned char status_reserved:5; + unsigned char x_lsb; + unsigned char x_msb; + unsigned char y_lsb; + unsigned char y_msb; + unsigned char pressure_lsb; + unsigned char pressure_msb; + unsigned char battery_state; + unsigned char pen_id_0_7; + unsigned char pen_id_8_15; + unsigned char pen_id_16_23; + unsigned char pen_id_24_31; + } __packed; + unsigned char data[12]; + }; +}; + +struct synaptics_rmi4_apen_handle { + bool apen_present; + unsigned char intr_mask; + unsigned char battery_state; + unsigned short query_base_addr; + unsigned short control_base_addr; + unsigned short data_base_addr; + unsigned short command_base_addr; + unsigned short apen_data_addr; + unsigned short max_pressure; + unsigned int pen_id; + struct input_dev *apen_dev; + struct apen_data *apen_data; + struct synaptics_rmi4_data *rmi4_data; +}; + +static struct synaptics_rmi4_apen_handle *apen; + +DECLARE_COMPLETION(apen_remove_complete); + +static void apen_lift(void) +{ + input_report_key(apen->apen_dev, BTN_TOUCH, 0); + input_report_key(apen->apen_dev, BTN_TOOL_PEN, 0); + input_report_key(apen->apen_dev, BTN_TOOL_RUBBER, 0); + input_sync(apen->apen_dev); + apen->apen_present = false; +} + +static void apen_report(void) +{ + int retval; + int x; + int y; + int pressure; + static int invert = -1; + struct apen_data_8b_pressure *apen_data_8b; + struct synaptics_rmi4_data *rmi4_data = apen->rmi4_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + apen->apen_data_addr, + apen->apen_data->data, + sizeof(apen->apen_data->data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read active pen data\n", + __func__); + return; + } + + if (apen->apen_data->status_pen == 0) { + if (apen->apen_present) + apen_lift(); + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: No active pen data\n", + __func__); + + return; + } + + x = (apen->apen_data->x_msb << 8) | (apen->apen_data->x_lsb); + y = (apen->apen_data->y_msb << 8) | (apen->apen_data->y_lsb); + + if ((x == -1) && (y == -1)) { + if (apen->apen_present) + apen_lift(); + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Active pen in range but no valid x & y\n", + __func__); + + return; + } + + if (!apen->apen_present) + invert = -1; + + if (invert != -1 && invert != apen->apen_data->status_invert) + apen_lift(); + + invert = apen->apen_data->status_invert; + + if (apen->max_pressure == ACTIVE_PEN_MAX_PRESSURE_16BIT) { + pressure = (apen->apen_data->pressure_msb << 8) | + apen->apen_data->pressure_lsb; + apen->battery_state = apen->apen_data->battery_state; + apen->pen_id = (apen->apen_data->pen_id_24_31 << 24) | + (apen->apen_data->pen_id_16_23 << 16) | + (apen->apen_data->pen_id_8_15 << 8) | + apen->apen_data->pen_id_0_7; + } else { + apen_data_8b = (struct apen_data_8b_pressure *)apen->apen_data; + pressure = apen_data_8b->pressure_msb; + apen->battery_state = apen_data_8b->battery_state; + apen->pen_id = (apen_data_8b->pen_id_24_31 << 24) | + (apen_data_8b->pen_id_16_23 << 16) | + (apen_data_8b->pen_id_8_15 << 8) | + apen_data_8b->pen_id_0_7; + } + + input_report_key(apen->apen_dev, BTN_TOUCH, pressure > 0 ? 1 : 0); + input_report_key(apen->apen_dev, + apen->apen_data->status_invert > 0 ? + BTN_TOOL_RUBBER : BTN_TOOL_PEN, 1); + input_report_key(apen->apen_dev, + BTN_STYLUS, apen->apen_data->status_barrel > 0 ? + 1 : 0); + input_report_abs(apen->apen_dev, ABS_X, x); + input_report_abs(apen->apen_dev, ABS_Y, y); + input_report_abs(apen->apen_dev, ABS_PRESSURE, pressure); + + input_sync(apen->apen_dev); + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Active pen: status = %d, invert = %d, barrel = %d, x = %d, y = %d, pressure = %d\n", + __func__, + apen->apen_data->status_pen, + apen->apen_data->status_invert, + apen->apen_data->status_barrel, + x, y, pressure); + + apen->apen_present = true; +} + +static void apen_set_params(void) +{ + input_set_abs_params(apen->apen_dev, ABS_X, 0, + apen->rmi4_data->sensor_max_x, 0, 0); + input_set_abs_params(apen->apen_dev, ABS_Y, 0, + apen->rmi4_data->sensor_max_y, 0, 0); + input_set_abs_params(apen->apen_dev, ABS_PRESSURE, 0, + apen->max_pressure, 0, 0); +} + +static int apen_pressure(struct synaptics_rmi4_f12_query_8 *query_8) +{ + int retval; + unsigned char ii; + unsigned char data_reg_presence; + unsigned char size_of_query_9; + unsigned char *query_9; + unsigned char *data_desc; + struct synaptics_rmi4_data *rmi4_data = apen->rmi4_data; + + data_reg_presence = query_8->data[1]; + + size_of_query_9 = query_8->size_of_query9; + query_9 = kmalloc(size_of_query_9, GFP_KERNEL); + + retval = synaptics_rmi4_reg_read(rmi4_data, + apen->query_base_addr + 9, + query_9, + size_of_query_9); + if (retval < 0) + goto exit; + + data_desc = query_9; + + for (ii = 0; ii < 6; ii++) { + if (!(data_reg_presence & (1 << ii))) + continue; /* The data register is not present */ + data_desc++; /* Jump over the size entry */ + while (*data_desc & (1 << 7)) + data_desc++; + data_desc++; /* Go to the next descriptor */ + } + + data_desc++; /* Jump over the size entry */ + /* Check for the presence of subpackets 1 and 2 */ + if ((*data_desc & (3 << 1)) == (3 << 1)) + apen->max_pressure = ACTIVE_PEN_MAX_PRESSURE_16BIT; + else + apen->max_pressure = ACTIVE_PEN_MAX_PRESSURE_8BIT; + +exit: + kfree(query_9); + + return retval; +} + +static int apen_reg_init(void) +{ + int retval; + unsigned char data_offset; + unsigned char size_of_query8; + struct synaptics_rmi4_f12_query_8 query_8; + struct synaptics_rmi4_data *rmi4_data = apen->rmi4_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + apen->query_base_addr + 7, + &size_of_query8, + sizeof(size_of_query8)); + if (retval < 0) + return retval; + + retval = synaptics_rmi4_reg_read(rmi4_data, + apen->query_base_addr + 8, + query_8.data, + sizeof(query_8.data)); + if (retval < 0) + return retval; + + if ((size_of_query8 >= 2) && (query_8.data6_is_present)) { + data_offset = query_8.data0_is_present + + query_8.data1_is_present + + query_8.data2_is_present + + query_8.data3_is_present + + query_8.data4_is_present + + query_8.data5_is_present; + apen->apen_data_addr = apen->data_base_addr + data_offset; + retval = apen_pressure(&query_8); + if (retval < 0) + return retval; + } else { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Active pen support unavailable\n", + __func__); + retval = -ENODEV; + } + + return retval; +} + +static int apen_scan_pdt(void) +{ + int retval; + unsigned char ii; + unsigned char page; + unsigned char intr_count = 0; + unsigned char intr_off; + unsigned char intr_src; + unsigned short addr; + struct synaptics_rmi4_fn_desc fd; + struct synaptics_rmi4_data *rmi4_data = apen->rmi4_data; + + for (page = 0; page < PAGES_TO_SERVICE; page++) { + for (addr = PDT_START; addr > PDT_END; addr -= PDT_ENTRY_SIZE) { + addr |= (page << 8); + + retval = synaptics_rmi4_reg_read(rmi4_data, + addr, + (unsigned char *)&fd, + sizeof(fd)); + if (retval < 0) + return retval; + + addr &= ~(MASK_8BIT << 8); + + if (fd.fn_number) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Found F%02x\n", + __func__, fd.fn_number); + switch (fd.fn_number) { + case SYNAPTICS_RMI4_F12: + goto f12_found; + } + } else { + break; + } + + intr_count += fd.intr_src_count; + } + } + + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to find F12\n", + __func__); + return -EINVAL; + +f12_found: + apen->query_base_addr = fd.query_base_addr | (page << 8); + apen->control_base_addr = fd.ctrl_base_addr | (page << 8); + apen->data_base_addr = fd.data_base_addr | (page << 8); + apen->command_base_addr = fd.cmd_base_addr | (page << 8); + + retval = apen_reg_init(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to initialize active pen registers\n", + __func__); + return retval; + } + + apen->intr_mask = 0; + intr_src = fd.intr_src_count; + intr_off = intr_count % 8; + for (ii = intr_off; + ii < (intr_src + intr_off); + ii++) { + apen->intr_mask |= 1 << ii; + } + + rmi4_data->intr_mask[0] |= apen->intr_mask; + + addr = rmi4_data->f01_ctrl_base_addr + 1; + + retval = synaptics_rmi4_reg_write(rmi4_data, + addr, + &(rmi4_data->intr_mask[0]), + sizeof(rmi4_data->intr_mask[0])); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set interrupt enable bit\n", + __func__); + return retval; + } + + return 0; +} + +static void synaptics_rmi4_apen_attn(struct synaptics_rmi4_data *rmi4_data, + unsigned char intr_mask) +{ + if (!apen) + return; + + if (apen->intr_mask & intr_mask) + apen_report(); + + return; +} + +static int synaptics_rmi4_apen_init(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + + if (apen) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Handle already exists\n", + __func__); + return 0; + } + + apen = kzalloc(sizeof(*apen), GFP_KERNEL); + if (!apen) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for apen\n", + __func__); + retval = -ENOMEM; + goto exit; + } + + apen->apen_data = kzalloc(sizeof(*(apen->apen_data)), GFP_KERNEL); + if (!apen->apen_data) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for apen_data\n", + __func__); + retval = -ENOMEM; + goto exit_free_apen; + } + + apen->rmi4_data = rmi4_data; + + retval = apen_scan_pdt(); + if (retval < 0) + goto exit_free_apen_data; + + apen->apen_dev = input_allocate_device(); + if (apen->apen_dev == NULL) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to allocate active pen device\n", + __func__); + retval = -ENOMEM; + goto exit_free_apen_data; + } + + apen->apen_dev->name = ACTIVE_PEN_DRIVER_NAME; + apen->apen_dev->phys = APEN_PHYS_NAME; + apen->apen_dev->id.product = SYNAPTICS_DSX_DRIVER_PRODUCT; + apen->apen_dev->id.version = SYNAPTICS_DSX_DRIVER_VERSION; + apen->apen_dev->dev.parent = rmi4_data->pdev->dev.parent; + input_set_drvdata(apen->apen_dev, rmi4_data); + + set_bit(EV_KEY, apen->apen_dev->evbit); + set_bit(EV_ABS, apen->apen_dev->evbit); + set_bit(BTN_TOUCH, apen->apen_dev->keybit); + set_bit(BTN_TOOL_PEN, apen->apen_dev->keybit); + set_bit(BTN_TOOL_RUBBER, apen->apen_dev->keybit); + set_bit(BTN_STYLUS, apen->apen_dev->keybit); +#ifdef INPUT_PROP_DIRECT + set_bit(INPUT_PROP_DIRECT, apen->apen_dev->propbit); +#endif + + apen_set_params(); + + retval = input_register_device(apen->apen_dev); + if (retval) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to register active pen device\n", + __func__); + goto exit_free_input_device; + } + + return 0; + +exit_free_input_device: + input_free_device(apen->apen_dev); + +exit_free_apen_data: + kfree(apen->apen_data); + +exit_free_apen: + kfree(apen); + apen = NULL; + +exit: + return retval; +} + +static void synaptics_rmi4_apen_remove(struct synaptics_rmi4_data *rmi4_data) +{ + if (!apen) + goto exit; + + input_unregister_device(apen->apen_dev); + kfree(apen->apen_data); + kfree(apen); + apen = NULL; + +exit: + complete(&apen_remove_complete); +} + +static void synaptics_rmi4_apen_reset(struct synaptics_rmi4_data *rmi4_data) +{ + if (!apen) { + synaptics_rmi4_apen_init(rmi4_data); + return; + } + + apen_lift(); + + apen_scan_pdt(); +} + +static void synaptics_rmi4_apen_reinit(struct synaptics_rmi4_data *rmi4_data) +{ + if (!apen) + return; + + apen_lift(); +} + +static void synaptics_rmi4_apen_e_suspend(struct synaptics_rmi4_data *rmi4_data) +{ + if (!apen) + return; + + apen_lift(); +} + +static void synaptics_rmi4_apen_suspend(struct synaptics_rmi4_data *rmi4_data) +{ + if (!apen) + return; + + apen_lift(); +} + +static struct synaptics_rmi4_exp_fn active_pen_module = { + .fn_type = RMI_ACTIVE_PEN, + .init = synaptics_rmi4_apen_init, + .remove = synaptics_rmi4_apen_remove, + .reset = synaptics_rmi4_apen_reset, + .reinit = synaptics_rmi4_apen_reinit, + .early_suspend = synaptics_rmi4_apen_e_suspend, + .suspend = synaptics_rmi4_apen_suspend, + .resume = NULL, + .late_resume = NULL, + .attn = synaptics_rmi4_apen_attn, +}; + +static int __init rmi4_active_pen_module_init(void) +{ + synaptics_rmi4_new_function(&active_pen_module, true); + + return 0; +} + +static void __exit rmi4_active_pen_module_exit(void) +{ + synaptics_rmi4_new_function(&active_pen_module, false); + + wait_for_completion(&apen_remove_complete); +} + +module_init(rmi4_active_pen_module_init); +module_exit(rmi4_active_pen_module_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics DSX Active Pen Module"); +MODULE_LICENSE("GPL v2"); diff --git a/qcom/opensource/touch-drivers/synaptics_dsx/synaptics_dsx_core.c b/qcom/opensource/touch-drivers/synaptics_dsx/synaptics_dsx_core.c new file mode 100644 index 0000000000..065cdbb7da --- /dev/null +++ b/qcom/opensource/touch-drivers/synaptics_dsx/synaptics_dsx_core.c @@ -0,0 +1,5079 @@ +/* + * Synaptics DSX touchscreen driver + * + * Copyright (C) 2012-2016 Synaptics Incorporated. All rights reserved. + * + * Copyright (c) 2018-2021 The Linux Foundation. All rights reserved. + * Copyright (C) 2012 Alexandra Chin + * Copyright (C) 2012 Scott Lin + * + * 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 +#include +#include +#include +#include +#include +#include "synaptics_dsx_core.h" +#ifdef KERNEL_ABOVE_2_6_38 +#include +#endif + +#include + +#define INPUT_PHYS_NAME "synaptics_dsx/touch_input" +#define STYLUS_PHYS_NAME "synaptics_dsx/stylus" + +#define VIRTUAL_KEY_MAP_FILE_NAME "virtualkeys." PLATFORM_DRIVER_NAME + +#ifdef KERNEL_ABOVE_2_6_38 +#define TYPE_B_PROTOCOL +#endif + +/* +#define USE_DATA_SERVER +*/ + +#define WAKEUP_GESTURE false + +#define NO_0D_WHILE_2D +#define REPORT_2D_Z +#define REPORT_2D_W +/* +#define REPORT_2D_PRESSURE +*/ + +#define F12_DATA_15_WORKAROUND + +#define IGNORE_FN_INIT_FAILURE +#define FB_READY_RESET +#define FB_READY_WAIT_MS 100 +#define FB_READY_TIMEOUT_S 30 +#ifdef SYNA_TDDI +#define TDDI_LPWG_WAIT_US 10 +#endif +#define RPT_TYPE (1 << 0) +#define RPT_X_LSB (1 << 1) +#define RPT_X_MSB (1 << 2) +#define RPT_Y_LSB (1 << 3) +#define RPT_Y_MSB (1 << 4) +#define RPT_Z (1 << 5) +#define RPT_WX (1 << 6) +#define RPT_WY (1 << 7) +#define RPT_DEFAULT (RPT_TYPE | RPT_X_LSB | RPT_X_MSB | RPT_Y_LSB | RPT_Y_MSB) + +#define REBUILD_WORK_DELAY_MS 500 /* ms */ + +#define EXP_FN_WORK_DELAY_MS 500 /* ms */ +#define MAX_F11_TOUCH_WIDTH 15 +#define MAX_F12_TOUCH_WIDTH 255 + +#define CHECK_STATUS_TIMEOUT_MS 100 + +#define F01_STD_QUERY_LEN 21 +#define F01_BUID_ID_OFFSET 18 + +#define STATUS_NO_ERROR 0x00 +#define STATUS_RESET_OCCURRED 0x01 +#define STATUS_INVALID_CONFIG 0x02 +#define STATUS_DEVICE_FAILURE 0x03 +#define STATUS_CONFIG_CRC_FAILURE 0x04 +#define STATUS_FIRMWARE_CRC_FAILURE 0x05 +#define STATUS_CRC_IN_PROGRESS 0x06 + +#define NORMAL_OPERATION (0 << 0) +#define SENSOR_SLEEP (1 << 0) +#define NO_SLEEP_OFF (0 << 2) +#define NO_SLEEP_ON (1 << 2) +#define CONFIGURED (1 << 7) + +#define F11_CONTINUOUS_MODE 0x00 +#define F11_WAKEUP_GESTURE_MODE 0x04 +#define F12_CONTINUOUS_MODE 0x00 +#define F12_WAKEUP_GESTURE_MODE 0x02 +#define F12_UDG_DETECT 0x0f + +static int synaptics_rmi4_check_status(struct synaptics_rmi4_data *rmi4_data, + bool *was_in_bl_mode); +static int synaptics_rmi4_free_fingers(struct synaptics_rmi4_data *rmi4_data); +static int synaptics_rmi4_reinit_device(struct synaptics_rmi4_data *rmi4_data); +static int synaptics_rmi4_reset_device(struct synaptics_rmi4_data *rmi4_data, + bool rebuild); + +#ifdef CONFIG_DRM +static void synaptics_rmi4_dsi_panel_notifier_cb( + enum panel_event_notifier_tag tag, + struct panel_event_notification *notification, + void *client_data); +#endif + +#ifdef CONFIG_HAS_EARLYSUSPEND +#ifndef CONFIG_FB +#define USE_EARLYSUSPEND +#endif +#endif + +#ifdef USE_EARLYSUSPEND +static int synaptics_rmi4_early_suspend(struct early_suspend *h); + +static int synaptics_rmi4_late_resume(struct early_suspend *h); +#endif + +static int synaptics_rmi4_suspend(struct device *dev); + +static int synaptics_rmi4_resume(struct device *dev); + +static void synaptics_rmi4_defer_probe(struct work_struct *work); + +static ssize_t synaptics_rmi4_f01_reset_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t synaptics_rmi4_f01_productinfo_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t synaptics_rmi4_f01_buildid_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t synaptics_rmi4_f01_flashprog_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t synaptics_rmi4_0dbutton_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t synaptics_rmi4_0dbutton_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t synaptics_rmi4_suspend_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t synaptics_rmi4_wake_gesture_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t synaptics_rmi4_wake_gesture_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +#ifdef USE_DATA_SERVER +static ssize_t synaptics_rmi4_synad_pid_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); +#endif + +static ssize_t synaptics_rmi4_virtual_key_map_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf); + +struct synaptics_rmi4_f01_device_status { + union { + struct { + unsigned char status_code:4; + unsigned char reserved:2; + unsigned char flash_prog:1; + unsigned char unconfigured:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct synaptics_rmi4_f11_query_0_5 { + union { + struct { + /* query 0 */ + unsigned char f11_query0_b0__2:3; + unsigned char has_query_9:1; + unsigned char has_query_11:1; + unsigned char has_query_12:1; + unsigned char has_query_27:1; + unsigned char has_query_28:1; + + /* query 1 */ + unsigned char num_of_fingers:3; + unsigned char has_rel:1; + unsigned char has_abs:1; + unsigned char has_gestures:1; + unsigned char has_sensitibity_adjust:1; + unsigned char f11_query1_b7:1; + + /* query 2 */ + unsigned char num_of_x_electrodes; + + /* query 3 */ + unsigned char num_of_y_electrodes; + + /* query 4 */ + unsigned char max_electrodes:7; + unsigned char f11_query4_b7:1; + + /* query 5 */ + unsigned char abs_data_size:2; + unsigned char has_anchored_finger:1; + unsigned char has_adj_hyst:1; + unsigned char has_dribble:1; + unsigned char has_bending_correction:1; + unsigned char has_large_object_suppression:1; + unsigned char has_jitter_filter:1; + } __packed; + unsigned char data[6]; + }; +}; + +struct synaptics_rmi4_f11_query_7_8 { + union { + struct { + /* query 7 */ + unsigned char has_single_tap:1; + unsigned char has_tap_and_hold:1; + unsigned char has_double_tap:1; + unsigned char has_early_tap:1; + unsigned char has_flick:1; + unsigned char has_press:1; + unsigned char has_pinch:1; + unsigned char has_chiral_scroll:1; + + /* query 8 */ + unsigned char has_palm_detect:1; + unsigned char has_rotate:1; + unsigned char has_touch_shapes:1; + unsigned char has_scroll_zones:1; + unsigned char individual_scroll_zones:1; + unsigned char has_multi_finger_scroll:1; + unsigned char has_multi_finger_scroll_edge_motion:1; + unsigned char has_multi_finger_scroll_inertia:1; + } __packed; + unsigned char data[2]; + }; +}; + +struct synaptics_rmi4_f11_query_9 { + union { + struct { + unsigned char has_pen:1; + unsigned char has_proximity:1; + unsigned char has_large_object_sensitivity:1; + unsigned char has_suppress_on_large_object_detect:1; + unsigned char has_two_pen_thresholds:1; + unsigned char has_contact_geometry:1; + unsigned char has_pen_hover_discrimination:1; + unsigned char has_pen_hover_and_edge_filters:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct synaptics_rmi4_f11_query_12 { + union { + struct { + unsigned char has_small_object_detection:1; + unsigned char has_small_object_detection_tuning:1; + unsigned char has_8bit_w:1; + unsigned char has_2d_adjustable_mapping:1; + unsigned char has_general_information_2:1; + unsigned char has_physical_properties:1; + unsigned char has_finger_limit:1; + unsigned char has_linear_cofficient_2:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct synaptics_rmi4_f11_query_27 { + union { + struct { + unsigned char f11_query27_b0:1; + unsigned char has_pen_position_correction:1; + unsigned char has_pen_jitter_filter_coefficient:1; + unsigned char has_group_decomposition:1; + unsigned char has_wakeup_gesture:1; + unsigned char has_small_finger_correction:1; + unsigned char has_data_37:1; + unsigned char f11_query27_b7:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct synaptics_rmi4_f11_ctrl_6_9 { + union { + struct { + unsigned char sensor_max_x_pos_7_0; + unsigned char sensor_max_x_pos_11_8:4; + unsigned char f11_ctrl7_b4__7:4; + unsigned char sensor_max_y_pos_7_0; + unsigned char sensor_max_y_pos_11_8:4; + unsigned char f11_ctrl9_b4__7:4; + } __packed; + unsigned char data[4]; + }; +}; + +struct synaptics_rmi4_f11_data_1_5 { + union { + struct { + unsigned char x_position_11_4; + unsigned char y_position_11_4; + unsigned char x_position_3_0:4; + unsigned char y_position_3_0:4; + unsigned char wx:4; + unsigned char wy:4; + unsigned char z; + } __packed; + unsigned char data[5]; + }; +}; + +struct synaptics_rmi4_f12_query_5 { + union { + struct { + unsigned char size_of_query6; + struct { + unsigned char ctrl0_is_present:1; + unsigned char ctrl1_is_present:1; + unsigned char ctrl2_is_present:1; + unsigned char ctrl3_is_present:1; + unsigned char ctrl4_is_present:1; + unsigned char ctrl5_is_present:1; + unsigned char ctrl6_is_present:1; + unsigned char ctrl7_is_present:1; + } __packed; + struct { + unsigned char ctrl8_is_present:1; + unsigned char ctrl9_is_present:1; + unsigned char ctrl10_is_present:1; + unsigned char ctrl11_is_present:1; + unsigned char ctrl12_is_present:1; + unsigned char ctrl13_is_present:1; + unsigned char ctrl14_is_present:1; + unsigned char ctrl15_is_present:1; + } __packed; + struct { + unsigned char ctrl16_is_present:1; + unsigned char ctrl17_is_present:1; + unsigned char ctrl18_is_present:1; + unsigned char ctrl19_is_present:1; + unsigned char ctrl20_is_present:1; + unsigned char ctrl21_is_present:1; + unsigned char ctrl22_is_present:1; + unsigned char ctrl23_is_present:1; + } __packed; + struct { + unsigned char ctrl24_is_present:1; + unsigned char ctrl25_is_present:1; + unsigned char ctrl26_is_present:1; + unsigned char ctrl27_is_present:1; + unsigned char ctrl28_is_present:1; + unsigned char ctrl29_is_present:1; + unsigned char ctrl30_is_present:1; + unsigned char ctrl31_is_present:1; + } __packed; + struct { + unsigned char ctrl32_is_present:1; + unsigned char ctrl33_is_present:1; + unsigned char ctrl34_is_present:1; + unsigned char ctrl35_is_present:1; + unsigned char ctrl36_is_present:1; + unsigned char ctrl37_is_present:1; + unsigned char ctrl38_is_present:1; + unsigned char ctrl39_is_present:1; + } __packed; + struct { + unsigned char ctrl40_is_present:1; + unsigned char ctrl41_is_present:1; + unsigned char ctrl42_is_present:1; + unsigned char ctrl43_is_present:1; + unsigned char ctrl44_is_present:1; + unsigned char ctrl45_is_present:1; + unsigned char ctrl46_is_present:1; + unsigned char ctrl47_is_present:1; + } __packed; + struct { + unsigned char ctrl48_is_present:1; + unsigned char ctrl49_is_present:1; + unsigned char ctrl50_is_present:1; + unsigned char ctrl51_is_present:1; + unsigned char ctrl52_is_present:1; + unsigned char ctrl53_is_present:1; + unsigned char ctrl54_is_present:1; + unsigned char ctrl55_is_present:1; + } __packed; + struct { + unsigned char ctrl56_is_present:1; + unsigned char ctrl57_is_present:1; + unsigned char ctrl58_is_present:1; + unsigned char ctrl59_is_present:1; + unsigned char ctrl60_is_present:1; + unsigned char ctrl61_is_present:1; + unsigned char ctrl62_is_present:1; + unsigned char ctrl63_is_present:1; + } __packed; + }; + unsigned char data[9]; + }; +}; + +struct synaptics_rmi4_f12_query_8 { + union { + struct { + unsigned char size_of_query9; + struct { + unsigned char data0_is_present:1; + unsigned char data1_is_present:1; + unsigned char data2_is_present:1; + unsigned char data3_is_present:1; + unsigned char data4_is_present:1; + unsigned char data5_is_present:1; + unsigned char data6_is_present:1; + unsigned char data7_is_present:1; + } __packed; + struct { + unsigned char data8_is_present:1; + unsigned char data9_is_present:1; + unsigned char data10_is_present:1; + unsigned char data11_is_present:1; + unsigned char data12_is_present:1; + unsigned char data13_is_present:1; + unsigned char data14_is_present:1; + unsigned char data15_is_present:1; + } __packed; + struct { + unsigned char data16_is_present:1; + unsigned char data17_is_present:1; + unsigned char data18_is_present:1; + unsigned char data19_is_present:1; + unsigned char data20_is_present:1; + unsigned char data21_is_present:1; + unsigned char data22_is_present:1; + unsigned char data23_is_present:1; + } __packed; + struct { + unsigned char data24_is_present:1; + unsigned char data25_is_present:1; + unsigned char data26_is_present:1; + unsigned char data27_is_present:1; + unsigned char data28_is_present:1; + unsigned char data29_is_present:1; + unsigned char data30_is_present:1; + unsigned char data31_is_present:1; + } __packed; + }; + unsigned char data[5]; + }; +}; + +struct synaptics_rmi4_f12_ctrl_8 { + union { + struct { + unsigned char max_x_coord_lsb; + unsigned char max_x_coord_msb; + unsigned char max_y_coord_lsb; + unsigned char max_y_coord_msb; + unsigned char rx_pitch_lsb; + unsigned char rx_pitch_msb; + unsigned char tx_pitch_lsb; + unsigned char tx_pitch_msb; + unsigned char low_rx_clip; + unsigned char high_rx_clip; + unsigned char low_tx_clip; + unsigned char high_tx_clip; + unsigned char num_of_rx; + unsigned char num_of_tx; + }; + unsigned char data[14]; + }; +}; + +struct synaptics_rmi4_f12_ctrl_23 { + union { + struct { + unsigned char finger_enable:1; + unsigned char active_stylus_enable:1; + unsigned char palm_enable:1; + unsigned char unclassified_object_enable:1; + unsigned char hovering_finger_enable:1; + unsigned char gloved_finger_enable:1; + unsigned char f12_ctr23_00_b6__7:2; + unsigned char max_reported_objects; + unsigned char f12_ctr23_02_b0:1; + unsigned char report_active_stylus_as_finger:1; + unsigned char report_palm_as_finger:1; + unsigned char report_unclassified_object_as_finger:1; + unsigned char report_hovering_finger_as_finger:1; + unsigned char report_gloved_finger_as_finger:1; + unsigned char report_narrow_object_swipe_as_finger:1; + unsigned char report_handedge_as_finger:1; + unsigned char cover_enable:1; + unsigned char stylus_enable:1; + unsigned char eraser_enable:1; + unsigned char small_object_enable:1; + unsigned char f12_ctr23_03_b4__7:4; + unsigned char report_cover_as_finger:1; + unsigned char report_stylus_as_finger:1; + unsigned char report_eraser_as_finger:1; + unsigned char report_small_object_as_finger:1; + unsigned char f12_ctr23_04_b4__7:4; + }; + unsigned char data[5]; + }; +}; + +struct synaptics_rmi4_f12_ctrl_31 { + union { + struct { + unsigned char max_x_coord_lsb; + unsigned char max_x_coord_msb; + unsigned char max_y_coord_lsb; + unsigned char max_y_coord_msb; + unsigned char rx_pitch_lsb; + unsigned char rx_pitch_msb; + unsigned char rx_clip_low; + unsigned char rx_clip_high; + unsigned char wedge_clip_low; + unsigned char wedge_clip_high; + unsigned char num_of_p; + unsigned char num_of_q; + }; + unsigned char data[12]; + }; +}; + +struct synaptics_rmi4_f12_ctrl_58 { + union { + struct { + unsigned char reporting_format; + unsigned char f12_ctr58_00_reserved; + unsigned char min_force_lsb; + unsigned char min_force_msb; + unsigned char max_force_lsb; + unsigned char max_force_msb; + unsigned char light_press_threshold_lsb; + unsigned char light_press_threshold_msb; + unsigned char light_press_hysteresis_lsb; + unsigned char light_press_hysteresis_msb; + unsigned char hard_press_threshold_lsb; + unsigned char hard_press_threshold_msb; + unsigned char hard_press_hysteresis_lsb; + unsigned char hard_press_hysteresis_msb; + }; + unsigned char data[14]; + }; +}; + +struct synaptics_rmi4_f12_finger_data { + unsigned char object_type_and_status; + unsigned char x_lsb; + unsigned char x_msb; + unsigned char y_lsb; + unsigned char y_msb; +#ifdef REPORT_2D_Z + unsigned char z; +#endif +#ifdef REPORT_2D_W + unsigned char wx; + unsigned char wy; +#endif +}; + +struct synaptics_rmi4_f1a_query { + union { + struct { + unsigned char max_button_count:3; + unsigned char f1a_query0_b3__4:2; + unsigned char has_query4:1; + unsigned char has_query3:1; + unsigned char has_query2:1; + unsigned char has_general_control:1; + unsigned char has_interrupt_enable:1; + unsigned char has_multibutton_select:1; + unsigned char has_tx_rx_map:1; + unsigned char has_perbutton_threshold:1; + unsigned char has_release_threshold:1; + unsigned char has_strongestbtn_hysteresis:1; + unsigned char has_filter_strength:1; + } __packed; + unsigned char data[2]; + }; +}; + +struct synaptics_rmi4_f1a_query_4 { + union { + struct { + unsigned char has_ctrl19:1; + unsigned char f1a_query4_b1__4:4; + unsigned char has_ctrl24:1; + unsigned char f1a_query4_b6__7:2; + } __packed; + unsigned char data[1]; + }; +}; + +struct synaptics_rmi4_f1a_control_0 { + union { + struct { + unsigned char multibutton_report:2; + unsigned char filter_mode:2; + unsigned char reserved:4; + } __packed; + unsigned char data[1]; + }; +}; + +struct synaptics_rmi4_f1a_control { + struct synaptics_rmi4_f1a_control_0 general_control; + unsigned char button_int_enable; + unsigned char multi_button; + unsigned char *txrx_map; + unsigned char *button_threshold; + unsigned char button_release_threshold; + unsigned char strongest_button_hysteresis; + unsigned char filter_strength; +}; + +struct synaptics_rmi4_f1a_handle { + int button_bitmask_size; + unsigned char max_count; + unsigned char valid_button_count; + unsigned char *button_data_buffer; + unsigned char *button_map; + struct synaptics_rmi4_f1a_query button_query; + struct synaptics_rmi4_f1a_control button_control; +}; + +struct synaptics_rmi4_exp_fhandler { + struct synaptics_rmi4_exp_fn *exp_fn; + bool insert; + bool remove; + struct list_head link; +}; + +struct synaptics_rmi4_exp_fn_data { + bool initialized; + bool queue_work; + struct mutex mutex; + struct list_head list; + struct delayed_work work; + struct workqueue_struct *workqueue; + struct synaptics_rmi4_data *rmi4_data; +}; + +static struct synaptics_rmi4_exp_fn_data exp_data; + +static struct synaptics_dsx_button_map *vir_button_map; + +#ifdef USE_DATA_SERVER +static pid_t synad_pid; +static struct task_struct *synad_task; +static struct siginfo interrupt_signal; +#endif + +static struct device_attribute attrs[] = { + __ATTR(reset, 0220, + synaptics_rmi4_show_error, + synaptics_rmi4_f01_reset_store), + __ATTR(productinfo, 0444, + synaptics_rmi4_f01_productinfo_show, + synaptics_rmi4_store_error), + __ATTR(buildid, 0444, + synaptics_rmi4_f01_buildid_show, + synaptics_rmi4_store_error), + __ATTR(flashprog, 0444, + synaptics_rmi4_f01_flashprog_show, + synaptics_rmi4_store_error), + __ATTR(0dbutton, 0664, + synaptics_rmi4_0dbutton_show, + synaptics_rmi4_0dbutton_store), + __ATTR(suspend, 0220, + synaptics_rmi4_show_error, + synaptics_rmi4_suspend_store), + __ATTR(wake_gesture, 0664, + synaptics_rmi4_wake_gesture_show, + synaptics_rmi4_wake_gesture_store), +#ifdef USE_DATA_SERVER + __ATTR(synad_pid, 0220, + synaptics_rmi4_show_error, + synaptics_rmi4_synad_pid_store), +#endif +}; + +static struct kobj_attribute virtual_key_map_attr = { + .attr = { + .name = VIRTUAL_KEY_MAP_FILE_NAME, + .mode = 0444, + }, + .show = synaptics_rmi4_virtual_key_map_show, +}; + +static ssize_t synaptics_rmi4_f01_reset_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned int reset; + struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); + + if (kstrtouint(buf, 10, &reset) != 1) + return -EINVAL; + + if (reset != 1) + return -EINVAL; + + retval = synaptics_rmi4_reset_device(rmi4_data, false); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to issue reset command, error = %d\n", + __func__, retval); + return retval; + } + + return count; +} + +static ssize_t synaptics_rmi4_f01_productinfo_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "0x%02x 0x%02x\n", + (rmi4_data->rmi4_mod_info.product_info[0]), + (rmi4_data->rmi4_mod_info.product_info[1])); +} + +static ssize_t synaptics_rmi4_f01_buildid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%u\n", + rmi4_data->firmware_id); +} + +static ssize_t synaptics_rmi4_f01_flashprog_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + struct synaptics_rmi4_f01_device_status device_status; + struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); + + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_data_base_addr, + device_status.data, + sizeof(device_status.data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read device status, error = %d\n", + __func__, retval); + return retval; + } + + return snprintf(buf, PAGE_SIZE, "%u\n", + device_status.flash_prog); +} + +static ssize_t synaptics_rmi4_0dbutton_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%u\n", + rmi4_data->button_0d_enabled); +} + +static ssize_t synaptics_rmi4_0dbutton_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned int input; + unsigned char ii; + unsigned char intr_enable; + struct synaptics_rmi4_fn *fhandler; + struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); + struct synaptics_rmi4_device_info *rmi; + + rmi = &(rmi4_data->rmi4_mod_info); + + if (kstrtouint(buf, 10, &input) != 1) + return -EINVAL; + + input = input > 0 ? 1 : 0; + + if (rmi4_data->button_0d_enabled == input) + return count; + + if (list_empty(&rmi->support_fn_list)) + return -ENODEV; + + list_for_each_entry(fhandler, &rmi->support_fn_list, link) { + if (fhandler->fn_number == SYNAPTICS_RMI4_F1A) { + ii = fhandler->intr_reg_num; + + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_ctrl_base_addr + 1 + ii, + &intr_enable, + sizeof(intr_enable)); + if (retval < 0) + return retval; + + if (input == 1) + intr_enable |= fhandler->intr_mask; + else + intr_enable &= ~fhandler->intr_mask; + + retval = synaptics_rmi4_reg_write(rmi4_data, + rmi4_data->f01_ctrl_base_addr + 1 + ii, + &intr_enable, + sizeof(intr_enable)); + if (retval < 0) + return retval; + } + } + + rmi4_data->button_0d_enabled = input; + + return count; +} + +static ssize_t synaptics_rmi4_suspend_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int input; + + if (kstrtouint(buf, 10, &input) != 1) + return -EINVAL; + + if (input == 1) + synaptics_rmi4_suspend(dev); + else if (input == 0) + synaptics_rmi4_resume(dev); + else + return -EINVAL; + + return count; +} + +static ssize_t synaptics_rmi4_wake_gesture_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%u\n", + rmi4_data->enable_wakeup_gesture); +} + +static ssize_t synaptics_rmi4_wake_gesture_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int input; + struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); + + if (kstrtouint(buf, 10, &input) != 1) + return -EINVAL; + + input = input > 0 ? 1 : 0; + + if (rmi4_data->f11_wakeup_gesture || rmi4_data->f12_wakeup_gesture) + rmi4_data->enable_wakeup_gesture = input; + + return count; +} + +#ifdef USE_DATA_SERVER +static ssize_t synaptics_rmi4_synad_pid_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int input; + + if (kstrtouint(buf, 10, &input) != 1) + return -EINVAL; + + synad_pid = input; + + if (synad_pid) { + synad_task = pid_task(find_vpid(synad_pid), PIDTYPE_PID); + if (!synad_task) + return -EINVAL; + } + + return count; +} +#endif + +static ssize_t synaptics_rmi4_virtual_key_map_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int ii; + int cnt; + int count = 0; + + for (ii = 0; ii < vir_button_map->nbuttons; ii++) { + cnt = snprintf(buf, PAGE_SIZE - count, "0x01:%d:%d:%d:%d:%d\n", + vir_button_map->map[ii * 5 + 0], + vir_button_map->map[ii * 5 + 1], + vir_button_map->map[ii * 5 + 2], + vir_button_map->map[ii * 5 + 3], + vir_button_map->map[ii * 5 + 4]); + buf += cnt; + count += cnt; + } + + return count; +} + +static int synaptics_rmi4_f11_wg(struct synaptics_rmi4_data *rmi4_data, + bool enable) +{ + int retval; + unsigned char reporting_control; + struct synaptics_rmi4_fn *fhandler; + struct synaptics_rmi4_device_info *rmi; + + rmi = &(rmi4_data->rmi4_mod_info); + + list_for_each_entry(fhandler, &rmi->support_fn_list, link) { + if (fhandler->fn_number == SYNAPTICS_RMI4_F11) + break; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.ctrl_base, + &reporting_control, + sizeof(reporting_control)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to change reporting mode\n", + __func__); + return retval; + } + + reporting_control = (reporting_control & ~MASK_3BIT); + if (enable) + reporting_control |= F11_WAKEUP_GESTURE_MODE; + else + reporting_control |= F11_CONTINUOUS_MODE; + + retval = synaptics_rmi4_reg_write(rmi4_data, + fhandler->full_addr.ctrl_base, + &reporting_control, + sizeof(reporting_control)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to change reporting mode\n", + __func__); + return retval; + } + + return retval; +} + +static int synaptics_rmi4_f12_wg(struct synaptics_rmi4_data *rmi4_data, + bool enable) +{ + int retval; + unsigned char offset; + unsigned char reporting_control[3]; + struct synaptics_rmi4_f12_extra_data *extra_data; + struct synaptics_rmi4_fn *fhandler; + struct synaptics_rmi4_device_info *rmi; + + rmi = &(rmi4_data->rmi4_mod_info); + + list_for_each_entry(fhandler, &rmi->support_fn_list, link) { + if (fhandler->fn_number == SYNAPTICS_RMI4_F12) + break; + } + + extra_data = (struct synaptics_rmi4_f12_extra_data *)fhandler->extra; + offset = extra_data->ctrl20_offset; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.ctrl_base + offset, + reporting_control, + sizeof(reporting_control)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to change reporting mode\n", + __func__); + return retval; + } + + if (enable) + reporting_control[rmi4_data->set_wakeup_gesture] = + F12_WAKEUP_GESTURE_MODE; + else + reporting_control[rmi4_data->set_wakeup_gesture] = + F12_CONTINUOUS_MODE; + + retval = synaptics_rmi4_reg_write(rmi4_data, + fhandler->full_addr.ctrl_base + offset, + reporting_control, + sizeof(reporting_control)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to change reporting mode\n", + __func__); + return retval; + } + + return retval; +} + +static void synaptics_rmi4_wakeup_gesture(struct synaptics_rmi4_data *rmi4_data, + bool enable) +{ + if (rmi4_data->f11_wakeup_gesture) + synaptics_rmi4_f11_wg(rmi4_data, enable); + else if (rmi4_data->f12_wakeup_gesture) + synaptics_rmi4_f12_wg(rmi4_data, enable); +} + +static int synaptics_rmi4_f11_abs_report(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn *fhandler) +{ + int retval; + unsigned char touch_count = 0; /* number of touch points */ + unsigned char reg_index; + unsigned char finger; + unsigned char fingers_supported; + unsigned char num_of_finger_status_regs; + unsigned char finger_shift; + unsigned char finger_status; + unsigned char finger_status_reg[3]; + unsigned char detected_gestures; + unsigned short data_addr; + unsigned short data_offset; + int x; + int y; + int wx; + int wy; + int temp; + struct synaptics_rmi4_f11_data_1_5 data; + struct synaptics_rmi4_f11_extra_data *extra_data; + + /* + * The number of finger status registers is determined by the + * maximum number of fingers supported - 2 bits per finger. So + * the number of finger status registers to read is: + * register_count = ceil(max_num_of_fingers / 4) + */ + fingers_supported = fhandler->num_of_data_points; + num_of_finger_status_regs = (fingers_supported + 3) / 4; + data_addr = fhandler->full_addr.data_base; + + extra_data = (struct synaptics_rmi4_f11_extra_data *)fhandler->extra; + + if (rmi4_data->suspend && rmi4_data->enable_wakeup_gesture) { + retval = synaptics_rmi4_reg_read(rmi4_data, + data_addr + extra_data->data38_offset, + &detected_gestures, + sizeof(detected_gestures)); + if (retval < 0) + return 0; + + if (detected_gestures) { + input_report_key(rmi4_data->input_dev, KEY_WAKEUP, 1); + input_sync(rmi4_data->input_dev); + input_report_key(rmi4_data->input_dev, KEY_WAKEUP, 0); + input_sync(rmi4_data->input_dev); + rmi4_data->suspend = false; + } +/* synaptics_rmi4_wakeup_gesture(rmi4_data, false); */ + return 0; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + data_addr, + finger_status_reg, + num_of_finger_status_regs); + if (retval < 0) + return 0; + + mutex_lock(&(rmi4_data->rmi4_report_mutex)); + + for (finger = 0; finger < fingers_supported; finger++) { + reg_index = finger / 4; + finger_shift = (finger % 4) * 2; + finger_status = (finger_status_reg[reg_index] >> finger_shift) + & MASK_2BIT; + + /* + * Each 2-bit finger status field represents the following: + * 00 = finger not present + * 01 = finger present and data accurate + * 10 = finger present but data may be inaccurate + * 11 = reserved + */ +#ifdef TYPE_B_PROTOCOL + input_mt_slot(rmi4_data->input_dev, finger); + input_mt_report_slot_state(rmi4_data->input_dev, + MT_TOOL_FINGER, finger_status); +#endif + + if (finger_status) { + data_offset = data_addr + + num_of_finger_status_regs + + (finger * sizeof(data.data)); + retval = synaptics_rmi4_reg_read(rmi4_data, + data_offset, + data.data, + sizeof(data.data)); + if (retval < 0) { + touch_count = 0; + goto exit; + } + + x = (data.x_position_11_4 << 4) | data.x_position_3_0; + y = (data.y_position_11_4 << 4) | data.y_position_3_0; + wx = data.wx; + wy = data.wy; + + if (rmi4_data->hw_if->board_data->swap_axes) { + temp = x; + x = y; + y = temp; + temp = wx; + wx = wy; + wy = temp; + } + + if (rmi4_data->hw_if->board_data->x_flip) + x = rmi4_data->sensor_max_x - x; + if (rmi4_data->hw_if->board_data->y_flip) + y = rmi4_data->sensor_max_y - y; + + input_report_key(rmi4_data->input_dev, + BTN_TOUCH, 1); + input_report_key(rmi4_data->input_dev, + BTN_TOOL_FINGER, 1); + input_report_abs(rmi4_data->input_dev, + ABS_MT_POSITION_X, x); + input_report_abs(rmi4_data->input_dev, + ABS_MT_POSITION_Y, y); +#ifdef REPORT_2D_W + input_report_abs(rmi4_data->input_dev, + ABS_MT_TOUCH_MAJOR, max(wx, wy)); + input_report_abs(rmi4_data->input_dev, + ABS_MT_TOUCH_MINOR, min(wx, wy)); +#endif +#ifndef TYPE_B_PROTOCOL + input_mt_sync(rmi4_data->input_dev); +#endif + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Finger %d: status = 0x%02x, x = %d, y = %d, wx = %d, wy = %d\n", + __func__, finger, + finger_status, + x, y, wx, wy); + + touch_count++; + } + } + + if (touch_count == 0) { + input_report_key(rmi4_data->input_dev, + BTN_TOUCH, 0); + input_report_key(rmi4_data->input_dev, + BTN_TOOL_FINGER, 0); +#ifndef TYPE_B_PROTOCOL + input_mt_sync(rmi4_data->input_dev); +#endif + } + + input_sync(rmi4_data->input_dev); + +exit: + mutex_unlock(&(rmi4_data->rmi4_report_mutex)); + + return touch_count; +} + +static int synaptics_rmi4_f12_abs_report(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn *fhandler) +{ + int retval; + unsigned char touch_count = 0; /* number of touch points */ + unsigned char index; + unsigned char finger; + unsigned char fingers_to_process; + unsigned char finger_status; + unsigned char size_of_2d_data; + unsigned char gesture_type; + unsigned short data_addr; + int x; + int y; + int wx; + int wy; + int temp; +#if defined(REPORT_2D_PRESSURE) || defined(F51_DISCRETE_FORCE) + int pressure; +#endif +#ifdef REPORT_2D_PRESSURE + unsigned char f_fingers; + unsigned char f_lsb; + unsigned char f_msb; + unsigned char *f_data; +#endif +#ifdef F51_DISCRETE_FORCE + unsigned char force_level; +#endif + struct synaptics_rmi4_f12_extra_data *extra_data; + struct synaptics_rmi4_f12_finger_data *data; + struct synaptics_rmi4_f12_finger_data *finger_data; + static unsigned char finger_presence; + static unsigned char stylus_presence; +#ifdef F12_DATA_15_WORKAROUND + static unsigned char objects_already_present; +#endif + + fingers_to_process = fhandler->num_of_data_points; + data_addr = fhandler->full_addr.data_base; + extra_data = (struct synaptics_rmi4_f12_extra_data *)fhandler->extra; + size_of_2d_data = sizeof(struct synaptics_rmi4_f12_finger_data); + + if (rmi4_data->suspend && rmi4_data->enable_wakeup_gesture) { + retval = synaptics_rmi4_reg_read(rmi4_data, + data_addr + extra_data->data4_offset, + rmi4_data->gesture_detection, + sizeof(rmi4_data->gesture_detection)); + if (retval < 0) + return 0; + + gesture_type = rmi4_data->gesture_detection[0]; + + if (gesture_type && gesture_type != F12_UDG_DETECT) { + input_report_key(rmi4_data->input_dev, KEY_WAKEUP, 1); + input_sync(rmi4_data->input_dev); + input_report_key(rmi4_data->input_dev, KEY_WAKEUP, 0); + input_sync(rmi4_data->input_dev); + /* synaptics_rmi4_wakeup_gesture(rmi4_data, false); */ + /* rmi4_data->suspend = false; */ + } + + return 0; + } + + /* Determine the total number of fingers to process */ + if (extra_data->data15_size) { + retval = synaptics_rmi4_reg_read(rmi4_data, + data_addr + extra_data->data15_offset, + extra_data->data15_data, + extra_data->data15_size); + if (retval < 0) + return 0; + + /* Start checking from the highest bit */ + index = extra_data->data15_size - 1; /* Highest byte */ + finger = (fingers_to_process - 1) % 8; /* Highest bit */ + do { + if (extra_data->data15_data[index] & (1 << finger)) + break; + + if (finger) { + finger--; + } else if (index > 0) { + index--; /* Move to the next lower byte */ + finger = 7; + } + + fingers_to_process--; + } while (fingers_to_process); + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Number of fingers to process = %d\n", + __func__, fingers_to_process); + } + +#ifdef F12_DATA_15_WORKAROUND + fingers_to_process = max(fingers_to_process, objects_already_present); +#endif + + if (!fingers_to_process) { + synaptics_rmi4_free_fingers(rmi4_data); + finger_presence = 0; + stylus_presence = 0; + return 0; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + data_addr + extra_data->data1_offset, + (unsigned char *)fhandler->data, + fingers_to_process * size_of_2d_data); + if (retval < 0) + return 0; + + data = (struct synaptics_rmi4_f12_finger_data *)fhandler->data; + +#ifdef REPORT_2D_PRESSURE + if (rmi4_data->report_pressure) { + retval = synaptics_rmi4_reg_read(rmi4_data, + data_addr + extra_data->data29_offset, + extra_data->data29_data, + extra_data->data29_size); + if (retval < 0) + return 0; + } +#endif + + mutex_lock(&(rmi4_data->rmi4_report_mutex)); + + for (finger = 0; finger < fingers_to_process; finger++) { + finger_data = data + finger; + finger_status = finger_data->object_type_and_status; + +#ifdef F12_DATA_15_WORKAROUND + objects_already_present = finger + 1; +#endif + + x = (finger_data->x_msb << 8) | (finger_data->x_lsb); + y = (finger_data->y_msb << 8) | (finger_data->y_lsb); +#ifdef REPORT_2D_W + wx = finger_data->wx; + wy = finger_data->wy; +#endif + + if (rmi4_data->hw_if->board_data->swap_axes) { + temp = x; + x = y; + y = temp; + temp = wx; + wx = wy; + wy = temp; + } + + if (rmi4_data->hw_if->board_data->x_flip) + x = rmi4_data->sensor_max_x - x; + if (rmi4_data->hw_if->board_data->y_flip) + y = rmi4_data->sensor_max_y - y; + + switch (finger_status) { + case F12_FINGER_STATUS: + case F12_GLOVED_FINGER_STATUS: + /* Stylus has priority over fingers */ + if (stylus_presence) + break; +#ifdef TYPE_B_PROTOCOL + input_mt_slot(rmi4_data->input_dev, finger); + input_mt_report_slot_state(rmi4_data->input_dev, + MT_TOOL_FINGER, 1); +#endif + + input_report_key(rmi4_data->input_dev, + BTN_TOUCH, 1); + input_report_key(rmi4_data->input_dev, + BTN_TOOL_FINGER, 1); + input_report_abs(rmi4_data->input_dev, + ABS_MT_POSITION_X, x); + input_report_abs(rmi4_data->input_dev, + ABS_MT_POSITION_Y, y); +#ifdef REPORT_2D_W + if (rmi4_data->wedge_sensor) { + input_report_abs(rmi4_data->input_dev, + ABS_MT_TOUCH_MAJOR, wx); + input_report_abs(rmi4_data->input_dev, + ABS_MT_TOUCH_MINOR, wx); + } else { + input_report_abs(rmi4_data->input_dev, + ABS_MT_TOUCH_MAJOR, + max(wx, wy)); + input_report_abs(rmi4_data->input_dev, + ABS_MT_TOUCH_MINOR, + min(wx, wy)); + } +#endif +#ifdef REPORT_2D_PRESSURE + if (rmi4_data->report_pressure) { + f_fingers = extra_data->data29_size / 2; + f_data = extra_data->data29_data; + if (finger + 1 > f_fingers) { + pressure = 1; + } else { + f_lsb = finger * 2; + f_msb = finger * 2 + 1; + pressure = (int)f_data[f_lsb] << 0 | + (int)f_data[f_msb] << 8; + } + pressure = pressure > 0 ? pressure : 1; + if (pressure > rmi4_data->force_max) + pressure = rmi4_data->force_max; + input_report_abs(rmi4_data->input_dev, + ABS_MT_PRESSURE, pressure); + } +#elif defined(F51_DISCRETE_FORCE) + if (finger == 0) { + retval = synaptics_rmi4_reg_read(rmi4_data, + FORCE_LEVEL_ADDR, + &force_level, + sizeof(force_level)); + if (retval < 0) + return 0; + pressure = force_level > 0 ? force_level : 1; + } else { + pressure = 1; + } + input_report_abs(rmi4_data->input_dev, + ABS_MT_PRESSURE, pressure); +#endif +#ifndef TYPE_B_PROTOCOL + input_mt_sync(rmi4_data->input_dev); +#endif + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Finger %d: status = 0x%02x, x = %d, y = %d, wx = %d, wy = %d\n", + __func__, finger, + finger_status, + x, y, wx, wy); + + finger_presence = 1; + touch_count++; + break; + case F12_PALM_STATUS: + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Finger %d: x = %d, y = %d, wx = %d, wy = %d\n", + __func__, finger, + x, y, wx, wy); + break; + case F12_STYLUS_STATUS: + case F12_ERASER_STATUS: + if (!rmi4_data->stylus_enable) + break; + /* Stylus has priority over fingers */ + if (finger_presence) { + mutex_unlock(&(rmi4_data->rmi4_report_mutex)); + synaptics_rmi4_free_fingers(rmi4_data); + mutex_lock(&(rmi4_data->rmi4_report_mutex)); + finger_presence = 0; + } + if (stylus_presence) {/* Allow one stylus at a timee */ + if (finger + 1 != stylus_presence) + break; + } + input_report_key(rmi4_data->stylus_dev, + BTN_TOUCH, 1); + if (finger_status == F12_STYLUS_STATUS) { + input_report_key(rmi4_data->stylus_dev, + BTN_TOOL_PEN, 1); + } else { + input_report_key(rmi4_data->stylus_dev, + BTN_TOOL_RUBBER, 1); + } + input_report_abs(rmi4_data->stylus_dev, + ABS_X, x); + input_report_abs(rmi4_data->stylus_dev, + ABS_Y, y); + input_sync(rmi4_data->stylus_dev); + + stylus_presence = finger + 1; + touch_count++; + break; + default: +#ifdef TYPE_B_PROTOCOL + input_mt_slot(rmi4_data->input_dev, finger); + input_mt_report_slot_state(rmi4_data->input_dev, + MT_TOOL_FINGER, 0); +#endif + break; + } + } + + if (touch_count == 0) { + finger_presence = 0; +#ifdef F12_DATA_15_WORKAROUND + objects_already_present = 0; +#endif + input_report_key(rmi4_data->input_dev, + BTN_TOUCH, 0); + input_report_key(rmi4_data->input_dev, + BTN_TOOL_FINGER, 0); +#ifndef TYPE_B_PROTOCOL + input_mt_sync(rmi4_data->input_dev); +#endif + + if (rmi4_data->stylus_enable) { + stylus_presence = 0; + input_report_key(rmi4_data->stylus_dev, + BTN_TOUCH, 0); + input_report_key(rmi4_data->stylus_dev, + BTN_TOOL_PEN, 0); + if (rmi4_data->eraser_enable) { + input_report_key(rmi4_data->stylus_dev, + BTN_TOOL_RUBBER, 0); + } + input_sync(rmi4_data->stylus_dev); + } + } + + input_sync(rmi4_data->input_dev); + + mutex_unlock(&(rmi4_data->rmi4_report_mutex)); + + return touch_count; +} + +static int synaptics_rmi4_f1a_report(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn *fhandler) +{ + int retval; + unsigned char touch_count = 0; + unsigned char button; + unsigned char index; + unsigned char shift; + unsigned char status; + unsigned char *data; + unsigned short data_addr = fhandler->full_addr.data_base; + struct synaptics_rmi4_f1a_handle *f1a = fhandler->data; + static unsigned char do_once = 1; + static bool current_status[MAX_NUMBER_OF_BUTTONS]; +#ifdef NO_0D_WHILE_2D + static bool before_2d_status[MAX_NUMBER_OF_BUTTONS]; + static bool while_2d_status[MAX_NUMBER_OF_BUTTONS]; +#endif + + if (do_once) { + memset(current_status, 0, sizeof(current_status)); +#ifdef NO_0D_WHILE_2D + memset(before_2d_status, 0, sizeof(before_2d_status)); + memset(while_2d_status, 0, sizeof(while_2d_status)); +#endif + do_once = 0; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + data_addr, + f1a->button_data_buffer, + f1a->button_bitmask_size); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read button data registers\n", + __func__); + return retval; + } + + data = f1a->button_data_buffer; + + mutex_lock(&(rmi4_data->rmi4_report_mutex)); + + for (button = 0; button < f1a->valid_button_count; button++) { + index = button / 8; + shift = button % 8; + status = ((data[index] >> shift) & MASK_1BIT); + + if (current_status[button] == status) + continue; + else + current_status[button] = status; + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Button %d (code %d) ->%d\n", + __func__, button, + f1a->button_map[button], + status); +#ifdef NO_0D_WHILE_2D + if (rmi4_data->fingers_on_2d == false) { + if (status == 1) { + before_2d_status[button] = 1; + } else { + if (while_2d_status[button] == 1) { + while_2d_status[button] = 0; + continue; + } else { + before_2d_status[button] = 0; + } + } + touch_count++; + input_report_key(rmi4_data->input_dev, + f1a->button_map[button], + status); + } else { + if (before_2d_status[button] == 1) { + before_2d_status[button] = 0; + touch_count++; + input_report_key(rmi4_data->input_dev, + f1a->button_map[button], + status); + } else { + if (status == 1) + while_2d_status[button] = 1; + else + while_2d_status[button] = 0; + } + } +#else + touch_count++; + input_report_key(rmi4_data->input_dev, + f1a->button_map[button], + status); +#endif + } + + if (touch_count) + input_sync(rmi4_data->input_dev); + + mutex_unlock(&(rmi4_data->rmi4_report_mutex)); + + return retval; +} + +static void synaptics_rmi4_report_touch(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn *fhandler) +{ + unsigned char touch_count_2d; + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Function %02x reporting\n", + __func__, fhandler->fn_number); + + switch (fhandler->fn_number) { + case SYNAPTICS_RMI4_F11: + touch_count_2d = synaptics_rmi4_f11_abs_report(rmi4_data, + fhandler); + + if (touch_count_2d) + rmi4_data->fingers_on_2d = true; + else + rmi4_data->fingers_on_2d = false; + break; + case SYNAPTICS_RMI4_F12: + touch_count_2d = synaptics_rmi4_f12_abs_report(rmi4_data, + fhandler); + + if (touch_count_2d) + rmi4_data->fingers_on_2d = true; + else + rmi4_data->fingers_on_2d = false; + break; + case SYNAPTICS_RMI4_F1A: + synaptics_rmi4_f1a_report(rmi4_data, fhandler); + break; +#ifdef USE_DATA_SERVER + case SYNAPTICS_RMI4_F21: + if (synad_pid) + send_sig_info(SIGIO, &interrupt_signal, synad_task); + break; +#endif + default: + break; + } +} + +static int synaptics_rmi4_sensor_report(struct synaptics_rmi4_data *rmi4_data, + bool report) +{ + int retval; + unsigned char data[MAX_INTR_REGISTERS + 1]; + unsigned char *intr = &data[1]; + bool was_in_bl_mode; + struct synaptics_rmi4_f01_device_status status; + struct synaptics_rmi4_fn *fhandler; + struct synaptics_rmi4_exp_fhandler *exp_fhandler; + struct synaptics_rmi4_device_info *rmi; + + rmi = &(rmi4_data->rmi4_mod_info); + + /* + * Get interrupt status information from F01 Data1 register to + * determine the source(s) that are flagging the interrupt. + */ + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_data_base_addr, + data, + rmi4_data->num_of_intr_regs + 1); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read interrupt status\n", + __func__); + return retval; + } + + status.data[0] = data[0]; + if (status.status_code == STATUS_CRC_IN_PROGRESS) { + retval = synaptics_rmi4_check_status(rmi4_data, + &was_in_bl_mode); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to check status\n", + __func__); + return retval; + } + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_data_base_addr, + status.data, + sizeof(status.data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read device status\n", + __func__); + return retval; + } + } + if (status.unconfigured && !status.flash_prog) { + pr_notice("%s: spontaneous reset detected\n", __func__); + retval = synaptics_rmi4_reinit_device(rmi4_data); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to reinit device\n", + __func__); + } + } + + if (!report) + return retval; + + /* + * Traverse the function handler list and service the source(s) + * of the interrupt accordingly. + */ + if (!list_empty(&rmi->support_fn_list)) { + list_for_each_entry(fhandler, &rmi->support_fn_list, link) { + if (fhandler->num_of_data_sources) { + if (fhandler->intr_mask & + intr[fhandler->intr_reg_num]) { + synaptics_rmi4_report_touch(rmi4_data, + fhandler); + } + } + } + } + + mutex_lock(&exp_data.mutex); + if (!list_empty(&exp_data.list)) { + list_for_each_entry(exp_fhandler, &exp_data.list, link) { + if (!exp_fhandler->insert && + !exp_fhandler->remove && + (exp_fhandler->exp_fn->attn != NULL)) + exp_fhandler->exp_fn->attn(rmi4_data, intr[0]); + } + } + mutex_unlock(&exp_data.mutex); + + return retval; +} + +static irqreturn_t synaptics_rmi4_irq(int irq, void *data) +{ + struct synaptics_rmi4_data *rmi4_data = data; + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + if (gpio_get_value(bdata->irq_gpio) != bdata->irq_on_state) + goto exit; + + synaptics_rmi4_sensor_report(rmi4_data, true); + +exit: + return IRQ_HANDLED; +} + +static int synaptics_rmi4_int_enable(struct synaptics_rmi4_data *rmi4_data, + bool enable) +{ + int retval = 0; + unsigned char ii; + unsigned char zero = 0x00; + unsigned char *intr_mask; + unsigned short intr_addr; + + intr_mask = rmi4_data->intr_mask; + + for (ii = 0; ii < rmi4_data->num_of_intr_regs; ii++) { + if (intr_mask[ii] != 0x00) { + intr_addr = rmi4_data->f01_ctrl_base_addr + 1 + ii; + if (enable) { + retval = synaptics_rmi4_reg_write(rmi4_data, + intr_addr, + &(intr_mask[ii]), + sizeof(intr_mask[ii])); + if (retval < 0) + return retval; + } else { + retval = synaptics_rmi4_reg_write(rmi4_data, + intr_addr, + &zero, + sizeof(zero)); + if (retval < 0) + return retval; + } + } + } + + return retval; +} + +static int synaptics_rmi4_irq_enable(struct synaptics_rmi4_data *rmi4_data, + bool enable, bool attn_only) +{ + int retval = 0; + unsigned char data[MAX_INTR_REGISTERS]; + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + mutex_lock(&(rmi4_data->rmi4_irq_enable_mutex)); + + if (attn_only) { + retval = synaptics_rmi4_int_enable(rmi4_data, enable); + goto exit; + } + + if (enable) { + if (rmi4_data->irq_enabled) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Interrupt already enabled\n", + __func__); + goto exit; + } + + retval = synaptics_rmi4_int_enable(rmi4_data, false); + if (retval < 0) + goto exit; + + /* Clear interrupts */ + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_data_base_addr + 1, + data, + rmi4_data->num_of_intr_regs); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read interrupt status\n", + __func__); + goto exit; + } + + retval = request_threaded_irq(rmi4_data->irq, NULL, + synaptics_rmi4_irq, bdata->irq_flags, + PLATFORM_DRIVER_NAME, rmi4_data); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create irq thread\n", + __func__); + goto exit; + } + + retval = synaptics_rmi4_int_enable(rmi4_data, true); + if (retval < 0) + goto exit; + + rmi4_data->irq_enabled = true; + } else { + if (rmi4_data->irq_enabled) { + disable_irq(rmi4_data->irq); + free_irq(rmi4_data->irq, rmi4_data); + rmi4_data->irq_enabled = false; + } + } + +exit: + mutex_unlock(&(rmi4_data->rmi4_irq_enable_mutex)); + + return retval; +} + +static void synaptics_rmi4_set_intr_mask(struct synaptics_rmi4_fn *fhandler, + struct synaptics_rmi4_fn_desc *fd, + unsigned int intr_count) +{ + unsigned char ii; + unsigned char intr_offset; + + fhandler->intr_reg_num = (intr_count + 7) / 8; + if (fhandler->intr_reg_num != 0) + fhandler->intr_reg_num -= 1; + + /* Set an enable bit for each data source */ + intr_offset = intr_count % 8; + fhandler->intr_mask = 0; + for (ii = intr_offset; + ii < (fd->intr_src_count + intr_offset); + ii++) + fhandler->intr_mask |= 1 << ii; +} + +static int synaptics_rmi4_f01_init(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn *fhandler, + struct synaptics_rmi4_fn_desc *fd, + unsigned int intr_count) +{ + fhandler->fn_number = fd->fn_number; + fhandler->num_of_data_sources = fd->intr_src_count; + fhandler->data = NULL; + fhandler->extra = NULL; + + synaptics_rmi4_set_intr_mask(fhandler, fd, intr_count); + + rmi4_data->f01_query_base_addr = fd->query_base_addr; + rmi4_data->f01_ctrl_base_addr = fd->ctrl_base_addr; + rmi4_data->f01_data_base_addr = fd->data_base_addr; + rmi4_data->f01_cmd_base_addr = fd->cmd_base_addr; + + return 0; +} + +static int synaptics_rmi4_f11_init(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn *fhandler, + struct synaptics_rmi4_fn_desc *fd, + unsigned int intr_count) +{ + int retval; + int temp; + unsigned char offset; + unsigned char fingers_supported; + struct synaptics_rmi4_f11_extra_data *extra_data; + struct synaptics_rmi4_f11_query_0_5 query_0_5; + struct synaptics_rmi4_f11_query_7_8 query_7_8; + struct synaptics_rmi4_f11_query_9 query_9; + struct synaptics_rmi4_f11_query_12 query_12; + struct synaptics_rmi4_f11_query_27 query_27; + struct synaptics_rmi4_f11_ctrl_6_9 control_6_9; + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + fhandler->fn_number = fd->fn_number; + fhandler->num_of_data_sources = fd->intr_src_count; + fhandler->extra = kmalloc(sizeof(*extra_data), GFP_KERNEL); + if (!fhandler->extra) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for fhandler->extra\n", + __func__); + return -ENOMEM; + } + extra_data = (struct synaptics_rmi4_f11_extra_data *)fhandler->extra; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.query_base, + query_0_5.data, + sizeof(query_0_5.data)); + if (retval < 0) + return retval; + + /* Maximum number of fingers supported */ + if (query_0_5.num_of_fingers <= 4) + fhandler->num_of_data_points = query_0_5.num_of_fingers + 1; + else if (query_0_5.num_of_fingers == 5) + fhandler->num_of_data_points = 10; + + rmi4_data->num_of_fingers = fhandler->num_of_data_points; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.ctrl_base + 6, + control_6_9.data, + sizeof(control_6_9.data)); + if (retval < 0) + return retval; + + /* Maximum x and y */ + rmi4_data->sensor_max_x = control_6_9.sensor_max_x_pos_7_0 | + (control_6_9.sensor_max_x_pos_11_8 << 8); + rmi4_data->sensor_max_y = control_6_9.sensor_max_y_pos_7_0 | + (control_6_9.sensor_max_y_pos_11_8 << 8); + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Function %02x max x = %d max y = %d\n", + __func__, fhandler->fn_number, + rmi4_data->sensor_max_x, + rmi4_data->sensor_max_y); + + rmi4_data->max_touch_width = MAX_F11_TOUCH_WIDTH; + + if (bdata->swap_axes) { + temp = rmi4_data->sensor_max_x; + rmi4_data->sensor_max_x = rmi4_data->sensor_max_y; + rmi4_data->sensor_max_y = temp; + } + + synaptics_rmi4_set_intr_mask(fhandler, fd, intr_count); + + fhandler->data = NULL; + + offset = sizeof(query_0_5.data); + + /* query 6 */ + if (query_0_5.has_rel) + offset += 1; + + /* queries 7 8 */ + if (query_0_5.has_gestures) { + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.query_base + offset, + query_7_8.data, + sizeof(query_7_8.data)); + if (retval < 0) + return retval; + + offset += sizeof(query_7_8.data); + } + + /* query 9 */ + if (query_0_5.has_query_9) { + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.query_base + offset, + query_9.data, + sizeof(query_9.data)); + if (retval < 0) + return retval; + + offset += sizeof(query_9.data); + } + + /* query 10 */ + if (query_0_5.has_gestures && query_7_8.has_touch_shapes) + offset += 1; + + /* query 11 */ + if (query_0_5.has_query_11) + offset += 1; + + /* query 12 */ + if (query_0_5.has_query_12) { + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.query_base + offset, + query_12.data, + sizeof(query_12.data)); + if (retval < 0) + return retval; + + offset += sizeof(query_12.data); + } + + /* query 13 */ + if (query_0_5.has_jitter_filter) + offset += 1; + + /* query 14 */ + if (query_0_5.has_query_12 && query_12.has_general_information_2) + offset += 1; + + /* queries 15 16 17 18 19 20 21 22 23 24 25 26*/ + if (query_0_5.has_query_12 && query_12.has_physical_properties) + offset += 12; + + /* query 27 */ + if (query_0_5.has_query_27) { + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.query_base + offset, + query_27.data, + sizeof(query_27.data)); + if (retval < 0) + return retval; + + rmi4_data->f11_wakeup_gesture = query_27.has_wakeup_gesture; + } + + if (!rmi4_data->f11_wakeup_gesture) + return retval; + + /* data 0 */ + fingers_supported = fhandler->num_of_data_points; + offset = (fingers_supported + 3) / 4; + + /* data 1 2 3 4 5 */ + offset += 5 * fingers_supported; + + /* data 6 7 */ + if (query_0_5.has_rel) + offset += 2 * fingers_supported; + + /* data 8 */ + if (query_0_5.has_gestures && query_7_8.data[0]) + offset += 1; + + /* data 9 */ + if (query_0_5.has_gestures && (query_7_8.data[0] || query_7_8.data[1])) + offset += 1; + + /* data 10 */ + if (query_0_5.has_gestures && + (query_7_8.has_pinch || query_7_8.has_flick)) + offset += 1; + + /* data 11 12 */ + if (query_0_5.has_gestures && + (query_7_8.has_flick || query_7_8.has_rotate)) + offset += 2; + + /* data 13 */ + if (query_0_5.has_gestures && query_7_8.has_touch_shapes) + offset += (fingers_supported + 3) / 4; + + /* data 14 15 */ + if (query_0_5.has_gestures && + (query_7_8.has_scroll_zones || + query_7_8.has_multi_finger_scroll || + query_7_8.has_chiral_scroll)) + offset += 2; + + /* data 16 17 */ + if (query_0_5.has_gestures && + (query_7_8.has_scroll_zones && + query_7_8.individual_scroll_zones)) + offset += 2; + + /* data 18 19 20 21 22 23 24 25 26 27 */ + if (query_0_5.has_query_9 && query_9.has_contact_geometry) + offset += 10 * fingers_supported; + + /* data 28 */ + if (query_0_5.has_bending_correction || + query_0_5.has_large_object_suppression) + offset += 1; + + /* data 29 30 31 */ + if (query_0_5.has_query_9 && query_9.has_pen_hover_discrimination) + offset += 3; + + /* data 32 */ + if (query_0_5.has_query_12 && + query_12.has_small_object_detection_tuning) + offset += 1; + + /* data 33 34 */ + if (query_0_5.has_query_27 && query_27.f11_query27_b0) + offset += 2; + + /* data 35 */ + if (query_0_5.has_query_12 && query_12.has_8bit_w) + offset += fingers_supported; + + /* data 36 */ + if (query_0_5.has_bending_correction) + offset += 1; + + /* data 37 */ + if (query_0_5.has_query_27 && query_27.has_data_37) + offset += 1; + + /* data 38 */ + if (query_0_5.has_query_27 && query_27.has_wakeup_gesture) + extra_data->data38_offset = offset; + + return retval; +} + +static int synaptics_rmi4_f12_set_enables(struct synaptics_rmi4_data *rmi4_data, + unsigned short ctrl28) +{ + int retval; + static unsigned short ctrl_28_address; + + if (ctrl28) + ctrl_28_address = ctrl28; + + retval = synaptics_rmi4_reg_write(rmi4_data, + ctrl_28_address, + &rmi4_data->report_enable, + sizeof(rmi4_data->report_enable)); + if (retval < 0) + return retval; + + return retval; +} + +static int synaptics_rmi4_f12_find_sub(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn *fhandler, + unsigned char *presence, unsigned char presence_size, + unsigned char structure_offset, unsigned char reg, + unsigned char sub) +{ + int retval; + unsigned char cnt; + unsigned char regnum; + unsigned char bitnum; + unsigned char p_index; + unsigned char s_index; + unsigned char offset; + unsigned char max_reg; + unsigned char *structure; + + max_reg = (presence_size - 1) * 8 - 1; + + if (reg > max_reg) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Register number (%d) over limit\n", + __func__, reg); + return -EINVAL; + } + + p_index = reg / 8 + 1; + bitnum = reg % 8; + if ((presence[p_index] & (1 << bitnum)) == 0x00) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Register %d is not present\n", + __func__, reg); + return -EINVAL; + } + + structure = kmalloc(presence[0], GFP_KERNEL); + if (!structure) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for structure register\n", + __func__); + return -ENOMEM; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.query_base + structure_offset, + structure, + presence[0]); + if (retval < 0) + goto exit; + + s_index = 0; + + for (regnum = 0; regnum < reg; regnum++) { + p_index = regnum / 8 + 1; + bitnum = regnum % 8; + if ((presence[p_index] & (1 << bitnum)) == 0x00) + continue; + + if (structure[s_index] == 0x00) + s_index += 3; + else + s_index++; + + while (structure[s_index] & ~MASK_7BIT) + s_index++; + + s_index++; + } + + cnt = 0; + s_index++; + offset = sub / 7; + bitnum = sub % 7; + + do { + if (cnt == offset) { + if (structure[s_index + cnt] & (1 << bitnum)) + retval = 1; + else + retval = 0; + goto exit; + } + cnt++; + } while (structure[s_index + cnt - 1] & ~MASK_7BIT); + + retval = 0; + +exit: + kfree(structure); + + return retval; +} + +static int synaptics_rmi4_f12_init(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn *fhandler, + struct synaptics_rmi4_fn_desc *fd, + unsigned int intr_count) +{ + int retval = 0; + int temp; + unsigned char subpacket; + unsigned char ctrl_23_size; + unsigned char size_of_2d_data; + unsigned char size_of_query5; + unsigned char size_of_query8; + unsigned char ctrl_8_offset; + unsigned char ctrl_20_offset; + unsigned char ctrl_23_offset; + unsigned char ctrl_28_offset; + unsigned char ctrl_31_offset; + unsigned char ctrl_58_offset; + unsigned char num_of_fingers; + struct synaptics_rmi4_f12_extra_data *extra_data; + struct synaptics_rmi4_f12_query_5 *query_5 = NULL; + struct synaptics_rmi4_f12_query_8 *query_8 = NULL; + struct synaptics_rmi4_f12_ctrl_8 *ctrl_8 = NULL; + struct synaptics_rmi4_f12_ctrl_23 *ctrl_23 = NULL; + struct synaptics_rmi4_f12_ctrl_31 *ctrl_31 = NULL; + struct synaptics_rmi4_f12_ctrl_58 *ctrl_58 = NULL; + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + fhandler->fn_number = fd->fn_number; + fhandler->num_of_data_sources = fd->intr_src_count; + fhandler->extra = kmalloc(sizeof(*extra_data), GFP_KERNEL); + if (!fhandler->extra) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for fhandler->extra\n", + __func__); + return -ENOMEM; + } + extra_data = (struct synaptics_rmi4_f12_extra_data *)fhandler->extra; + size_of_2d_data = sizeof(struct synaptics_rmi4_f12_finger_data); + + query_5 = kzalloc(sizeof(*query_5), GFP_KERNEL); + if (!query_5) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for query_5\n", + __func__); + retval = -ENOMEM; + goto exit; + } + + query_8 = kzalloc(sizeof(*query_8), GFP_KERNEL); + if (!query_8) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for query_8\n", + __func__); + retval = -ENOMEM; + goto exit; + } + + ctrl_8 = kzalloc(sizeof(*ctrl_8), GFP_KERNEL); + if (!ctrl_8) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for ctrl_8\n", + __func__); + retval = -ENOMEM; + goto exit; + } + + ctrl_23 = kzalloc(sizeof(*ctrl_23), GFP_KERNEL); + if (!ctrl_23) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for ctrl_23\n", + __func__); + retval = -ENOMEM; + goto exit; + } + + ctrl_31 = kzalloc(sizeof(*ctrl_31), GFP_KERNEL); + if (!ctrl_31) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for ctrl_31\n", + __func__); + retval = -ENOMEM; + goto exit; + } + + ctrl_58 = kzalloc(sizeof(*ctrl_58), GFP_KERNEL); + if (!ctrl_58) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for ctrl_58\n", + __func__); + retval = -ENOMEM; + goto exit; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.query_base + 4, + &size_of_query5, + sizeof(size_of_query5)); + if (retval < 0) + goto exit; + + if (size_of_query5 > sizeof(query_5->data)) + size_of_query5 = sizeof(query_5->data); + memset(query_5->data, 0x00, sizeof(query_5->data)); + + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.query_base + 5, + query_5->data, + size_of_query5); + if (retval < 0) + goto exit; + + ctrl_8_offset = query_5->ctrl0_is_present + + query_5->ctrl1_is_present + + query_5->ctrl2_is_present + + query_5->ctrl3_is_present + + query_5->ctrl4_is_present + + query_5->ctrl5_is_present + + query_5->ctrl6_is_present + + query_5->ctrl7_is_present; + + ctrl_20_offset = ctrl_8_offset + + query_5->ctrl8_is_present + + query_5->ctrl9_is_present + + query_5->ctrl10_is_present + + query_5->ctrl11_is_present + + query_5->ctrl12_is_present + + query_5->ctrl13_is_present + + query_5->ctrl14_is_present + + query_5->ctrl15_is_present + + query_5->ctrl16_is_present + + query_5->ctrl17_is_present + + query_5->ctrl18_is_present + + query_5->ctrl19_is_present; + + ctrl_23_offset = ctrl_20_offset + + query_5->ctrl20_is_present + + query_5->ctrl21_is_present + + query_5->ctrl22_is_present; + + ctrl_28_offset = ctrl_23_offset + + query_5->ctrl23_is_present + + query_5->ctrl24_is_present + + query_5->ctrl25_is_present + + query_5->ctrl26_is_present + + query_5->ctrl27_is_present; + + ctrl_31_offset = ctrl_28_offset + + query_5->ctrl28_is_present + + query_5->ctrl29_is_present + + query_5->ctrl30_is_present; + + ctrl_58_offset = ctrl_31_offset + + query_5->ctrl31_is_present + + query_5->ctrl32_is_present + + query_5->ctrl33_is_present + + query_5->ctrl34_is_present + + query_5->ctrl35_is_present + + query_5->ctrl36_is_present + + query_5->ctrl37_is_present + + query_5->ctrl38_is_present + + query_5->ctrl39_is_present + + query_5->ctrl40_is_present + + query_5->ctrl41_is_present + + query_5->ctrl42_is_present + + query_5->ctrl43_is_present + + query_5->ctrl44_is_present + + query_5->ctrl45_is_present + + query_5->ctrl46_is_present + + query_5->ctrl47_is_present + + query_5->ctrl48_is_present + + query_5->ctrl49_is_present + + query_5->ctrl50_is_present + + query_5->ctrl51_is_present + + query_5->ctrl52_is_present + + query_5->ctrl53_is_present + + query_5->ctrl54_is_present + + query_5->ctrl55_is_present + + query_5->ctrl56_is_present + + query_5->ctrl57_is_present; + + ctrl_23_size = 2; + for (subpacket = 2; subpacket <= 4; subpacket++) { + retval = synaptics_rmi4_f12_find_sub(rmi4_data, + fhandler, query_5->data, sizeof(query_5->data), + 6, 23, subpacket); + if (retval == 1) + ctrl_23_size++; + else if (retval < 0) + goto exit; + + } + + retval = synaptics_rmi4_f12_find_sub(rmi4_data, + fhandler, query_5->data, sizeof(query_5->data), + 6, 20, 0); + if (retval == 1) + rmi4_data->set_wakeup_gesture = 2; + else if (retval == 0) + rmi4_data->set_wakeup_gesture = 0; + else if (retval < 0) + goto exit; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.ctrl_base + ctrl_23_offset, + ctrl_23->data, + ctrl_23_size); + if (retval < 0) + goto exit; + + /* Maximum number of fingers supported */ + fhandler->num_of_data_points = min_t(unsigned char, + ctrl_23->max_reported_objects, + (unsigned char)F12_FINGERS_TO_SUPPORT); + + num_of_fingers = fhandler->num_of_data_points; + rmi4_data->num_of_fingers = num_of_fingers; + + rmi4_data->stylus_enable = ctrl_23->stylus_enable; + rmi4_data->eraser_enable = ctrl_23->eraser_enable; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.query_base + 7, + &size_of_query8, + sizeof(size_of_query8)); + if (retval < 0) + goto exit; + + if (size_of_query8 > sizeof(query_8->data)) + size_of_query8 = sizeof(query_8->data); + memset(query_8->data, 0x00, sizeof(query_8->data)); + + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.query_base + 8, + query_8->data, + size_of_query8); + if (retval < 0) + goto exit; + + /* Determine the presence of the Data0 register */ + extra_data->data1_offset = query_8->data0_is_present; + + if ((size_of_query8 >= 3) && (query_8->data15_is_present)) { + extra_data->data15_offset = query_8->data0_is_present + + query_8->data1_is_present + + query_8->data2_is_present + + query_8->data3_is_present + + query_8->data4_is_present + + query_8->data5_is_present + + query_8->data6_is_present + + query_8->data7_is_present + + query_8->data8_is_present + + query_8->data9_is_present + + query_8->data10_is_present + + query_8->data11_is_present + + query_8->data12_is_present + + query_8->data13_is_present + + query_8->data14_is_present; + extra_data->data15_size = (num_of_fingers + 7) / 8; + } else { + extra_data->data15_size = 0; + } + +#ifdef REPORT_2D_PRESSURE + if ((size_of_query8 >= 5) && (query_8->data29_is_present)) { + extra_data->data29_offset = query_8->data0_is_present + + query_8->data1_is_present + + query_8->data2_is_present + + query_8->data3_is_present + + query_8->data4_is_present + + query_8->data5_is_present + + query_8->data6_is_present + + query_8->data7_is_present + + query_8->data8_is_present + + query_8->data9_is_present + + query_8->data10_is_present + + query_8->data11_is_present + + query_8->data12_is_present + + query_8->data13_is_present + + query_8->data14_is_present + + query_8->data15_is_present + + query_8->data16_is_present + + query_8->data17_is_present + + query_8->data18_is_present + + query_8->data19_is_present + + query_8->data20_is_present + + query_8->data21_is_present + + query_8->data22_is_present + + query_8->data23_is_present + + query_8->data24_is_present + + query_8->data25_is_present + + query_8->data26_is_present + + query_8->data27_is_present + + query_8->data28_is_present; + extra_data->data29_size = 0; + for (subpacket = 0; subpacket <= num_of_fingers; subpacket++) { + retval = synaptics_rmi4_f12_find_sub(rmi4_data, + fhandler, query_8->data, + sizeof(query_8->data), + 9, 29, subpacket); + if (retval == 1) + extra_data->data29_size += 2; + else if (retval < 0) + goto exit; + } + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.ctrl_base + ctrl_58_offset, + ctrl_58->data, + sizeof(ctrl_58->data)); + if (retval < 0) + goto exit; + rmi4_data->force_min = + (int)(ctrl_58->min_force_lsb << 0) | + (int)(ctrl_58->min_force_msb << 8); + rmi4_data->force_max = + (int)(ctrl_58->max_force_lsb << 0) | + (int)(ctrl_58->max_force_msb << 8); + rmi4_data->report_pressure = true; + } else { + extra_data->data29_size = 0; + rmi4_data->report_pressure = false; + } +#endif + + rmi4_data->report_enable = RPT_DEFAULT; +#ifdef REPORT_2D_Z + rmi4_data->report_enable |= RPT_Z; +#endif +#ifdef REPORT_2D_W + rmi4_data->report_enable |= (RPT_WX | RPT_WY); +#endif + + retval = synaptics_rmi4_f12_set_enables(rmi4_data, + fhandler->full_addr.ctrl_base + ctrl_28_offset); + if (retval < 0) + goto exit; + + if (query_5->ctrl8_is_present) { + rmi4_data->wedge_sensor = false; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.ctrl_base + ctrl_8_offset, + ctrl_8->data, + sizeof(ctrl_8->data)); + if (retval < 0) + goto exit; + + /* Maximum x and y */ + rmi4_data->sensor_max_x = + ((unsigned int)ctrl_8->max_x_coord_lsb << 0) | + ((unsigned int)ctrl_8->max_x_coord_msb << 8); + rmi4_data->sensor_max_y = + ((unsigned int)ctrl_8->max_y_coord_lsb << 0) | + ((unsigned int)ctrl_8->max_y_coord_msb << 8); + + rmi4_data->max_touch_width = MAX_F12_TOUCH_WIDTH; + } else { + rmi4_data->wedge_sensor = true; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.ctrl_base + ctrl_31_offset, + ctrl_31->data, + sizeof(ctrl_31->data)); + if (retval < 0) + goto exit; + + /* Maximum x and y */ + rmi4_data->sensor_max_x = + ((unsigned int)ctrl_31->max_x_coord_lsb << 0) | + ((unsigned int)ctrl_31->max_x_coord_msb << 8); + rmi4_data->sensor_max_y = + ((unsigned int)ctrl_31->max_y_coord_lsb << 0) | + ((unsigned int)ctrl_31->max_y_coord_msb << 8); + + rmi4_data->max_touch_width = MAX_F12_TOUCH_WIDTH; + } + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Function %02x max x = %d max y = %d\n", + __func__, fhandler->fn_number, + rmi4_data->sensor_max_x, + rmi4_data->sensor_max_y); + + if (bdata->swap_axes) { + temp = rmi4_data->sensor_max_x; + rmi4_data->sensor_max_x = rmi4_data->sensor_max_y; + rmi4_data->sensor_max_y = temp; + } + + rmi4_data->f12_wakeup_gesture = query_5->ctrl27_is_present; + if (rmi4_data->f12_wakeup_gesture) { + extra_data->ctrl20_offset = ctrl_20_offset; + extra_data->data4_offset = query_8->data0_is_present + + query_8->data1_is_present + + query_8->data2_is_present + + query_8->data3_is_present; + } + + synaptics_rmi4_set_intr_mask(fhandler, fd, intr_count); + + /* Allocate memory for finger data storage space */ + fhandler->data_size = num_of_fingers * size_of_2d_data; + fhandler->data = kmalloc(fhandler->data_size, GFP_KERNEL); + if (!fhandler->data) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for fhandler->data\n", + __func__); + retval = -ENOMEM; + goto exit; + } + +exit: + kfree(query_5); + kfree(query_8); + kfree(ctrl_8); + kfree(ctrl_23); + kfree(ctrl_31); + kfree(ctrl_58); + + return retval; +} + +static int synaptics_rmi4_f1a_alloc_mem(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn *fhandler) +{ + int retval; + struct synaptics_rmi4_f1a_handle *f1a; + + f1a = kzalloc(sizeof(*f1a), GFP_KERNEL); + if (!f1a) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for function handle\n", + __func__); + return -ENOMEM; + } + + fhandler->data = (void *)f1a; + fhandler->extra = NULL; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.query_base, + f1a->button_query.data, + sizeof(f1a->button_query.data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read query registers\n", + __func__); + return retval; + } + + f1a->max_count = f1a->button_query.max_button_count + 1; + + f1a->button_control.txrx_map = kzalloc(f1a->max_count * 2, GFP_KERNEL); + if (!f1a->button_control.txrx_map) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for tx rx mapping\n", + __func__); + return -ENOMEM; + } + + f1a->button_bitmask_size = (f1a->max_count + 7) / 8; + + f1a->button_data_buffer = kcalloc(f1a->button_bitmask_size, + sizeof(*(f1a->button_data_buffer)), GFP_KERNEL); + if (!f1a->button_data_buffer) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for data buffer\n", + __func__); + return -ENOMEM; + } + + f1a->button_map = kcalloc(f1a->max_count, + sizeof(*(f1a->button_map)), GFP_KERNEL); + if (!f1a->button_map) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for button map\n", + __func__); + return -ENOMEM; + } + + return 0; +} + +static int synaptics_rmi4_f1a_button_map(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn *fhandler) +{ + int retval; + unsigned char ii; + unsigned char offset = 0; + struct synaptics_rmi4_f1a_query_4 query_4; + struct synaptics_rmi4_f1a_handle *f1a = fhandler->data; + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + rmi4_data->valid_button_count = f1a->valid_button_count; + + offset = f1a->button_query.has_general_control + + f1a->button_query.has_interrupt_enable + + f1a->button_query.has_multibutton_select; + + if (f1a->button_query.has_tx_rx_map) { + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.ctrl_base + offset, + f1a->button_control.txrx_map, + f1a->max_count * 2); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read tx rx mapping\n", + __func__); + return retval; + } + + rmi4_data->button_txrx_mapping = f1a->button_control.txrx_map; + } + + if (f1a->button_query.has_query4) { + offset = 2 + f1a->button_query.has_query2 + + f1a->button_query.has_query3; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.query_base + offset, + query_4.data, + sizeof(query_4.data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read button features 4\n", + __func__); + return retval; + } + + if (query_4.has_ctrl24) + rmi4_data->external_afe_buttons = true; + else + rmi4_data->external_afe_buttons = false; + } + + if (!bdata->cap_button_map) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: cap_button_map is NULL in board file\n", + __func__); + return -ENODEV; + } else if (!bdata->cap_button_map->map) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Button map is missing in board file\n", + __func__); + return -ENODEV; + } else { + if (bdata->cap_button_map->nbuttons != f1a->max_count) { + f1a->valid_button_count = min(f1a->max_count, + bdata->cap_button_map->nbuttons); + } else { + f1a->valid_button_count = f1a->max_count; + } + + for (ii = 0; ii < f1a->valid_button_count; ii++) + f1a->button_map[ii] = bdata->cap_button_map->map[ii]; + + rmi4_data->valid_button_count = f1a->valid_button_count; + } + + return 0; +} + +static void synaptics_rmi4_f1a_kfree(struct synaptics_rmi4_fn *fhandler) +{ + struct synaptics_rmi4_f1a_handle *f1a = fhandler->data; + + if (f1a) { + kfree(f1a->button_control.txrx_map); + kfree(f1a->button_data_buffer); + kfree(f1a->button_map); + kfree(f1a); + fhandler->data = NULL; + } +} + +static int synaptics_rmi4_f1a_init(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn *fhandler, + struct synaptics_rmi4_fn_desc *fd, + unsigned int intr_count) +{ + int retval; + + fhandler->fn_number = fd->fn_number; + fhandler->num_of_data_sources = fd->intr_src_count; + + synaptics_rmi4_set_intr_mask(fhandler, fd, intr_count); + + retval = synaptics_rmi4_f1a_alloc_mem(rmi4_data, fhandler); + if (retval < 0) + goto error_exit; + + retval = synaptics_rmi4_f1a_button_map(rmi4_data, fhandler); + if (retval < 0) + goto error_exit; + + rmi4_data->button_0d_enabled = 1; + + return 0; + +error_exit: + synaptics_rmi4_f1a_kfree(fhandler); + + return retval; +} + +static void synaptics_rmi4_empty_fn_list(struct synaptics_rmi4_data *rmi4_data) +{ + struct synaptics_rmi4_fn *fhandler; + struct synaptics_rmi4_fn *fhandler_temp; + struct synaptics_rmi4_device_info *rmi; + + rmi = &(rmi4_data->rmi4_mod_info); + + if (!list_empty(&rmi->support_fn_list)) { + list_for_each_entry_safe(fhandler, + fhandler_temp, + &rmi->support_fn_list, + link) { + if (fhandler->fn_number == SYNAPTICS_RMI4_F1A) { + synaptics_rmi4_f1a_kfree(fhandler); + } else { + kfree(fhandler->extra); + kfree(fhandler->data); + } + list_del(&fhandler->link); + kfree(fhandler); + } + } + INIT_LIST_HEAD(&rmi->support_fn_list); +} + +static int synaptics_rmi4_check_status(struct synaptics_rmi4_data *rmi4_data, + bool *was_in_bl_mode) +{ + int retval; + int timeout = CHECK_STATUS_TIMEOUT_MS; + struct synaptics_rmi4_f01_device_status status; + + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_data_base_addr, + status.data, + sizeof(status.data)); + if (retval < 0) + return retval; + + while (status.status_code == STATUS_CRC_IN_PROGRESS) { + if (timeout > 0) + msleep(20); + else + return -EINVAL; + + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_data_base_addr, + status.data, + sizeof(status.data)); + if (retval < 0) + return retval; + + timeout -= 20; + } + + if (timeout != CHECK_STATUS_TIMEOUT_MS) + *was_in_bl_mode = true; + + if (status.flash_prog == 1) { + rmi4_data->flash_prog_mode = true; + pr_notice("%s: In flash prog mode, status = 0x%02x\n", + __func__, + status.status_code); + } else { + rmi4_data->flash_prog_mode = false; + } + + return 0; +} + +static int synaptics_rmi4_set_configured(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + unsigned char device_ctrl; + + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_ctrl_base_addr, + &device_ctrl, + sizeof(device_ctrl)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set configured\n", + __func__); + return retval; + } + + rmi4_data->no_sleep_setting = device_ctrl & NO_SLEEP_ON; + device_ctrl |= CONFIGURED; + + retval = synaptics_rmi4_reg_write(rmi4_data, + rmi4_data->f01_ctrl_base_addr, + &device_ctrl, + sizeof(device_ctrl)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set configured\n", + __func__); + } + + return retval; +} + +static int synaptics_rmi4_alloc_fh(struct synaptics_rmi4_fn **fhandler, + struct synaptics_rmi4_fn_desc *rmi_fd, int page_number) +{ + *fhandler = kzalloc(sizeof(**fhandler), GFP_KERNEL); + if (!(*fhandler)) + return -ENOMEM; + + (*fhandler)->full_addr.data_base = + (rmi_fd->data_base_addr | + (page_number << 8)); + (*fhandler)->full_addr.ctrl_base = + (rmi_fd->ctrl_base_addr | + (page_number << 8)); + (*fhandler)->full_addr.cmd_base = + (rmi_fd->cmd_base_addr | + (page_number << 8)); + (*fhandler)->full_addr.query_base = + (rmi_fd->query_base_addr | + (page_number << 8)); + + return 0; +} + +static int synaptics_rmi4_query_device(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + unsigned char page_number; + unsigned char intr_count; + unsigned char *f01_query; + unsigned short pdt_entry_addr; + bool f01found; + bool f35found; + bool was_in_bl_mode; + struct synaptics_rmi4_fn_desc rmi_fd; + struct synaptics_rmi4_fn *fhandler; + struct synaptics_rmi4_device_info *rmi; + + rmi = &(rmi4_data->rmi4_mod_info); + +rescan_pdt: + f01found = false; + f35found = false; + was_in_bl_mode = false; + intr_count = 0; + INIT_LIST_HEAD(&rmi->support_fn_list); + + /* Scan the page description tables of the pages to service */ + for (page_number = 0; page_number < PAGES_TO_SERVICE; page_number++) { + for (pdt_entry_addr = PDT_START; pdt_entry_addr > PDT_END; + pdt_entry_addr -= PDT_ENTRY_SIZE) { + pdt_entry_addr |= (page_number << 8); + + retval = synaptics_rmi4_reg_read(rmi4_data, + pdt_entry_addr, + (unsigned char *)&rmi_fd, + sizeof(rmi_fd)); + if (retval < 0) + return retval; + + pdt_entry_addr &= ~(MASK_8BIT << 8); + + fhandler = NULL; + + if (rmi_fd.fn_number == 0) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Reached end of PDT\n", + __func__); + break; + } + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: F%02x found (page %d)\n", + __func__, rmi_fd.fn_number, + page_number); + + switch (rmi_fd.fn_number) { + case SYNAPTICS_RMI4_F01: + if (rmi_fd.intr_src_count == 0) + break; + + f01found = true; + + retval = synaptics_rmi4_alloc_fh(&fhandler, + &rmi_fd, page_number); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc for F%d\n", + __func__, + rmi_fd.fn_number); + return retval; + } + + retval = synaptics_rmi4_f01_init(rmi4_data, + fhandler, &rmi_fd, intr_count); + if (retval < 0) + return retval; + + retval = synaptics_rmi4_check_status(rmi4_data, + &was_in_bl_mode); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to check status\n", + __func__); + return retval; + } + + if (was_in_bl_mode) { + kfree(fhandler); + fhandler = NULL; + goto rescan_pdt; + } + + if (rmi4_data->flash_prog_mode) + goto flash_prog_mode; + + break; + case SYNAPTICS_RMI4_F11: + if (rmi_fd.intr_src_count == 0) + break; + + retval = synaptics_rmi4_alloc_fh(&fhandler, + &rmi_fd, page_number); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc for F%d\n", + __func__, + rmi_fd.fn_number); + return retval; + } + + retval = synaptics_rmi4_f11_init(rmi4_data, + fhandler, &rmi_fd, intr_count); + if (retval < 0) + return retval; + break; + case SYNAPTICS_RMI4_F12: + if (rmi_fd.intr_src_count == 0) + break; + + retval = synaptics_rmi4_alloc_fh(&fhandler, + &rmi_fd, page_number); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc for F%d\n", + __func__, + rmi_fd.fn_number); + return retval; + } + + retval = synaptics_rmi4_f12_init(rmi4_data, + fhandler, &rmi_fd, intr_count); + if (retval < 0) + return retval; + break; + case SYNAPTICS_RMI4_F1A: + if (rmi_fd.intr_src_count == 0) + break; + + retval = synaptics_rmi4_alloc_fh(&fhandler, + &rmi_fd, page_number); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc for F%d\n", + __func__, + rmi_fd.fn_number); + return retval; + } + + retval = synaptics_rmi4_f1a_init(rmi4_data, + fhandler, &rmi_fd, intr_count); + if (retval < 0) { +#ifdef IGNORE_FN_INIT_FAILURE + kfree(fhandler); + fhandler = NULL; +#else + return retval; +#endif + } + break; +#ifdef USE_DATA_SERVER + case SYNAPTICS_RMI4_F21: + if (rmi_fd.intr_src_count == 0) + break; + + retval = synaptics_rmi4_alloc_fh(&fhandler, + &rmi_fd, page_number); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc for F%d\n", + __func__, + rmi_fd.fn_number); + return retval; + } + + fhandler->fn_number = rmi_fd.fn_number; + fhandler->num_of_data_sources = + rmi_fd.intr_src_count; + + synaptics_rmi4_set_intr_mask(fhandler, &rmi_fd, + intr_count); + break; +#endif + case SYNAPTICS_RMI4_F35: + f35found = true; + break; +#ifdef F51_DISCRETE_FORCE + case SYNAPTICS_RMI4_F51: + rmi4_data->f51_query_base_addr = + rmi_fd.query_base_addr | + (page_number << 8); + break; +#endif + } + + /* Accumulate the interrupt count */ + intr_count += rmi_fd.intr_src_count; + + if (fhandler && rmi_fd.intr_src_count) { + list_add_tail(&fhandler->link, + &rmi->support_fn_list); + } + } + } + + if (!f01found) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to find F01\n", + __func__); + if (!f35found) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to find F35\n", + __func__); + return -EINVAL; + } else { + pr_notice("%s: In microbootloader mode\n", + __func__); + return 0; + } + } + +flash_prog_mode: + rmi4_data->num_of_intr_regs = (intr_count + 7) / 8; + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Number of interrupt registers = %d\n", + __func__, rmi4_data->num_of_intr_regs); + + f01_query = kmalloc(F01_STD_QUERY_LEN, GFP_KERNEL); + if (!f01_query) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for f01_query\n", + __func__); + return -ENOMEM; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_query_base_addr, + f01_query, + F01_STD_QUERY_LEN); + if (retval < 0) { + kfree(f01_query); + return retval; + } + + /* RMI Version 4.0 currently supported */ + rmi->version_major = 4; + rmi->version_minor = 0; + + rmi->manufacturer_id = f01_query[0]; + rmi->product_props = f01_query[1]; + rmi->product_info[0] = f01_query[2]; + rmi->product_info[1] = f01_query[3]; + retval = secure_memcpy(rmi->product_id_string, + sizeof(rmi->product_id_string), + &f01_query[11], + F01_STD_QUERY_LEN - 11, + PRODUCT_ID_SIZE); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy product ID string\n", + __func__); + } + + kfree(f01_query); + + if (rmi->manufacturer_id != 1) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Non-Synaptics device found, manufacturer ID = %d\n", + __func__, rmi->manufacturer_id); + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_query_base_addr + F01_BUID_ID_OFFSET, + rmi->build_id, + sizeof(rmi->build_id)); + if (retval < 0) + return retval; + + rmi4_data->firmware_id = (unsigned int)rmi->build_id[0] + + (unsigned int)rmi->build_id[1] * 0x100 + + (unsigned int)rmi->build_id[2] * 0x10000; + + memset(rmi4_data->intr_mask, 0x00, sizeof(rmi4_data->intr_mask)); + + /* + * Map out the interrupt bit masks for the interrupt sources + * from the registered function handlers. + */ + if (!list_empty(&rmi->support_fn_list)) { + list_for_each_entry(fhandler, &rmi->support_fn_list, link) { + if (fhandler->num_of_data_sources) { + rmi4_data->intr_mask[fhandler->intr_reg_num] |= + fhandler->intr_mask; + } + } + } + + if (rmi4_data->f11_wakeup_gesture || rmi4_data->f12_wakeup_gesture) + rmi4_data->enable_wakeup_gesture = WAKEUP_GESTURE; + else + rmi4_data->enable_wakeup_gesture = false; + + synaptics_rmi4_set_configured(rmi4_data); + + return 0; +} + +static int synaptics_rmi4_gpio_setup(int gpio, bool config, int dir, int state) +{ + int retval = 0; + unsigned char buf[16]; + + if (config) { + snprintf(buf, sizeof(buf), "dsx_gpio_%u\n", gpio); + + retval = gpio_request(gpio, buf); + if (retval) { + pr_err("%s: Failed to get gpio %d (code: %d)", + __func__, gpio, retval); + return retval; + } + + if (dir == 0) + retval = gpio_direction_input(gpio); + else + retval = gpio_direction_output(gpio, state); + if (retval) { + pr_err("%s: Failed to set gpio %d direction", + __func__, gpio); + return retval; + } + } else { + gpio_free(gpio); + } + + return retval; +} + +static void synaptics_rmi4_set_params(struct synaptics_rmi4_data *rmi4_data) +{ + unsigned char ii; + struct synaptics_rmi4_f1a_handle *f1a; + struct synaptics_rmi4_fn *fhandler; + struct synaptics_rmi4_device_info *rmi; + + rmi = &(rmi4_data->rmi4_mod_info); + + input_set_abs_params(rmi4_data->input_dev, + ABS_MT_POSITION_X, 0, + rmi4_data->sensor_max_x, 0, 0); + input_set_abs_params(rmi4_data->input_dev, + ABS_MT_POSITION_Y, 0, + rmi4_data->sensor_max_y, 0, 0); +#ifdef REPORT_2D_W + input_set_abs_params(rmi4_data->input_dev, + ABS_MT_TOUCH_MAJOR, 0, + rmi4_data->max_touch_width, 0, 0); + input_set_abs_params(rmi4_data->input_dev, + ABS_MT_TOUCH_MINOR, 0, + rmi4_data->max_touch_width, 0, 0); +#endif + + rmi4_data->input_settings.sensor_max_x = rmi4_data->sensor_max_x; + rmi4_data->input_settings.sensor_max_y = rmi4_data->sensor_max_y; + rmi4_data->input_settings.max_touch_width = rmi4_data->max_touch_width; + +#ifdef REPORT_2D_PRESSURE + if (rmi4_data->report_pressure) { + input_set_abs_params(rmi4_data->input_dev, + ABS_MT_PRESSURE, rmi4_data->force_min, + rmi4_data->force_max, 0, 0); + + rmi4_data->input_settings.force_min = rmi4_data->force_min; + rmi4_data->input_settings.force_max = rmi4_data->force_max; + } +#elif defined(F51_DISCRETE_FORCE) + input_set_abs_params(rmi4_data->input_dev, + ABS_MT_PRESSURE, 0, + FORCE_LEVEL_MAX, 0, 0); +#endif + +#ifdef TYPE_B_PROTOCOL +#ifdef KERNEL_ABOVE_3_6 + input_mt_init_slots(rmi4_data->input_dev, + rmi4_data->num_of_fingers, INPUT_MT_DIRECT); +#else + input_mt_init_slots(rmi4_data->input_dev, + rmi4_data->num_of_fingers); +#endif +#endif + + rmi4_data->input_settings.num_of_fingers = rmi4_data->num_of_fingers; + + f1a = NULL; + if (!list_empty(&rmi->support_fn_list)) { + list_for_each_entry(fhandler, &rmi->support_fn_list, link) { + if (fhandler->fn_number == SYNAPTICS_RMI4_F1A) + f1a = fhandler->data; + } + } + + if (f1a) { + for (ii = 0; ii < f1a->valid_button_count; ii++) { + set_bit(f1a->button_map[ii], + rmi4_data->input_dev->keybit); + input_set_capability(rmi4_data->input_dev, + EV_KEY, f1a->button_map[ii]); + } + + rmi4_data->input_settings.valid_button_count = + f1a->valid_button_count; + } + + if (vir_button_map->nbuttons) { + for (ii = 0; ii < vir_button_map->nbuttons; ii++) { + set_bit(vir_button_map->map[ii * 5], + rmi4_data->input_dev->keybit); + input_set_capability(rmi4_data->input_dev, + EV_KEY, vir_button_map->map[ii * 5]); + } + } + + if (rmi4_data->f11_wakeup_gesture || rmi4_data->f12_wakeup_gesture) { + set_bit(KEY_WAKEUP, rmi4_data->input_dev->keybit); + input_set_capability(rmi4_data->input_dev, EV_KEY, KEY_WAKEUP); + } +} + +static int synaptics_rmi4_set_input_dev(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + rmi4_data->input_dev = input_allocate_device(); + if (rmi4_data->input_dev == NULL) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to allocate input device\n", + __func__); + retval = -ENOMEM; + goto err_input_device; + } + + retval = synaptics_rmi4_query_device(rmi4_data); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to query device\n", + __func__); + goto err_query_device; + } + + rmi4_data->input_dev->name = PLATFORM_DRIVER_NAME; + rmi4_data->input_dev->phys = INPUT_PHYS_NAME; + rmi4_data->input_dev->id.product = SYNAPTICS_DSX_DRIVER_PRODUCT; + rmi4_data->input_dev->id.version = SYNAPTICS_DSX_DRIVER_VERSION; + rmi4_data->input_dev->dev.parent = rmi4_data->pdev->dev.parent; + input_set_drvdata(rmi4_data->input_dev, rmi4_data); + + set_bit(EV_SYN, rmi4_data->input_dev->evbit); + set_bit(EV_KEY, rmi4_data->input_dev->evbit); + set_bit(EV_ABS, rmi4_data->input_dev->evbit); + set_bit(BTN_TOUCH, rmi4_data->input_dev->keybit); + set_bit(BTN_TOOL_FINGER, rmi4_data->input_dev->keybit); +#ifdef INPUT_PROP_DIRECT + set_bit(INPUT_PROP_DIRECT, rmi4_data->input_dev->propbit); +#endif + + if (bdata->max_y_for_2d >= 0) + rmi4_data->sensor_max_y = bdata->max_y_for_2d; + + synaptics_rmi4_set_params(rmi4_data); + + retval = input_register_device(rmi4_data->input_dev); + if (retval) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to register input device\n", + __func__); + goto err_register_input; + } + + rmi4_data->input_settings.stylus_enable = rmi4_data->stylus_enable; + rmi4_data->input_settings.eraser_enable = rmi4_data->eraser_enable; + + if (!rmi4_data->stylus_enable) + return 0; + + rmi4_data->stylus_dev = input_allocate_device(); + if (rmi4_data->stylus_dev == NULL) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to allocate stylus device\n", + __func__); + retval = -ENOMEM; + goto err_stylus_device; + } + + rmi4_data->stylus_dev->name = STYLUS_DRIVER_NAME; + rmi4_data->stylus_dev->phys = STYLUS_PHYS_NAME; + rmi4_data->stylus_dev->id.product = SYNAPTICS_DSX_DRIVER_PRODUCT; + rmi4_data->stylus_dev->id.version = SYNAPTICS_DSX_DRIVER_VERSION; + rmi4_data->stylus_dev->dev.parent = rmi4_data->pdev->dev.parent; + input_set_drvdata(rmi4_data->stylus_dev, rmi4_data); + + set_bit(EV_KEY, rmi4_data->stylus_dev->evbit); + set_bit(EV_ABS, rmi4_data->stylus_dev->evbit); + set_bit(BTN_TOUCH, rmi4_data->stylus_dev->keybit); + set_bit(BTN_TOOL_PEN, rmi4_data->stylus_dev->keybit); + if (rmi4_data->eraser_enable) + set_bit(BTN_TOOL_RUBBER, rmi4_data->stylus_dev->keybit); +#ifdef INPUT_PROP_DIRECT + set_bit(INPUT_PROP_DIRECT, rmi4_data->stylus_dev->propbit); +#endif + + input_set_abs_params(rmi4_data->stylus_dev, ABS_X, 0, + rmi4_data->sensor_max_x, 0, 0); + input_set_abs_params(rmi4_data->stylus_dev, ABS_Y, 0, + rmi4_data->sensor_max_y, 0, 0); + + retval = input_register_device(rmi4_data->stylus_dev); + if (retval) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to register stylus device\n", + __func__); + goto err_register_stylus; + } + + return 0; + +err_register_stylus: + rmi4_data->stylus_dev = NULL; + +err_stylus_device: + input_unregister_device(rmi4_data->input_dev); + rmi4_data->input_dev = NULL; + +err_register_input: +err_query_device: + synaptics_rmi4_empty_fn_list(rmi4_data); + input_free_device(rmi4_data->input_dev); + +err_input_device: + return retval; +} + +static int synaptics_rmi4_set_gpio(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + retval = synaptics_rmi4_gpio_setup( + bdata->irq_gpio, + true, 0, 0); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to configure attention GPIO\n", + __func__); + goto err_gpio_irq; + } + + if (bdata->power_gpio >= 0) { + retval = synaptics_rmi4_gpio_setup( + bdata->power_gpio, + true, 1, !bdata->power_on_state); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to configure power GPIO\n", + __func__); + goto err_gpio_power; + } + } + + if (bdata->reset_gpio >= 0) { + retval = synaptics_rmi4_gpio_setup( + bdata->reset_gpio, + true, 1, !bdata->reset_on_state); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to configure reset GPIO\n", + __func__); + goto err_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_gpio_reset: + if (bdata->power_gpio >= 0) + synaptics_rmi4_gpio_setup(bdata->power_gpio, false, 0, 0); + +err_gpio_power: + synaptics_rmi4_gpio_setup(bdata->irq_gpio, false, 0, 0); + +err_gpio_irq: + return retval; +} + +static int synaptics_dsx_pinctrl_init(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + + /* Get pinctrl if target uses pinctrl */ + rmi4_data->ts_pinctrl = devm_pinctrl_get((rmi4_data->pdev->dev.parent)); + if (IS_ERR_OR_NULL(rmi4_data->ts_pinctrl)) { + retval = PTR_ERR(rmi4_data->ts_pinctrl); + dev_err(rmi4_data->pdev->dev.parent, + "Target does not use pinctrl %d\n", retval); + goto err_pinctrl_get; + } + + rmi4_data->pinctrl_state_active + = pinctrl_lookup_state(rmi4_data->ts_pinctrl, "pmx_ts_active"); + if (IS_ERR_OR_NULL(rmi4_data->pinctrl_state_active)) { + retval = PTR_ERR(rmi4_data->pinctrl_state_active); + dev_err(rmi4_data->pdev->dev.parent, + "Can not lookup %s pinstate %d\n", + PINCTRL_STATE_ACTIVE, retval); + goto err_pinctrl_lookup; + } + + rmi4_data->pinctrl_state_suspend + = pinctrl_lookup_state(rmi4_data->ts_pinctrl, "pmx_ts_suspend"); + if (IS_ERR_OR_NULL(rmi4_data->pinctrl_state_suspend)) { + retval = PTR_ERR(rmi4_data->pinctrl_state_suspend); + dev_err(rmi4_data->pdev->dev.parent, + "Can not lookup %s pinstate %d\n", + PINCTRL_STATE_SUSPEND, retval); + goto err_pinctrl_lookup; + } + + rmi4_data->pinctrl_state_release + = pinctrl_lookup_state(rmi4_data->ts_pinctrl, "pmx_ts_release"); + if (IS_ERR_OR_NULL(rmi4_data->pinctrl_state_release)) { + retval = PTR_ERR(rmi4_data->pinctrl_state_release); + dev_err(rmi4_data->pdev->dev.parent, + "Can not lookup %s pinstate %d\n", + PINCTRL_STATE_RELEASE, retval); + } + + return 0; + +err_pinctrl_lookup: + devm_pinctrl_put(rmi4_data->ts_pinctrl); +err_pinctrl_get: + rmi4_data->ts_pinctrl = NULL; + return retval; +} + +static int synaptics_rmi4_regulator_configure(struct synaptics_rmi4_data *rmi4_data, + bool enable) +{ + int retval; + + if (enable) { + retval = regulator_set_load(rmi4_data->pwr_reg, + 20000); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set active regulator load avdd\n", + __func__); + return retval; + } + + retval = regulator_set_voltage(rmi4_data->pwr_reg, + 3296000, + 3304000); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set active regulator voltage avdd\n", + __func__); + goto err_avdd_load; + } + + retval = regulator_set_load(rmi4_data->bus_reg, + 62000); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set active regulator load vdd\n", + __func__); + goto err_avdd_load; + } + + retval = regulator_set_voltage(rmi4_data->bus_reg, + 1800000, + 1800000); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set active regulator voltage vdd\n", + __func__); + goto err_vdd_load; + } + + } else { + retval = regulator_set_load(rmi4_data->pwr_reg, 0); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set inactive regulator load avdd\n", + __func__); + return retval; + } + + retval = regulator_set_load(rmi4_data->bus_reg, 0); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set inactive regulator load vdd\n", + __func__); + return retval; + } + + } + return retval; + +err_vdd_load: + regulator_set_load(rmi4_data->bus_reg, 0); +err_avdd_load: + regulator_set_load(rmi4_data->pwr_reg, 0); + return retval; +} + +static int synaptics_rmi4_get_reg(struct synaptics_rmi4_data *rmi4_data, + bool get) +{ + int retval; + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + if (!get) { + retval = 0; + goto regulator_put; + } + + if ((bdata->pwr_reg_name != NULL) && (*bdata->pwr_reg_name != 0)) { + rmi4_data->pwr_reg = regulator_get(rmi4_data->pdev->dev.parent, + bdata->pwr_reg_name); + if (IS_ERR(rmi4_data->pwr_reg)) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to get power regulator\n", + __func__); + retval = PTR_ERR(rmi4_data->pwr_reg); + goto regulator_put; + } + } + + retval = regulator_set_load(rmi4_data->pwr_reg, + 20000); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set regulator current avdd\n", + __func__); + goto regulator_put; + } + + retval = regulator_set_voltage(rmi4_data->pwr_reg, + 3296000, + 3304000); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set regulator voltage avdd\n", + __func__); + goto regulator_put; + } + + if ((bdata->bus_reg_name != NULL) && (*bdata->bus_reg_name != 0)) { + rmi4_data->bus_reg = regulator_get(rmi4_data->pdev->dev.parent, + bdata->bus_reg_name); + if (IS_ERR(rmi4_data->bus_reg)) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to get bus pullup regulator\n", + __func__); + retval = PTR_ERR(rmi4_data->bus_reg); + goto regulator_put; + } + } + + retval = regulator_set_load(rmi4_data->bus_reg, + 62000); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set regulator current vdd\n", + __func__); + goto regulator_put; + } + + retval = regulator_set_voltage(rmi4_data->bus_reg, + 1800000, + 1800000); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set regulator voltage vdd\n", + __func__); + goto regulator_put; + } + + return 0; + +regulator_put: + if (rmi4_data->pwr_reg) { + regulator_put(rmi4_data->pwr_reg); + rmi4_data->pwr_reg = NULL; + } + + if (rmi4_data->bus_reg) { + regulator_put(rmi4_data->bus_reg); + rmi4_data->bus_reg = NULL; + } + + return retval; +} + +static int synaptics_rmi4_enable_reg(struct synaptics_rmi4_data *rmi4_data, + bool enable) +{ + int retval; + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + if (!enable) { + retval = 0; + goto disable_pwr_reg; + } + + if (rmi4_data->bus_reg && rmi4_data->vdd_status == 0) { + retval = regulator_enable(rmi4_data->bus_reg); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to enable bus pullup regulator\n", + __func__); + goto exit; + } + rmi4_data->vdd_status = 1; + } + + if (rmi4_data->pwr_reg && rmi4_data->avdd_status == 0) { + retval = regulator_enable(rmi4_data->pwr_reg); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to enable power regulator\n", + __func__); + goto disable_bus_reg; + } + rmi4_data->avdd_status = 1; + msleep(bdata->power_delay_ms); + } + + return 0; + +disable_pwr_reg: + if (rmi4_data->pwr_reg && rmi4_data->avdd_status == 1) { + regulator_disable(rmi4_data->pwr_reg); + rmi4_data->avdd_status = 0; + } + +disable_bus_reg: + if (rmi4_data->bus_reg && rmi4_data->vdd_status == 1) { + regulator_disable(rmi4_data->bus_reg); + rmi4_data->vdd_status = 0; + } + +exit: + synaptics_rmi4_regulator_configure(rmi4_data, false); + return retval; +} + +static int synaptics_rmi4_free_fingers(struct synaptics_rmi4_data *rmi4_data) +{ + unsigned char ii; + + mutex_lock(&(rmi4_data->rmi4_report_mutex)); + +#ifdef TYPE_B_PROTOCOL + for (ii = 0; ii < rmi4_data->num_of_fingers; ii++) { + input_mt_slot(rmi4_data->input_dev, ii); + input_mt_report_slot_state(rmi4_data->input_dev, + MT_TOOL_FINGER, 0); + } +#endif + input_report_key(rmi4_data->input_dev, + BTN_TOUCH, 0); + input_report_key(rmi4_data->input_dev, + BTN_TOOL_FINGER, 0); +#ifndef TYPE_B_PROTOCOL + input_mt_sync(rmi4_data->input_dev); +#endif + input_sync(rmi4_data->input_dev); + + if (rmi4_data->stylus_enable) { + input_report_key(rmi4_data->stylus_dev, + BTN_TOUCH, 0); + input_report_key(rmi4_data->stylus_dev, + BTN_TOOL_PEN, 0); + if (rmi4_data->eraser_enable) { + input_report_key(rmi4_data->stylus_dev, + BTN_TOOL_RUBBER, 0); + } + input_sync(rmi4_data->stylus_dev); + } + + mutex_unlock(&(rmi4_data->rmi4_report_mutex)); + + rmi4_data->fingers_on_2d = false; + + return 0; +} + +static int synaptics_rmi4_sw_reset(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + unsigned char command = 0x01; + + retval = synaptics_rmi4_reg_write(rmi4_data, + rmi4_data->f01_cmd_base_addr, + &command, + sizeof(command)); + if (retval < 0) + return retval; + + msleep(rmi4_data->hw_if->board_data->reset_delay_ms); + + if (rmi4_data->hw_if->ui_hw_init) { + retval = rmi4_data->hw_if->ui_hw_init(rmi4_data); + if (retval < 0) + return retval; + } + + return 0; +} + +static int synaptics_rmi4_do_rebuild(struct synaptics_rmi4_data *rmi4_data) +{ + struct synaptics_rmi4_input_settings *settings; + + settings = &(rmi4_data->input_settings); + + if (settings->num_of_fingers != rmi4_data->num_of_fingers) + return 1; + + if (settings->valid_button_count != rmi4_data->valid_button_count) + return 1; + + if (settings->max_touch_width != rmi4_data->max_touch_width) + return 1; + + if (settings->sensor_max_x != rmi4_data->sensor_max_x) + return 1; + + if (settings->sensor_max_y != rmi4_data->sensor_max_y) + return 1; + + if (settings->force_min != rmi4_data->force_min) + return 1; + + if (settings->force_max != rmi4_data->force_max) + return 1; + + if (settings->stylus_enable != rmi4_data->stylus_enable) + return 1; + + if (settings->eraser_enable != rmi4_data->eraser_enable) + return 1; + + return 0; +} + +static void synaptics_rmi4_rebuild_work(struct work_struct *work) +{ + int retval; + unsigned char attr_count; + struct synaptics_rmi4_exp_fhandler *exp_fhandler; + struct delayed_work *delayed_work = + container_of(work, struct delayed_work, work); + struct synaptics_rmi4_data *rmi4_data = + container_of(delayed_work, struct synaptics_rmi4_data, + rb_work); + + mutex_lock(&(rmi4_data->rmi4_reset_mutex)); + + mutex_lock(&exp_data.mutex); + + synaptics_rmi4_irq_enable(rmi4_data, false, false); + + if (!list_empty(&exp_data.list)) { + list_for_each_entry(exp_fhandler, &exp_data.list, link) + if (exp_fhandler->exp_fn->remove != NULL) + exp_fhandler->exp_fn->remove(rmi4_data); + } + + for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) { + sysfs_remove_file(&rmi4_data->input_dev->dev.kobj, + &attrs[attr_count].attr); + } + + synaptics_rmi4_free_fingers(rmi4_data); + synaptics_rmi4_empty_fn_list(rmi4_data); + input_unregister_device(rmi4_data->input_dev); + rmi4_data->input_dev = NULL; + if (rmi4_data->stylus_enable) { + input_unregister_device(rmi4_data->stylus_dev); + rmi4_data->stylus_dev = NULL; + } + + retval = synaptics_rmi4_set_input_dev(rmi4_data); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set up input device\n", + __func__); + goto exit; + } + + for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) { + retval = sysfs_create_file(&rmi4_data->input_dev->dev.kobj, + &attrs[attr_count].attr); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create sysfs attributes\n", + __func__); + goto exit; + } + } + + if (!list_empty(&exp_data.list)) { + list_for_each_entry(exp_fhandler, &exp_data.list, link) + if (exp_fhandler->exp_fn->init != NULL) + exp_fhandler->exp_fn->init(rmi4_data); + } + +exit: + synaptics_rmi4_irq_enable(rmi4_data, true, false); + + mutex_unlock(&exp_data.mutex); + + mutex_unlock(&(rmi4_data->rmi4_reset_mutex)); +} + +static int synaptics_rmi4_reinit_device(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + struct synaptics_rmi4_fn *fhandler; + struct synaptics_rmi4_exp_fhandler *exp_fhandler; + struct synaptics_rmi4_device_info *rmi; + + rmi = &(rmi4_data->rmi4_mod_info); + + mutex_lock(&(rmi4_data->rmi4_reset_mutex)); + + synaptics_rmi4_free_fingers(rmi4_data); + + if (!list_empty(&rmi->support_fn_list)) { + list_for_each_entry(fhandler, &rmi->support_fn_list, link) { + if (fhandler->fn_number == SYNAPTICS_RMI4_F12) { + synaptics_rmi4_f12_set_enables(rmi4_data, 0); + break; + } + } + } + + retval = synaptics_rmi4_int_enable(rmi4_data, true); + if (retval < 0) + goto exit; + + mutex_lock(&exp_data.mutex); + if (!list_empty(&exp_data.list)) { + list_for_each_entry(exp_fhandler, &exp_data.list, link) + if (exp_fhandler->exp_fn->reinit != NULL) + exp_fhandler->exp_fn->reinit(rmi4_data); + } + mutex_unlock(&exp_data.mutex); + + synaptics_rmi4_set_configured(rmi4_data); + + retval = 0; + +exit: + mutex_unlock(&(rmi4_data->rmi4_reset_mutex)); + return retval; +} + +static int synaptics_rmi4_reset_device(struct synaptics_rmi4_data *rmi4_data, + bool rebuild) +{ + int retval; + struct synaptics_rmi4_exp_fhandler *exp_fhandler; + + mutex_lock(&(rmi4_data->rmi4_reset_mutex)); + + synaptics_rmi4_irq_enable(rmi4_data, false, false); + + retval = synaptics_rmi4_sw_reset(rmi4_data); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to issue reset command\n", + __func__); + goto exit; + } + + synaptics_rmi4_free_fingers(rmi4_data); + + synaptics_rmi4_empty_fn_list(rmi4_data); + + retval = synaptics_rmi4_query_device(rmi4_data); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to query device\n", + __func__); + goto exit; + } + + mutex_lock(&exp_data.mutex); + if (!list_empty(&exp_data.list)) { + list_for_each_entry(exp_fhandler, &exp_data.list, link) + if (exp_fhandler->exp_fn->reset != NULL) + exp_fhandler->exp_fn->reset(rmi4_data); + } + mutex_unlock(&exp_data.mutex); + + retval = 0; + +exit: + synaptics_rmi4_irq_enable(rmi4_data, true, false); + + mutex_unlock(&(rmi4_data->rmi4_reset_mutex)); + + if (rebuild && synaptics_rmi4_do_rebuild(rmi4_data)) { + queue_delayed_work(rmi4_data->rb_workqueue, + &rmi4_data->rb_work, + msecs_to_jiffies(REBUILD_WORK_DELAY_MS)); + } + + return retval; +} + +#ifdef FB_READY_RESET +static void synaptics_rmi4_reset_work(struct work_struct *work) +{ + int retval = 0; + unsigned int timeout; + struct synaptics_rmi4_data *rmi4_data = + container_of(work, struct synaptics_rmi4_data, + reset_work); + + timeout = FB_READY_TIMEOUT_S * 1000 / FB_READY_WAIT_MS + 1; + + while (!rmi4_data->fb_ready) { + msleep(FB_READY_WAIT_MS); + timeout--; + if (timeout == 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Timed out waiting for FB ready\n", + __func__); + goto err; + } + } + + mutex_lock(&rmi4_data->rmi4_exp_init_mutex); + + retval = synaptics_rmi4_reset_device(rmi4_data, false); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to issue reset command\n", + __func__); + } + + mutex_unlock(&rmi4_data->rmi4_exp_init_mutex); +err: + + dev_err(rmi4_data->pdev->dev.parent, + "%s: Timed out waiting for FB ready\n", + __func__); + +} +#endif + +static int synaptics_rmi4_sleep_enable(struct synaptics_rmi4_data *rmi4_data, + bool enable) +{ + int retval; + unsigned char device_ctrl; + unsigned char no_sleep_setting = rmi4_data->no_sleep_setting; + + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_ctrl_base_addr, + &device_ctrl, + sizeof(device_ctrl)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read device control\n", + __func__); + return retval; + } + + device_ctrl = device_ctrl & ~MASK_3BIT; + if (enable) + device_ctrl = device_ctrl | SENSOR_SLEEP; + else + device_ctrl = device_ctrl | no_sleep_setting | NORMAL_OPERATION; + + retval = synaptics_rmi4_reg_write(rmi4_data, + rmi4_data->f01_ctrl_base_addr, + &device_ctrl, + sizeof(device_ctrl)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write device control\n", + __func__); + return retval; + } + + rmi4_data->sensor_sleep = enable; + + return retval; +} + +static void synaptics_rmi4_exp_fn_work(struct work_struct *work) +{ + struct synaptics_rmi4_exp_fhandler *exp_fhandler; + struct synaptics_rmi4_exp_fhandler *exp_fhandler_temp; + struct synaptics_rmi4_data *rmi4_data = exp_data.rmi4_data; + + mutex_lock(&rmi4_data->rmi4_exp_init_mutex); + mutex_lock(&rmi4_data->rmi4_reset_mutex); + mutex_lock(&exp_data.mutex); + if (!list_empty(&exp_data.list)) { + list_for_each_entry_safe(exp_fhandler, + exp_fhandler_temp, + &exp_data.list, + link) { + if ((exp_fhandler->exp_fn->init != NULL) && + exp_fhandler->insert) { + exp_fhandler->exp_fn->init(rmi4_data); + exp_fhandler->insert = false; + } else if ((exp_fhandler->exp_fn->remove != NULL) && + exp_fhandler->remove) { + exp_fhandler->exp_fn->remove(rmi4_data); + list_del(&exp_fhandler->link); + kfree(exp_fhandler); + } + } + } + mutex_unlock(&exp_data.mutex); + mutex_unlock(&rmi4_data->rmi4_reset_mutex); + mutex_unlock(&rmi4_data->rmi4_exp_init_mutex); +} + +void synaptics_rmi4_new_function(struct synaptics_rmi4_exp_fn *exp_fn, + bool insert) +{ + struct synaptics_rmi4_exp_fhandler *exp_fhandler; + + if (!exp_data.initialized) { + mutex_init(&exp_data.mutex); + INIT_LIST_HEAD(&exp_data.list); + exp_data.initialized = true; + } + + mutex_lock(&exp_data.mutex); + if (insert) { + exp_fhandler = kzalloc(sizeof(*exp_fhandler), GFP_KERNEL); + if (!exp_fhandler) { + pr_err("%s: Failed to alloc mem for expansion function\n", + __func__); + goto exit; + } + exp_fhandler->exp_fn = exp_fn; + exp_fhandler->insert = true; + exp_fhandler->remove = false; + list_add_tail(&exp_fhandler->link, &exp_data.list); + } else if (!list_empty(&exp_data.list)) { + list_for_each_entry(exp_fhandler, &exp_data.list, link) { + if (exp_fhandler->exp_fn->fn_type == exp_fn->fn_type) { + exp_fhandler->insert = false; + exp_fhandler->remove = true; + goto exit; + } + } + } + +exit: + mutex_unlock(&exp_data.mutex); + + if (exp_data.queue_work) { + queue_delayed_work(exp_data.workqueue, + &exp_data.work, + msecs_to_jiffies(EXP_FN_WORK_DELAY_MS)); + } +} +EXPORT_SYMBOL(synaptics_rmi4_new_function); + +#ifdef CONFIG_DRM +static void synaptics_register_for_panel_events( + struct synaptics_rmi4_data *rmi4_data, struct device *dev) +{ + void *cookie; + + cookie = panel_event_notifier_register( + PANEL_EVENT_NOTIFICATION_PRIMARY, + PANEL_EVENT_NOTIFIER_CLIENT_PRIMARY_TOUCH, + active_panel, + &synaptics_rmi4_dsi_panel_notifier_cb, + &rmi4_data->fb_notifier); + if (!cookie) { + pr_err("Failed to register for panel events\n"); + return; + } + + pr_debug("Registered for panel notifications on panel: 0x%x\n", + active_panel); + rmi4_data->notifier_cookie = cookie; + +} +#endif + +static int synaptics_rmi4_probe(struct platform_device *pdev) +{ + int retval = 0; + struct synaptics_rmi4_data *rmi4_data; + const struct synaptics_dsx_hw_interface *hw_if; + const struct synaptics_dsx_board_data *bdata; + + hw_if = pdev->dev.platform_data; + if (!hw_if) { + dev_err(&pdev->dev, + "%s: No hardware interface found\n", + __func__); + return -EINVAL; + } + + bdata = hw_if->board_data; + if (!bdata) { + dev_err(&pdev->dev, + "%s: No board data found\n", + __func__); + return -EINVAL; + } + + rmi4_data = kzalloc(sizeof(*rmi4_data), GFP_KERNEL); + if (!rmi4_data) { + dev_err(&pdev->dev, + "%s: Failed to alloc mem for rmi4_data\n", + __func__); + return -ENOMEM; + } + + rmi4_data->pdev = pdev; + rmi4_data->current_page = MASK_8BIT; + rmi4_data->hw_if = hw_if; + rmi4_data->suspend = false; + rmi4_data->irq_enabled = false; + rmi4_data->fingers_on_2d = false; + + rmi4_data->reset_device = synaptics_rmi4_reset_device; + rmi4_data->irq_enable = synaptics_rmi4_irq_enable; + rmi4_data->sleep_enable = synaptics_rmi4_sleep_enable; + rmi4_data->report_touch = synaptics_rmi4_report_touch; + + mutex_init(&(rmi4_data->rmi4_reset_mutex)); + mutex_init(&(rmi4_data->rmi4_report_mutex)); + mutex_init(&(rmi4_data->rmi4_io_ctrl_mutex)); + mutex_init(&(rmi4_data->rmi4_exp_init_mutex)); + mutex_init(&(rmi4_data->rmi4_irq_enable_mutex)); + + platform_set_drvdata(pdev, rmi4_data); + + vir_button_map = bdata->vir_button_map; + + rmi4_data->initialized = false; + +#ifdef CONFIG_DRM + if (active_panel) { + pr_debug("panel detected, registering notifier\n"); + synaptics_register_for_panel_events(rmi4_data, + &pdev->dev); + } else { + pr_debug("panel not detected, freeing data\n"); + retval = -1; + goto err_drm_reg; + } +#endif + rmi4_data->rmi4_probe_wq = create_singlethread_workqueue( + "Synaptics_rmi4_probe_wq"); + if (!rmi4_data->rmi4_probe_wq) { + dev_err(&pdev->dev, + "%s: Failed to create probe workqueue\n", + __func__); + goto err_probe_wq; + } + INIT_WORK(&rmi4_data->rmi4_probe_work, synaptics_rmi4_defer_probe); + queue_work(rmi4_data->rmi4_probe_wq, &rmi4_data->rmi4_probe_work); + + return retval; + +err_probe_wq: +#ifdef CONFIG_DRM + if (active_panel && rmi4_data->notifier_cookie) { + pr_debug("unregistering panel notifications\n"); + panel_event_notifier_unregister( + &rmi4_data->notifier_cookie); + } +#endif +err_drm_reg: + kfree(rmi4_data); + + return retval; +} + +static void synaptics_rmi4_defer_probe(struct work_struct *work) +{ + int retval; + unsigned char attr_count; + struct synaptics_rmi4_data *rmi4_data = container_of(work, + struct synaptics_rmi4_data, rmi4_probe_work); + struct platform_device *pdev; + const struct synaptics_dsx_hw_interface *hw_if; + const struct synaptics_dsx_board_data *bdata; + + pdev = rmi4_data->pdev; + hw_if = rmi4_data->hw_if; + bdata = hw_if->board_data; + + init_completion(&rmi4_data->drm_init_done); + retval = wait_for_completion_interruptible(&rmi4_data->drm_init_done); + if (retval < 0) { + dev_err(&pdev->dev, + "%s: Wait for DRM init was interrupted\n", + __func__); + goto err_drm_init_wait; + } + + retval = synaptics_rmi4_get_reg(rmi4_data, true); + if (retval < 0) { + dev_err(&pdev->dev, + "%s: Failed to get regulators\n", + __func__); + goto err_get_reg; + } + + retval = synaptics_rmi4_enable_reg(rmi4_data, true); + if (retval < 0) { + dev_err(&pdev->dev, + "%s: Failed to enable regulators\n", + __func__); + goto err_enable_reg; + } + + retval = synaptics_rmi4_set_gpio(rmi4_data); + if (retval < 0) { + dev_err(&pdev->dev, + "%s: Failed to set up GPIO's\n", + __func__); + goto err_set_gpio; + } + + retval = synaptics_dsx_pinctrl_init(rmi4_data); + if (!retval && rmi4_data->ts_pinctrl) { + /* + * Pinctrl handle is optional. + * If pinctrl handle is found let pins to be + * configured in active state. If not found continue + * further without error. + */ + retval = pinctrl_select_state(rmi4_data->ts_pinctrl, + rmi4_data->pinctrl_state_active); + if (retval < 0) { + dev_err(&pdev->dev, + "%s: Failed to select %s pinstate %d\n", + __func__, PINCTRL_STATE_ACTIVE, retval); + } + } + + if (hw_if->ui_hw_init) { + retval = hw_if->ui_hw_init(rmi4_data); + if (retval < 0) { + dev_err(&pdev->dev, + "%s: Failed to initialize hardware interface\n", + __func__); + goto err_ui_hw_init; + } + } + + retval = synaptics_rmi4_set_input_dev(rmi4_data); + if (retval < 0) { + dev_err(&pdev->dev, + "%s: Failed to set up input device\n", + __func__); + goto err_set_input_dev; + } + +#ifdef USE_EARLYSUSPEND + rmi4_data->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; + rmi4_data->early_suspend.suspend = synaptics_rmi4_early_suspend; + rmi4_data->early_suspend.resume = synaptics_rmi4_late_resume; + register_early_suspend(&rmi4_data->early_suspend); +#endif + + if (!exp_data.initialized) { + mutex_init(&exp_data.mutex); + INIT_LIST_HEAD(&exp_data.list); + exp_data.initialized = true; + } + + rmi4_data->irq = gpio_to_irq(bdata->irq_gpio); + + retval = synaptics_rmi4_irq_enable(rmi4_data, true, false); + if (retval < 0) { + dev_err(&pdev->dev, + "%s: Failed to enable attention interrupt\n", + __func__); + goto err_enable_irq; + } + + if (vir_button_map->nbuttons) { + rmi4_data->board_prop_dir = kobject_create_and_add( + "board_properties", NULL); + if (!rmi4_data->board_prop_dir) { + dev_err(&pdev->dev, + "%s: Failed to create board_properties directory\n", + __func__); + goto err_virtual_buttons; + } else { + retval = sysfs_create_file(rmi4_data->board_prop_dir, + &virtual_key_map_attr.attr); + if (retval < 0) { + dev_err(&pdev->dev, + "%s: Failed to create virtual key map file\n", + __func__); + goto err_virtual_buttons; + } + } + } + + for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) { + retval = sysfs_create_file(&rmi4_data->input_dev->dev.kobj, + &attrs[attr_count].attr); + if (retval < 0) { + dev_err(&pdev->dev, + "%s: Failed to create sysfs attributes\n", + __func__); + goto err_sysfs; + } + } + +#ifdef USE_DATA_SERVER + memset(&interrupt_signal, 0, sizeof(interrupt_signal)); + interrupt_signal.si_signo = SIGIO; + interrupt_signal.si_code = SI_USER; +#endif + + rmi4_data->rb_workqueue = + create_singlethread_workqueue("dsx_rebuild_workqueue"); + INIT_DELAYED_WORK(&rmi4_data->rb_work, synaptics_rmi4_rebuild_work); + + exp_data.workqueue = create_singlethread_workqueue("dsx_exp_workqueue"); + INIT_DELAYED_WORK(&exp_data.work, synaptics_rmi4_exp_fn_work); + exp_data.rmi4_data = rmi4_data; + exp_data.queue_work = true; + queue_delayed_work(exp_data.workqueue, + &exp_data.work, + 0); + +#ifdef FB_READY_RESET + rmi4_data->reset_workqueue = + create_singlethread_workqueue("dsx_reset_workqueue"); + INIT_WORK(&rmi4_data->reset_work, synaptics_rmi4_reset_work); + queue_work(rmi4_data->reset_workqueue, &rmi4_data->reset_work); +#endif + rmi4_data->initialized = true; + + return; + +err_sysfs: + for (attr_count--; attr_count >= 0; attr_count--) { + sysfs_remove_file(&rmi4_data->input_dev->dev.kobj, + &attrs[attr_count].attr); + } + +err_virtual_buttons: + if (rmi4_data->board_prop_dir) { + sysfs_remove_file(rmi4_data->board_prop_dir, + &virtual_key_map_attr.attr); + kobject_put(rmi4_data->board_prop_dir); + } + + synaptics_rmi4_irq_enable(rmi4_data, false, false); + +err_enable_irq: +#ifdef USE_EARLYSUSPEND + unregister_early_suspend(&rmi4_data->early_suspend); +#endif + + synaptics_rmi4_empty_fn_list(rmi4_data); + input_unregister_device(rmi4_data->input_dev); + rmi4_data->input_dev = NULL; + if (rmi4_data->stylus_enable) { + input_unregister_device(rmi4_data->stylus_dev); + rmi4_data->stylus_dev = NULL; + } + +err_set_input_dev: + synaptics_rmi4_gpio_setup(bdata->irq_gpio, false, 0, 0); + + if (bdata->reset_gpio >= 0) + synaptics_rmi4_gpio_setup(bdata->reset_gpio, false, 0, 0); + + if (bdata->power_gpio >= 0) + synaptics_rmi4_gpio_setup(bdata->power_gpio, false, 0, 0); + +err_ui_hw_init: +err_set_gpio: + synaptics_rmi4_enable_reg(rmi4_data, false); + synaptics_rmi4_regulator_configure(rmi4_data, false); + + if (rmi4_data->ts_pinctrl) { + if (IS_ERR_OR_NULL(rmi4_data->pinctrl_state_release)) { + devm_pinctrl_put(rmi4_data->ts_pinctrl); + rmi4_data->ts_pinctrl = NULL; + } else { + if (pinctrl_select_state( + rmi4_data->ts_pinctrl, + rmi4_data->pinctrl_state_release)) + dev_err(&pdev->dev, + "%s: Failed to select %s pinstate\n", + __func__, PINCTRL_STATE_RELEASE); + } + } + +err_enable_reg: + synaptics_rmi4_get_reg(rmi4_data, false); + +err_get_reg: +err_drm_init_wait: +#ifdef CONFIG_DRM + if (active_panel && rmi4_data->notifier_cookie) { + pr_debug("unregistering panel_events\n"); + panel_event_notifier_unregister(&rmi4_data->notifier_cookie); + } +#endif + cancel_work_sync(&rmi4_data->rmi4_probe_work); + destroy_workqueue(rmi4_data->rmi4_probe_wq); + kfree(rmi4_data); +} + +static int synaptics_rmi4_remove(struct platform_device *pdev) +{ + unsigned char attr_count; + struct synaptics_rmi4_data *rmi4_data = platform_get_drvdata(pdev); + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + +#ifdef FB_READY_RESET + cancel_work_sync(&rmi4_data->reset_work); + flush_workqueue(rmi4_data->reset_workqueue); + destroy_workqueue(rmi4_data->reset_workqueue); +#endif + + cancel_delayed_work_sync(&exp_data.work); + flush_workqueue(exp_data.workqueue); + destroy_workqueue(exp_data.workqueue); + + cancel_delayed_work_sync(&rmi4_data->rb_work); + flush_workqueue(rmi4_data->rb_workqueue); + destroy_workqueue(rmi4_data->rb_workqueue); + + for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) { + sysfs_remove_file(&rmi4_data->input_dev->dev.kobj, + &attrs[attr_count].attr); + } + + if (rmi4_data->board_prop_dir) { + sysfs_remove_file(rmi4_data->board_prop_dir, + &virtual_key_map_attr.attr); + kobject_put(rmi4_data->board_prop_dir); + } + + synaptics_rmi4_irq_enable(rmi4_data, false, false); + +#ifdef CONFIG_DRM + if (active_panel) { + pr_debug("unregistering panel_events\n"); + panel_event_notifier_unregister(&rmi4_data->notifier_cookie); + } +#endif + +#ifdef USE_EARLYSUSPEND + unregister_early_suspend(&rmi4_data->early_suspend); +#endif + + synaptics_rmi4_empty_fn_list(rmi4_data); + input_unregister_device(rmi4_data->input_dev); + rmi4_data->input_dev = NULL; + if (rmi4_data->stylus_enable) { + input_unregister_device(rmi4_data->stylus_dev); + rmi4_data->stylus_dev = NULL; + } + + synaptics_rmi4_gpio_setup(bdata->irq_gpio, false, 0, 0); + + if (bdata->reset_gpio >= 0) + synaptics_rmi4_gpio_setup(bdata->reset_gpio, false, 0, 0); + + if (bdata->power_gpio >= 0) + synaptics_rmi4_gpio_setup(bdata->power_gpio, false, 0, 0); + + if (rmi4_data->ts_pinctrl) { + if (IS_ERR_OR_NULL(rmi4_data->pinctrl_state_release)) { + devm_pinctrl_put(rmi4_data->ts_pinctrl); + rmi4_data->ts_pinctrl = NULL; + } else { + pinctrl_select_state( + rmi4_data->ts_pinctrl, + rmi4_data->pinctrl_state_release); + } + } + + synaptics_rmi4_enable_reg(rmi4_data, false); + synaptics_rmi4_regulator_configure(rmi4_data, false); + synaptics_rmi4_get_reg(rmi4_data, false); + + cancel_work_sync(&rmi4_data->rmi4_probe_work); + destroy_workqueue(rmi4_data->rmi4_probe_wq); + + kfree(rmi4_data); + + return 0; +} + +#ifdef CONFIG_DRM +static void synaptics_rmi4_dsi_panel_notifier_cb( + enum panel_event_notifier_tag tag, + struct panel_event_notification *notification, + void *client_data) +{ + struct synaptics_rmi4_data *rmi4_data = + container_of(client_data, struct synaptics_rmi4_data, + fb_notifier); + + if (!notification) { + pr_err("Invalid notification\n"); + return; + } + pr_debug("Notification type: %d, early trigger:%d\n", + notification->notif_type, + notification->notif_data.early_trigger); + + if (!client_data) { + pr_err("No client data\n"); + return; + } + + switch (notification->notif_type) { + case DRM_PANEL_EVENT_UNBLANK: + if (rmi4_data->initialized) { + synaptics_rmi4_regulator_configure(rmi4_data, true); + synaptics_rmi4_resume(&rmi4_data->pdev->dev); + } else { + complete(&rmi4_data->drm_init_done); + } + rmi4_data->fb_ready = true; + break; + case DRM_PANEL_EVENT_BLANK: + if (rmi4_data->initialized) { + synaptics_rmi4_suspend(&rmi4_data->pdev->dev); + synaptics_rmi4_regulator_configure(rmi4_data, false); + } + rmi4_data->fb_ready = false; + break; + case DRM_PANEL_EVENT_BLANK_LP: + pr_debug("received lp event\n"); + break; + case DRM_PANEL_EVENT_FPS_CHANGE: + pr_debug("received fps change old fps:%d new fps:%d\n", + notification->notif_data.old_fps, + notification->notif_data.new_fps); + break; + default: + pr_debug("notification serviced :%d\n", + notification->notif_type); + break; + } + +} +#endif + +#ifdef USE_EARLYSUSPEND +static int synaptics_rmi4_early_suspend(struct early_suspend *h) +{ + struct synaptics_rmi4_exp_fhandler *exp_fhandler; + struct synaptics_rmi4_data *rmi4_data = + container_of(h, struct synaptics_rmi4_data, + early_suspend); + unsigned char device_ctrl; + + if (rmi4_data->stay_awake) + return retval; + + if (rmi4_data->enable_wakeup_gesture) { + if (rmi4_data->no_sleep_setting) { + synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_ctrl_base_addr, + &device_ctrl, + sizeof(device_ctrl)); + device_ctrl = device_ctrl & ~NO_SLEEP_ON; + synaptics_rmi4_reg_write(rmi4_data, + rmi4_data->f01_ctrl_base_addr, + &device_ctrl, + sizeof(device_ctrl)); + } + synaptics_rmi4_wakeup_gesture(rmi4_data, true); + enable_irq_wake(rmi4_data->irq); + goto exit; + } + +#ifdef SYNA_TDDI + if (rmi4_data->no_sleep_setting) { + synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_ctrl_base_addr, + &device_ctrl, + sizeof(device_ctrl)); + device_ctrl = device_ctrl & ~NO_SLEEP_ON; + synaptics_rmi4_reg_write(rmi4_data, + rmi4_data->f01_ctrl_base_addr, + &device_ctrl, + sizeof(device_ctrl)); + } + synaptics_rmi4_wakeup_gesture(rmi4_data, true); + usleep(TDDI_LPWG_WAIT_US); +#endif + synaptics_rmi4_irq_enable(rmi4_data, false, false); + synaptics_rmi4_sleep_enable(rmi4_data, true); + synaptics_rmi4_free_fingers(rmi4_data); + +exit: + mutex_lock(&exp_data.mutex); + if (!list_empty(&exp_data.list)) { + list_for_each_entry(exp_fhandler, &exp_data.list, link) + if (exp_fhandler->exp_fn->early_suspend != NULL) + exp_fhandler->exp_fn->early_suspend(rmi4_data); + } + mutex_unlock(&exp_data.mutex); + + rmi4_data->suspend = true; + + return retval; +} + +static int synaptics_rmi4_late_resume(struct early_suspend *h) +{ +#ifdef FB_READY_RESET + int retval; +#endif + struct synaptics_rmi4_exp_fhandler *exp_fhandler; + struct synaptics_rmi4_data *rmi4_data = + container_of(h, struct synaptics_rmi4_data, + early_suspend); + + if (rmi4_data->stay_awake) + return retval; + + if (rmi4_data->enable_wakeup_gesture) { + disable_irq_wake(rmi4_data->irq); + goto exit; + } + + rmi4_data->current_page = MASK_8BIT; + + if (rmi4_data->suspend) { + synaptics_rmi4_sleep_enable(rmi4_data, false); + synaptics_rmi4_irq_enable(rmi4_data, true, false); + } + +exit: +#ifdef FB_READY_RESET + if (rmi4_data->suspend) { + retval = synaptics_rmi4_reset_device(rmi4_data, false); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to issue reset command\n", + __func__); + } + } +#endif + mutex_lock(&exp_data.mutex); + if (!list_empty(&exp_data.list)) { + list_for_each_entry(exp_fhandler, &exp_data.list, link) + if (exp_fhandler->exp_fn->late_resume != NULL) + exp_fhandler->exp_fn->late_resume(rmi4_data); + } + mutex_unlock(&exp_data.mutex); + + rmi4_data->suspend = false; + + return retval; +} +#endif + +static int synaptics_rmi4_suspend(struct device *dev) +{ + struct synaptics_rmi4_exp_fhandler *exp_fhandler; + struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); + unsigned char device_ctrl; + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + if (rmi4_data->stay_awake) + return 0; + + if (rmi4_data->enable_wakeup_gesture) { + if (rmi4_data->no_sleep_setting) { + synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_ctrl_base_addr, + &device_ctrl, + sizeof(device_ctrl)); + device_ctrl = device_ctrl & ~NO_SLEEP_ON; + synaptics_rmi4_reg_write(rmi4_data, + rmi4_data->f01_ctrl_base_addr, + &device_ctrl, + sizeof(device_ctrl)); + } + synaptics_rmi4_wakeup_gesture(rmi4_data, true); + enable_irq_wake(rmi4_data->irq); + goto exit; + } + + if (!rmi4_data->suspend) { +#ifdef SYNA_TDDI + if (rmi4_data->no_sleep_setting) { + synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_ctrl_base_addr, + &device_ctrl, + sizeof(device_ctrl)); + device_ctrl = device_ctrl & ~NO_SLEEP_ON; + synaptics_rmi4_reg_write(rmi4_data, + rmi4_data->f01_ctrl_base_addr, + &device_ctrl, + sizeof(device_ctrl)); + } + synaptics_rmi4_wakeup_gesture(rmi4_data, true); + usleep(TDDI_LPWG_WAIT_US); +#endif + synaptics_rmi4_irq_enable(rmi4_data, false, false); + synaptics_rmi4_sleep_enable(rmi4_data, true); + synaptics_rmi4_free_fingers(rmi4_data); + } + + if (bdata->reset_gpio >= 0) { + gpio_set_value(bdata->reset_gpio, bdata->reset_on_state); + msleep(bdata->reset_active_ms); + } + + synaptics_rmi4_enable_reg(rmi4_data, false); + +exit: + mutex_lock(&exp_data.mutex); + if (!list_empty(&exp_data.list)) { + list_for_each_entry(exp_fhandler, &exp_data.list, link) + if (exp_fhandler->exp_fn->suspend != NULL) + exp_fhandler->exp_fn->suspend(rmi4_data); + } + mutex_unlock(&exp_data.mutex); + + rmi4_data->suspend = true; + + return 0; +} + +static int synaptics_rmi4_resume(struct device *dev) +{ +#ifdef FB_READY_RESET + int retval; +#endif + struct synaptics_rmi4_exp_fhandler *exp_fhandler; + struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); + + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + if (rmi4_data->stay_awake) + return 0; + + if (rmi4_data->enable_wakeup_gesture) { + disable_irq_wake(rmi4_data->irq); + synaptics_rmi4_wakeup_gesture(rmi4_data, false); + goto exit; + } + + synaptics_rmi4_enable_reg(rmi4_data, true); + + 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); + } + + + rmi4_data->current_page = MASK_8BIT; + + synaptics_rmi4_sleep_enable(rmi4_data, false); + synaptics_rmi4_irq_enable(rmi4_data, true, false); + +exit: +#ifdef FB_READY_RESET + retval = synaptics_rmi4_reset_device(rmi4_data, false); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to issue reset command\n", + __func__); + } +#endif + mutex_lock(&exp_data.mutex); + if (!list_empty(&exp_data.list)) { + list_for_each_entry(exp_fhandler, &exp_data.list, link) + if (exp_fhandler->exp_fn->resume != NULL) + exp_fhandler->exp_fn->resume(rmi4_data); + } + mutex_unlock(&exp_data.mutex); + + rmi4_data->suspend = false; + + return 0; +} + +#ifdef CONFIG_PM +static const struct dev_pm_ops synaptics_rmi4_dev_pm_ops = { + .suspend = synaptics_rmi4_suspend, + .resume = synaptics_rmi4_resume, +}; +#endif + +static struct platform_driver synaptics_rmi4_driver = { + .driver = { + .name = PLATFORM_DRIVER_NAME, + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &synaptics_rmi4_dev_pm_ops, +#endif + }, + .probe = synaptics_rmi4_probe, + .remove = synaptics_rmi4_remove, +}; + +static int __init synaptics_rmi4_init(void) +{ + int retval; + + retval = synaptics_rmi4_bus_init(); + if (retval) + return retval; + + return platform_driver_register(&synaptics_rmi4_driver); +} + +static void __exit synaptics_rmi4_exit(void) +{ + platform_driver_unregister(&synaptics_rmi4_driver); + + synaptics_rmi4_bus_exit(); +} + +module_init(synaptics_rmi4_init); +module_exit(synaptics_rmi4_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics DSX Touch Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/qcom/opensource/touch-drivers/synaptics_dsx/synaptics_dsx_core.h b/qcom/opensource/touch-drivers/synaptics_dsx/synaptics_dsx_core.h new file mode 100644 index 0000000000..409227d6fe --- /dev/null +++ b/qcom/opensource/touch-drivers/synaptics_dsx/synaptics_dsx_core.h @@ -0,0 +1,536 @@ +/* + * Synaptics DSX touchscreen driver + * + * Copyright (C) 2012-2016 Synaptics Incorporated. All rights reserved. + * + * Copyright (c) 2018-2021 The Linux Foundation. All rights reserved. + * Copyright (C) 2012 Alexandra Chin + * Copyright (C) 2012 Scott Lin + * + * 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_DSX_RMI4_H_ +#define _SYNAPTICS_DSX_RMI4_H_ + +#define SYNAPTICS_DS4 (1 << 0) +#define SYNAPTICS_DS5 (1 << 1) +#define SYNAPTICS_DSX_DRIVER_PRODUCT (SYNAPTICS_DS4 | SYNAPTICS_DS5) +#define SYNAPTICS_DSX_DRIVER_VERSION 0x2070 + +#include +#ifdef CONFIG_FB +#include +#include +#endif +#ifdef CONFIG_HAS_EARLYSUSPEND +#include +#endif + +#include + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 38)) +#define KERNEL_ABOVE_2_6_38 +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0)) +#define KERNEL_ABOVE_3_6 +#endif + +#ifdef KERNEL_ABOVE_2_6_38 +#define sstrtoul(...) kstrtoul(__VA_ARGS__) +#else +#define sstrtoul(...) strict_strtoul(__VA_ARGS__) +#endif + +#define PDT_PROPS (0X00EF) +#define PDT_START (0x00E9) +#define PDT_END (0x00D0) +#define PDT_ENTRY_SIZE (0x0006) +#define PAGES_TO_SERVICE (10) +#define PAGE_SELECT_LEN (2) +#define ADDRESS_LEN (2) + +#define SYNAPTICS_RMI4_F01 (0x01) +#define SYNAPTICS_RMI4_F11 (0x11) +#define SYNAPTICS_RMI4_F12 (0x12) +#define SYNAPTICS_RMI4_F1A (0x1A) +#define SYNAPTICS_RMI4_F21 (0x21) +#define SYNAPTICS_RMI4_F34 (0x34) +#define SYNAPTICS_RMI4_F35 (0x35) +#define SYNAPTICS_RMI4_F38 (0x38) +#define SYNAPTICS_RMI4_F51 (0x51) +#define SYNAPTICS_RMI4_F54 (0x54) +#define SYNAPTICS_RMI4_F55 (0x55) +#define SYNAPTICS_RMI4_FDB (0xDB) + +#define PRODUCT_INFO_SIZE 2 +#define PRODUCT_ID_SIZE 10 +#define BUILD_ID_SIZE 3 + +#define F12_FINGERS_TO_SUPPORT 10 +#define F12_NO_OBJECT_STATUS 0x00 +#define F12_FINGER_STATUS 0x01 +#define F12_ACTIVE_STYLUS_STATUS 0x02 +#define F12_PALM_STATUS 0x03 +#define F12_HOVERING_FINGER_STATUS 0x05 +#define F12_GLOVED_FINGER_STATUS 0x06 +#define F12_NARROW_OBJECT_STATUS 0x07 +#define F12_HAND_EDGE_STATUS 0x08 +#define F12_COVER_STATUS 0x0A +#define F12_STYLUS_STATUS 0x0B +#define F12_ERASER_STATUS 0x0C +#define F12_SMALL_OBJECT_STATUS 0x0D + +#define F12_GESTURE_DETECTION_LEN 5 + +#define MAX_NUMBER_OF_BUTTONS 4 +#define MAX_INTR_REGISTERS 4 + +#define MASK_16BIT 0xFFFF +#define MASK_8BIT 0xFF +#define MASK_7BIT 0x7F +#define MASK_6BIT 0x3F +#define MASK_5BIT 0x1F +#define MASK_4BIT 0x0F +#define MASK_3BIT 0x07 +#define MASK_2BIT 0x03 +#define MASK_1BIT 0x01 + +#define PINCTRL_STATE_ACTIVE "pmx_ts_active" +#define PINCTRL_STATE_SUSPEND "pmx_ts_suspend" +#define PINCTRL_STATE_RELEASE "pmx_ts_release" + +enum exp_fn { + RMI_DEV = 0, + RMI_FW_UPDATER, + RMI_TEST_REPORTING, + RMI_PROXIMITY, + RMI_ACTIVE_PEN, + RMI_GESTURE, + RMI_VIDEO, + RMI_DEBUG, + RMI_LAST, +}; + +extern struct drm_panel *active_panel; + +/* + * struct synaptics_rmi4_fn_desc - function descriptor fields in PDT entry + * @query_base_addr: base address for query registers + * @cmd_base_addr: base address for command registers + * @ctrl_base_addr: base address for control registers + * @data_base_addr: base address for data registers + * @intr_src_count: number of interrupt sources + * @fn_version: version of function + * @fn_number: function number + */ +struct synaptics_rmi4_fn_desc { + union { + struct { + unsigned char query_base_addr; + unsigned char cmd_base_addr; + unsigned char ctrl_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; + unsigned char data[6]; + }; +}; + +/* + * synaptics_rmi4_fn_full_addr - full 16-bit base addresses + * @query_base: 16-bit base address for query registers + * @cmd_base: 16-bit base address for command registers + * @ctrl_base: 16-bit base address for control registers + * @data_base: 16-bit base address for data registers + */ +struct synaptics_rmi4_fn_full_addr { + unsigned short query_base; + unsigned short cmd_base; + unsigned short ctrl_base; + unsigned short data_base; +}; + +/* + * struct synaptics_rmi4_f11_extra_data - extra data of F$11 + * @data38_offset: offset to F11_2D_DATA38 register + */ +struct synaptics_rmi4_f11_extra_data { + unsigned char data38_offset; +}; + +/* + * struct synaptics_rmi4_f12_extra_data - extra data of F$12 + * @data1_offset: offset to F12_2D_DATA01 register + * @data4_offset: offset to F12_2D_DATA04 register + * @data15_offset: offset to F12_2D_DATA15 register + * @data15_size: size of F12_2D_DATA15 register + * @data15_data: buffer for reading F12_2D_DATA15 register + * @data29_offset: offset to F12_2D_DATA29 register + * @data29_size: size of F12_2D_DATA29 register + * @data29_data: buffer for reading F12_2D_DATA29 register + * @ctrl20_offset: offset to F12_2D_CTRL20 register + */ +struct synaptics_rmi4_f12_extra_data { + unsigned char data1_offset; + unsigned char data4_offset; + unsigned char data15_offset; + unsigned char data15_size; + unsigned char data15_data[(F12_FINGERS_TO_SUPPORT + 7) / 8]; + unsigned char data29_offset; + unsigned char data29_size; + unsigned char data29_data[F12_FINGERS_TO_SUPPORT * 2]; + unsigned char ctrl20_offset; +}; + +/* + * struct synaptics_rmi4_fn - RMI function handler + * @fn_number: function number + * @num_of_data_sources: number of data sources + * @num_of_data_points: maximum number of fingers supported + * @intr_reg_num: index to associated interrupt register + * @intr_mask: interrupt mask + * @full_addr: full 16-bit base addresses of function registers + * @link: linked list for function handlers + * @data_size: size of private data + * @data: pointer to private data + * @extra: pointer to extra data + */ +struct synaptics_rmi4_fn { + unsigned char fn_number; + unsigned char num_of_data_sources; + unsigned char num_of_data_points; + unsigned char intr_reg_num; + unsigned char intr_mask; + struct synaptics_rmi4_fn_full_addr full_addr; + struct list_head link; + int data_size; + void *data; + void *extra; +}; + +/* + * struct synaptics_rmi4_input_settings - current input settings + * @num_of_fingers: maximum number of fingers for 2D touch + * @valid_button_count: number of valid 0D buttons + * @max_touch_width: maximum touch width + * @sensor_max_x: maximum x coordinate for 2D touch + * @sensor_max_y: maximum y coordinate for 2D touch + * @force_min: minimum force value + * @force_max: maximum force value + * @stylus_enable: flag to indicate reporting of stylus data + * @eraser_enable: flag to indicate reporting of eraser data + */ +struct synaptics_rmi4_input_settings { + unsigned char num_of_fingers; + unsigned char valid_button_count; + unsigned char max_touch_width; + int sensor_max_x; + int sensor_max_y; + int force_min; + int force_max; + bool stylus_enable; + bool eraser_enable; +}; + +/* + * struct synaptics_rmi4_device_info - device information + * @version_major: RMI protocol major version number + * @version_minor: RMI protocol minor version number + * @manufacturer_id: manufacturer ID + * @product_props: product properties + * @product_info: product information + * @product_id_string: product ID + * @build_id: firmware build ID + * @support_fn_list: linked list for function handlers + */ +struct synaptics_rmi4_device_info { + unsigned int version_major; + unsigned int version_minor; + unsigned char manufacturer_id; + unsigned char product_props; + unsigned char product_info[PRODUCT_INFO_SIZE]; + unsigned char product_id_string[PRODUCT_ID_SIZE + 1]; + unsigned char build_id[BUILD_ID_SIZE]; + struct list_head support_fn_list; +}; + +/* + * struct synaptics_rmi4_data - RMI4 device instance data + * @pdev: pointer to platform device + * @input_dev: pointer to associated input device + * @stylus_dev: pointer to associated stylus device + * @hw_if: pointer to hardware interface data + * @rmi4_mod_info: device information + * @board_prop_dir: /sys/board_properties directory for virtual key map file + * @pwr_reg: pointer to regulator for power control + * @bus_reg: pointer to regulator for bus pullup control + * @rmi4_reset_mutex: mutex for software reset + * @rmi4_report_mutex: mutex for input event reporting + * @rmi4_io_ctrl_mutex: mutex for communication interface I/O + * @rmi4_exp_init_mutex: mutex for expansion function module initialization + * @rmi4_irq_enable_mutex: mutex for enabling/disabling interrupt + * @rb_work: work for rebuilding input device + * @rb_workqueue: workqueue for rebuilding input device + * @fb_notifier: framebuffer notifier client + * @reset_work: work for issuing reset after display framebuffer ready + * @reset_workqueue: workqueue for issuing reset after display framebuffer ready + * @early_suspend: early suspend power management + * @current_page: current RMI page for register access + * @button_0d_enabled: switch for enabling 0d button support + * @num_of_tx: number of Tx channels for 2D touch + * @num_of_rx: number of Rx channels for 2D touch + * @num_of_fingers: maximum number of fingers for 2D touch + * @max_touch_width: maximum touch width + * @valid_button_count: number of valid 0D buttons + * @report_enable: input data to report for F$12 + * @no_sleep_setting: default setting of NoSleep in F01_RMI_CTRL00 register + * @gesture_detection: detected gesture type and properties + * @intr_mask: interrupt enable mask + * @button_txrx_mapping: Tx Rx mapping of 0D buttons + * @num_of_intr_regs: number of interrupt registers + * @f01_query_base_addr: query base address for f$01 + * @f01_cmd_base_addr: command base address for f$01 + * @f01_ctrl_base_addr: control base address for f$01 + * @f01_data_base_addr: data base address for f$01 + * @f51_query_base_addr: query base address for f$51 + * @firmware_id: firmware build ID + * @irq: attention interrupt + * @sensor_max_x: maximum x coordinate for 2D touch + * @sensor_max_y: maximum y coordinate for 2D touch + * @force_min: minimum force value + * @force_max: maximum force value + * @set_wakeup_gesture: location of set wakeup gesture + * @flash_prog_mode: flag to indicate flash programming mode status + * @irq_enabled: flag to indicate attention interrupt enable status + * @fingers_on_2d: flag to indicate presence of fingers in 2D area + * @suspend: flag to indicate whether in suspend state + * @sensor_sleep: flag to indicate sleep state of sensor + * @stay_awake: flag to indicate whether to stay awake during suspend + * @fb_ready: flag to indicate whether display framebuffer in ready state + * @f11_wakeup_gesture: flag to indicate support for wakeup gestures in F$11 + * @f12_wakeup_gesture: flag to indicate support for wakeup gestures in F$12 + * @enable_wakeup_gesture: flag to indicate usage of wakeup gestures + * @wedge_sensor: flag to indicate use of wedge sensor + * @report_pressure: flag to indicate reporting of pressure data + * @stylus_enable: flag to indicate reporting of stylus data + * @eraser_enable: flag to indicate reporting of eraser data + * @external_afe_buttons: flag to indicate presence of external AFE buttons + * @notifier_cookie: touch notifier for panel events + * @reset_device: pointer to device reset function + * @irq_enable: pointer to interrupt enable function + * @sleep_enable: pointer to sleep enable function + * @report_touch: pointer to touch reporting function + */ +struct synaptics_rmi4_data { + bool initialized; + struct platform_device *pdev; + struct input_dev *input_dev; + struct input_dev *stylus_dev; + const struct synaptics_dsx_hw_interface *hw_if; + struct synaptics_rmi4_device_info rmi4_mod_info; + struct synaptics_rmi4_input_settings input_settings; + struct kobject *board_prop_dir; + struct regulator *pwr_reg; + struct regulator *bus_reg; + struct mutex rmi4_reset_mutex; + struct mutex rmi4_report_mutex; + struct mutex rmi4_io_ctrl_mutex; + struct mutex rmi4_exp_init_mutex; + struct mutex rmi4_irq_enable_mutex; + struct delayed_work rb_work; + struct workqueue_struct *rb_workqueue; + struct work_struct rmi4_probe_work; + struct workqueue_struct *rmi4_probe_wq; + struct completion drm_init_done; + struct pinctrl *ts_pinctrl; + struct pinctrl_state *pinctrl_state_active; + struct pinctrl_state *pinctrl_state_suspend; + struct pinctrl_state *pinctrl_state_release; + struct notifier_block fb_notifier; + struct work_struct reset_work; + struct workqueue_struct *reset_workqueue; +#ifdef CONFIG_HAS_EARLYSUSPEND + struct early_suspend early_suspend; +#endif + unsigned char current_page; + unsigned char button_0d_enabled; + unsigned char num_of_tx; + unsigned char num_of_rx; + unsigned char num_of_fingers; + unsigned char max_touch_width; + unsigned char valid_button_count; + unsigned char report_enable; + unsigned char no_sleep_setting; + unsigned char gesture_detection[F12_GESTURE_DETECTION_LEN]; + unsigned char intr_mask[MAX_INTR_REGISTERS]; + unsigned char *button_txrx_mapping; + unsigned short num_of_intr_regs; + unsigned short f01_query_base_addr; + unsigned short f01_cmd_base_addr; + unsigned short f01_ctrl_base_addr; + unsigned short f01_data_base_addr; +#ifdef F51_DISCRETE_FORCE + unsigned short f51_query_base_addr; +#endif + unsigned int firmware_id; + int irq; + int sensor_max_x; + int sensor_max_y; + int force_min; + int force_max; + int set_wakeup_gesture; + int avdd_status; + int vdd_status; + bool flash_prog_mode; + bool irq_enabled; + bool fingers_on_2d; + bool suspend; + bool sensor_sleep; + bool stay_awake; + bool fb_ready; + bool f11_wakeup_gesture; + bool f12_wakeup_gesture; + bool enable_wakeup_gesture; + bool wedge_sensor; + bool report_pressure; + bool stylus_enable; + bool eraser_enable; + bool external_afe_buttons; + void *notifier_cookie; + int (*reset_device)(struct synaptics_rmi4_data *rmi4_data, + bool rebuild); + int (*irq_enable)(struct synaptics_rmi4_data *rmi4_data, bool enable, + bool attn_only); + int (*sleep_enable)(struct synaptics_rmi4_data *rmi4_data, + bool enable); + void (*report_touch)(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn *fhandler); +}; + +struct synaptics_dsx_bus_access { + unsigned char type; + int (*read)(struct synaptics_rmi4_data *rmi4_data, unsigned short addr, + unsigned char *data, unsigned int length); + int (*write)(struct synaptics_rmi4_data *rmi4_data, unsigned short addr, + unsigned char *data, unsigned int length); +}; + +struct synaptics_dsx_hw_interface { + struct synaptics_dsx_board_data *board_data; + const struct synaptics_dsx_bus_access *bus_access; + int (*bl_hw_init)(struct synaptics_rmi4_data *rmi4_data); + int (*ui_hw_init)(struct synaptics_rmi4_data *rmi4_data); +}; + +struct synaptics_rmi4_exp_fn { + enum exp_fn fn_type; + int (*init)(struct synaptics_rmi4_data *rmi4_data); + void (*remove)(struct synaptics_rmi4_data *rmi4_data); + void (*reset)(struct synaptics_rmi4_data *rmi4_data); + void (*reinit)(struct synaptics_rmi4_data *rmi4_data); + void (*early_suspend)(struct synaptics_rmi4_data *rmi4_data); + void (*suspend)(struct synaptics_rmi4_data *rmi4_data); + void (*resume)(struct synaptics_rmi4_data *rmi4_data); + void (*late_resume)(struct synaptics_rmi4_data *rmi4_data); + void (*attn)(struct synaptics_rmi4_data *rmi4_data, + unsigned char intr_mask); +}; + +int synaptics_rmi4_bus_init(void); + +void synaptics_rmi4_bus_exit(void); + +void synaptics_rmi4_new_function(struct synaptics_rmi4_exp_fn *exp_fn_module, + bool insert); + +int synaptics_fw_updater(const unsigned char *fw_data); + +static inline int synaptics_rmi4_reg_read( + struct synaptics_rmi4_data *rmi4_data, + unsigned short addr, + unsigned char *data, + unsigned int len) +{ + return rmi4_data->hw_if->bus_access->read(rmi4_data, addr, data, len); +} + +static inline int synaptics_rmi4_reg_write( + struct synaptics_rmi4_data *rmi4_data, + unsigned short addr, + unsigned char *data, + unsigned int len) +{ + return rmi4_data->hw_if->bus_access->write(rmi4_data, addr, data, len); +} + +static inline ssize_t synaptics_rmi4_show_error(struct device *dev, + struct device_attribute *attr, char *buf) +{ + dev_warn(dev, "%s Attempted to read from write-only attribute %s\n", + __func__, attr->attr.name); + return -EPERM; +} + +static inline ssize_t synaptics_rmi4_store_error(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + dev_warn(dev, "%s Attempted to write to read-only attribute %s\n", + __func__, attr->attr.name); + 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) + return -EINVAL; + + memcpy((void *)dest, (const void *)src, count); + + return 0; +} + +static inline void batohs(unsigned short *dest, unsigned char *src) +{ + *dest = src[1] * 0x100 + src[0]; +} + +static inline void hstoba(unsigned char *dest, unsigned short src) +{ + dest[0] = src % 0x100; + dest[1] = src / 0x100; +} + +#endif diff --git a/qcom/opensource/touch-drivers/synaptics_dsx/synaptics_dsx_fw_update.c b/qcom/opensource/touch-drivers/synaptics_dsx/synaptics_dsx_fw_update.c new file mode 100644 index 0000000000..52ca0ac86b --- /dev/null +++ b/qcom/opensource/touch-drivers/synaptics_dsx/synaptics_dsx_fw_update.c @@ -0,0 +1,5797 @@ +/* + * Synaptics DSX touchscreen driver + * + * Copyright (C) 2012-2016 Synaptics Incorporated. All rights reserved. + * + * Copyright (c) 2018-2021 The Linux Foundation. All rights reserved. + * Copyright (C) 2012 Alexandra Chin + * Copyright (C) 2012 Scott Lin + * + * 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 +#include +#include +#include +#include "synaptics_dsx_core.h" + +#define FW_IHEX_NAME "synaptics/startup_fw_update.bin" +#define FW_IMAGE_NAME "synaptics/startup_fw_update.img" + +#define ENABLE_SYS_REFLASH false +#define FORCE_UPDATE false +#define DO_LOCKDOWN false + +#define MAX_IMAGE_NAME_LEN 256 +#define MAX_FIRMWARE_ID_LEN 10 + +#define IMAGE_HEADER_VERSION_05 0x05 +#define IMAGE_HEADER_VERSION_06 0x06 +#define IMAGE_HEADER_VERSION_10 0x10 + +#define IMAGE_AREA_OFFSET 0x100 +#define LOCKDOWN_SIZE 0x50 + +#define MAX_UTILITY_PARAMS 20 + +#define V5V6_BOOTLOADER_ID_OFFSET 0 +#define V5V6_CONFIG_ID_SIZE 4 + +#define V5_PROPERTIES_OFFSET 2 +#define V5_BLOCK_SIZE_OFFSET 3 +#define V5_BLOCK_COUNT_OFFSET 5 +#define V5_BLOCK_NUMBER_OFFSET 0 +#define V5_BLOCK_DATA_OFFSET 2 + +#define V6_PROPERTIES_OFFSET 1 +#define V6_BLOCK_SIZE_OFFSET 2 +#define V6_BLOCK_COUNT_OFFSET 3 +#define V6_PROPERTIES_2_OFFSET 4 +#define V6_GUEST_CODE_BLOCK_COUNT_OFFSET 5 +#define V6_BLOCK_NUMBER_OFFSET 0 +#define V6_BLOCK_DATA_OFFSET 1 +#define V6_FLASH_COMMAND_OFFSET 2 +#define V6_FLASH_STATUS_OFFSET 3 + +#define V7_CONFIG_ID_SIZE 32 + +#define V7_FLASH_STATUS_OFFSET 0 +#define V7_PARTITION_ID_OFFSET 1 +#define V7_BLOCK_NUMBER_OFFSET 2 +#define V7_TRANSFER_LENGTH_OFFSET 3 +#define V7_COMMAND_OFFSET 4 +#define V7_PAYLOAD_OFFSET 5 + +#define V7_PARTITION_SUPPORT_BYTES 4 + +#define F35_ERROR_CODE_OFFSET 0 +#define F35_FLASH_STATUS_OFFSET 5 +#define F35_CHUNK_NUM_LSB_OFFSET 0 +#define F35_CHUNK_NUM_MSB_OFFSET 1 +#define F35_CHUNK_DATA_OFFSET 2 +#define F35_CHUNK_COMMAND_OFFSET 18 + +#define F35_CHUNK_SIZE 16 +#define F35_ERASE_ALL_WAIT_MS 5000 +#define F35_RESET_WAIT_MS 250 + +#define SLEEP_MODE_NORMAL (0x00) +#define SLEEP_MODE_SENSOR_SLEEP (0x01) +#define SLEEP_MODE_RESERVED0 (0x02) +#define SLEEP_MODE_RESERVED1 (0x03) + +#define ENABLE_WAIT_MS (1 * 1000) +#define WRITE_WAIT_MS (3 * 1000) +#define ERASE_WAIT_MS (5 * 1000) + +#define MIN_SLEEP_TIME_US 50 +#define MAX_SLEEP_TIME_US 100 + +#define INT_DISABLE_WAIT_MS 20 +#define ENTER_FLASH_PROG_WAIT_MS 20 +#define READ_CONFIG_WAIT_MS 20 + +static int fwu_do_reflash(void); + +static int fwu_recovery_check_status(void); + +#ifdef CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE_EXTRA_SYSFS +static ssize_t fwu_sysfs_show_image(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t fwu_sysfs_store_image(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t fwu_sysfs_do_recovery_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t fwu_sysfs_do_reflash_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t fwu_sysfs_write_config_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t fwu_sysfs_read_config_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t fwu_sysfs_config_area_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t fwu_sysfs_image_name_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t fwu_sysfs_image_size_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t fwu_sysfs_block_size_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t fwu_sysfs_firmware_block_count_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t fwu_sysfs_configuration_block_count_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t fwu_sysfs_disp_config_block_count_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t fwu_sysfs_perm_config_block_count_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t fwu_sysfs_bl_config_block_count_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t fwu_sysfs_utility_parameter_block_count_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t fwu_sysfs_guest_code_block_count_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t fwu_sysfs_write_guest_code_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +#ifdef SYNA_TDDI +static ssize_t fwu_sysfs_write_lockdown_code_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t fwu_sysfs_read_lockdown_code_show(struct device *dev, + struct device_attribute *attr, char *buf); +#endif + +#endif + +enum f34_version { + F34_V0 = 0, + F34_V1, + F34_V2, +}; + +enum bl_version { + BL_V5 = 5, + BL_V6 = 6, + BL_V7 = 7, + BL_V8 = 8, +}; + +enum flash_area { + NONE = 0, + UI_FIRMWARE, + UI_CONFIG, +}; + +enum update_mode { + NORMAL = 1, + FORCE = 2, + LOCKDOWN = 8, +}; + +enum config_area { + UI_CONFIG_AREA = 0, + PM_CONFIG_AREA, + BL_CONFIG_AREA, + DP_CONFIG_AREA, + FLASH_CONFIG_AREA, +#ifdef SYNA_TDDI + TDDI_FORCE_CONFIG_AREA, + TDDI_LCM_DATA_AREA, + TDDI_OEM_DATA_AREA, +#endif + UPP_AREA, +}; + +enum v7_status { + SUCCESS = 0x00, + DEVICE_NOT_IN_BOOTLOADER_MODE, + INVALID_PARTITION, + INVALID_COMMAND, + INVALID_BLOCK_OFFSET, + INVALID_TRANSFER, + NOT_ERASED, + FLASH_PROGRAMMING_KEY_INCORRECT, + BAD_PARTITION_TABLE, + CHECKSUM_FAILED, + FLASH_HARDWARE_FAILURE = 0x1f, +}; + +enum v7_partition_id { + BOOTLOADER_PARTITION = 0x01, + DEVICE_CONFIG_PARTITION, + FLASH_CONFIG_PARTITION, + MANUFACTURING_BLOCK_PARTITION, + GUEST_SERIALIZATION_PARTITION, + GLOBAL_PARAMETERS_PARTITION, + CORE_CODE_PARTITION, + CORE_CONFIG_PARTITION, + GUEST_CODE_PARTITION, + DISPLAY_CONFIG_PARTITION, + EXTERNAL_TOUCH_AFE_CONFIG_PARTITION, + UTILITY_PARAMETER_PARTITION, +}; + +enum v7_flash_command { + CMD_V7_IDLE = 0x00, + CMD_V7_ENTER_BL, + CMD_V7_READ, + CMD_V7_WRITE, + CMD_V7_ERASE, + CMD_V7_ERASE_AP, + CMD_V7_SENSOR_ID, +}; + +enum v5v6_flash_command { + CMD_V5V6_IDLE = 0x0, + CMD_V5V6_WRITE_FW = 0x2, + CMD_V5V6_ERASE_ALL = 0x3, + CMD_V5V6_WRITE_LOCKDOWN = 0x4, + CMD_V5V6_READ_CONFIG = 0x5, + CMD_V5V6_WRITE_CONFIG = 0x6, + CMD_V5V6_ERASE_UI_CONFIG = 0x7, + CMD_V5V6_ERASE_BL_CONFIG = 0x9, + CMD_V5V6_ERASE_DISP_CONFIG = 0xa, + CMD_V5V6_ERASE_GUEST_CODE = 0xb, + CMD_V5V6_WRITE_GUEST_CODE = 0xc, + CMD_V5V6_ERASE_CHIP = 0x0d, + CMD_V5V6_ENABLE_FLASH_PROG = 0xf, +#ifdef SYNA_TDDI + CMD_V5V6_ERASE_FORCE_CONFIG = 0x11, + CMD_V5V6_READ_FORCE_CONFIG = 0x12, + CMD_V5V6_WRITE_FORCE_CONFIG = 0x13, + CMD_V5V6_ERASE_LOCKDOWN_DATA = 0x1a, + CMD_V5V6_READ_LOCKDOWN_DATA = 0x1b, + CMD_V5V6_WRITE_LOCKDOWN_DATA = 0x1c, + CMD_V5V6_ERASE_LCM_DATA = 0x1d, + CMD_V5V6_ERASE_OEM_DATA = 0x1e, +#endif +}; + +enum flash_command { + CMD_IDLE = 0, + CMD_WRITE_FW, + CMD_WRITE_CONFIG, + CMD_WRITE_LOCKDOWN, + CMD_WRITE_GUEST_CODE, + CMD_WRITE_BOOTLOADER, + CMD_WRITE_UTILITY_PARAM, + CMD_READ_CONFIG, + CMD_ERASE_ALL, + CMD_ERASE_UI_FIRMWARE, + CMD_ERASE_UI_CONFIG, + CMD_ERASE_BL_CONFIG, + CMD_ERASE_DISP_CONFIG, + CMD_ERASE_FLASH_CONFIG, + CMD_ERASE_GUEST_CODE, + CMD_ERASE_BOOTLOADER, + CMD_ERASE_UTILITY_PARAMETER, + CMD_ENABLE_FLASH_PROG, +#ifdef SYNA_TDDI + CMD_ERASE_CHIP, + CMD_ERASE_FORCE_CONFIG, + CMD_READ_FORCE_CONFIG, + CMD_WRITE_FORCE_CONFIG, + CMD_ERASE_LOCKDOWN_DATA, + CMD_READ_LOCKDOWN_DATA, + CMD_WRITE_LOCKDOWN_DATA, + CMD_ERASE_LCM_DATA, + CMD_READ_LCM_DATA, + CMD_WRITE_LCM_DATA, + CMD_ERASE_OEM_DATA, + CMD_READ_OEM_DATA, + CMD_WRITE_OEM_DATA, +#endif +}; + +enum f35_flash_command { + CMD_F35_IDLE = 0x0, + CMD_F35_RESERVED = 0x1, + CMD_F35_WRITE_CHUNK = 0x2, + CMD_F35_ERASE_ALL = 0x3, + CMD_F35_RESET = 0x10, +}; + +enum container_id { + TOP_LEVEL_CONTAINER = 0, + UI_CONTAINER, + UI_CONFIG_CONTAINER, + BL_CONTAINER, + BL_IMAGE_CONTAINER, + BL_CONFIG_CONTAINER, + BL_LOCKDOWN_INFO_CONTAINER, + PERMANENT_CONFIG_CONTAINER, + GUEST_CODE_CONTAINER, + BL_PROTOCOL_DESCRIPTOR_CONTAINER, + UI_PROTOCOL_DESCRIPTOR_CONTAINER, + RMI_SELF_DISCOVERY_CONTAINER, + RMI_PAGE_CONTENT_CONTAINER, + GENERAL_INFORMATION_CONTAINER, + DEVICE_CONFIG_CONTAINER, + FLASH_CONFIG_CONTAINER, + GUEST_SERIALIZATION_CONTAINER, + GLOBAL_PARAMETERS_CONTAINER, + CORE_CODE_CONTAINER, + CORE_CONFIG_CONTAINER, + DISPLAY_CONFIG_CONTAINER, + EXTERNAL_TOUCH_AFE_CONFIG_CONTAINER, + UTILITY_CONTAINER, + UTILITY_PARAMETER_CONTAINER, +}; + +enum utility_parameter_id { + UNUSED = 0, + FORCE_PARAMETER, + ANTI_BENDING_PARAMETER, +}; + +struct pdt_properties { + union { + struct { + unsigned char reserved_1:6; + unsigned char has_bsr:1; + unsigned char reserved_2:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct partition_table { + unsigned char partition_id:5; + unsigned char byte_0_reserved:3; + unsigned char byte_1_reserved; + unsigned char partition_length_7_0; + unsigned char partition_length_15_8; + unsigned char start_physical_address_7_0; + unsigned char start_physical_address_15_8; + unsigned char partition_properties_7_0; + unsigned char partition_properties_15_8; +} __packed; + +struct f01_device_control { + union { + struct { + unsigned char sleep_mode:2; + unsigned char nosleep:1; + unsigned char reserved:2; + unsigned char charger_connected:1; + unsigned char report_rate:1; + unsigned char configured:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f34_v7_query_0 { + union { + struct { + unsigned char subpacket_1_size:3; + unsigned char has_config_id:1; + unsigned char f34_query0_b4:1; + unsigned char has_thqa:1; + unsigned char f34_query0_b6__7:2; + } __packed; + unsigned char data[1]; + }; +}; + +struct f34_v7_query_1_7 { + union { + struct { + /* query 1 */ + unsigned char bl_minor_revision; + unsigned char bl_major_revision; + + /* query 2 */ + unsigned char bl_fw_id_7_0; + unsigned char bl_fw_id_15_8; + unsigned char bl_fw_id_23_16; + unsigned char bl_fw_id_31_24; + + /* query 3 */ + unsigned char minimum_write_size; + unsigned char block_size_7_0; + unsigned char block_size_15_8; + unsigned char flash_page_size_7_0; + unsigned char flash_page_size_15_8; + + /* query 4 */ + unsigned char adjustable_partition_area_size_7_0; + unsigned char adjustable_partition_area_size_15_8; + + /* query 5 */ + unsigned char flash_config_length_7_0; + unsigned char flash_config_length_15_8; + + /* query 6 */ + unsigned char payload_length_7_0; + unsigned char payload_length_15_8; + + /* query 7 */ + unsigned char f34_query7_b0:1; + unsigned char has_bootloader:1; + unsigned char has_device_config:1; + unsigned char has_flash_config:1; + unsigned char has_manufacturing_block:1; + unsigned char has_guest_serialization:1; + unsigned char has_global_parameters:1; + unsigned char has_core_code:1; + unsigned char has_core_config:1; + unsigned char has_guest_code:1; + unsigned char has_display_config:1; + unsigned char f34_query7_b11__15:5; + unsigned char f34_query7_b16__23; + unsigned char f34_query7_b24__31; + } __packed; + unsigned char data[21]; + }; +}; + +struct f34_v7_data0 { + union { + struct { + unsigned char operation_status:5; + unsigned char device_cfg_status:2; + unsigned char bl_mode:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f34_v7_data_1_5 { + union { + struct { + unsigned char partition_id:5; + unsigned char f34_data1_b5__7:3; + unsigned char block_offset_7_0; + unsigned char block_offset_15_8; + unsigned char transfer_length_7_0; + unsigned char transfer_length_15_8; + unsigned char command; + unsigned char payload_0; + unsigned char payload_1; + } __packed; + unsigned char data[8]; + }; +}; + +struct f34_v5v6_flash_properties { + union { + struct { + unsigned char reg_map:1; + unsigned char unlocked:1; + unsigned char has_config_id:1; + unsigned char has_pm_config:1; + unsigned char has_bl_config:1; + unsigned char has_disp_config:1; + unsigned char has_ctrl1:1; + unsigned char has_query4:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f34_v5v6_flash_properties_2 { + union { + struct { + unsigned char has_guest_code:1; + unsigned char f34_query4_b1:1; + unsigned char has_gesture_config:1; + unsigned char has_force_config:1; + unsigned char has_lockdown_data:1; + unsigned char has_lcm_data:1; + unsigned char has_oem_data:1; + unsigned char f34_query4_b7:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct register_offset { + unsigned char properties; + unsigned char properties_2; + unsigned char block_size; + unsigned char block_count; + unsigned char gc_block_count; + unsigned char flash_status; + unsigned char partition_id; + unsigned char block_number; + unsigned char transfer_length; + unsigned char flash_cmd; + unsigned char payload; +}; + +struct block_count { + unsigned short ui_firmware; + unsigned short ui_config; + unsigned short dp_config; + unsigned short pm_config; + unsigned short fl_config; + unsigned short bl_image; + unsigned short bl_config; + unsigned short utility_param; + unsigned short lockdown; + unsigned short guest_code; +#ifdef SYNA_TDDI + unsigned short tddi_force_config; + unsigned short tddi_lockdown_data; + unsigned short tddi_lcm_data; + unsigned short tddi_oem_data; +#endif + unsigned short total_count; +}; + +struct physical_address { + unsigned short ui_firmware; + unsigned short ui_config; + unsigned short dp_config; + unsigned short pm_config; + unsigned short fl_config; + unsigned short bl_image; + unsigned short bl_config; + unsigned short utility_param; + unsigned short lockdown; + unsigned short guest_code; +}; + +struct container_descriptor { + unsigned char content_checksum[4]; + unsigned char container_id[2]; + unsigned char minor_version; + unsigned char major_version; + unsigned char reserved_08; + unsigned char reserved_09; + unsigned char reserved_0a; + unsigned char reserved_0b; + unsigned char container_option_flags[4]; + unsigned char content_options_length[4]; + unsigned char content_options_address[4]; + unsigned char content_length[4]; + unsigned char content_address[4]; +}; + +struct image_header_10 { + unsigned char checksum[4]; + unsigned char reserved_04; + unsigned char reserved_05; + unsigned char minor_header_version; + unsigned char major_header_version; + unsigned char reserved_08; + unsigned char reserved_09; + unsigned char reserved_0a; + unsigned char reserved_0b; + unsigned char top_level_container_start_addr[4]; +}; + +struct image_header_05_06 { + /* 0x00 - 0x0f */ + unsigned char checksum[4]; + unsigned char reserved_04; + unsigned char reserved_05; + unsigned char options_firmware_id:1; + unsigned char options_bootloader:1; + unsigned char options_guest_code:1; + unsigned char options_tddi:1; + unsigned char options_reserved:4; + unsigned char header_version; + unsigned char firmware_size[4]; + unsigned char config_size[4]; + /* 0x10 - 0x1f */ + unsigned char product_id[PRODUCT_ID_SIZE]; + unsigned char package_id[2]; + unsigned char package_id_revision[2]; + unsigned char product_info[PRODUCT_INFO_SIZE]; + /* 0x20 - 0x2f */ + unsigned char bootloader_addr[4]; + unsigned char bootloader_size[4]; + unsigned char ui_addr[4]; + unsigned char ui_size[4]; + /* 0x30 - 0x3f */ + unsigned char ds_id[16]; + /* 0x40 - 0x4f */ + union { + struct { + unsigned char cstmr_product_id[PRODUCT_ID_SIZE]; + unsigned char reserved_4a_4f[6]; + }; + struct { + unsigned char dsp_cfg_addr[4]; + unsigned char dsp_cfg_size[4]; + unsigned char reserved_48_4f[8]; + }; + }; + /* 0x50 - 0x53 */ + unsigned char firmware_id[4]; +}; + +struct block_data { + unsigned int size; + const unsigned char *data; +}; + +struct image_metadata { + bool contains_firmware_id; + bool contains_bootloader; + bool contains_guest_code; + bool contains_disp_config; + bool contains_perm_config; + bool contains_flash_config; + bool contains_utility_param; + unsigned int firmware_id; + unsigned int checksum; + unsigned int bootloader_size; + unsigned int disp_config_offset; + unsigned char bl_version; + unsigned char product_id[PRODUCT_ID_SIZE + 1]; + unsigned char cstmr_product_id[PRODUCT_ID_SIZE + 1]; + unsigned char utility_param_id[MAX_UTILITY_PARAMS]; + struct block_data bootloader; + struct block_data utility; + struct block_data ui_firmware; + struct block_data ui_config; + struct block_data dp_config; + struct block_data pm_config; + struct block_data fl_config; + struct block_data bl_image; + struct block_data bl_config; + struct block_data utility_param[MAX_UTILITY_PARAMS]; + struct block_data lockdown; + struct block_data guest_code; + struct block_count blkcount; + struct physical_address phyaddr; +}; + +struct synaptics_rmi4_fwu_handle { + enum bl_version bl_version; + bool initialized; + bool in_bl_mode; + bool in_ub_mode; + bool bl_mode_device; + bool force_update; + bool do_lockdown; + bool has_guest_code; +#ifdef SYNA_TDDI + bool has_force_config; + bool has_lockdown_data; + bool has_lcm_data; + bool has_oem_data; +#endif + bool has_utility_param; + bool new_partition_table; + bool incompatible_partition_tables; + bool write_bootloader; + unsigned int data_pos; + unsigned char *ext_data_source; + unsigned char *read_config_buf; + unsigned char intr_mask; + unsigned char command; + unsigned char bootloader_id[2]; + unsigned char config_id[32]; + unsigned char flash_status; + unsigned char partitions; +#ifdef F51_DISCRETE_FORCE + unsigned char *cal_data; + unsigned short cal_data_off; + unsigned short cal_data_size; + unsigned short cal_data_buf_size; + unsigned short cal_packet_data_size; +#endif + unsigned short block_size; + unsigned short config_size; + unsigned short config_area; + unsigned short config_block_count; + unsigned short flash_config_length; + unsigned short payload_length; + unsigned short partition_table_bytes; + unsigned short read_config_buf_size; + const unsigned char *config_data; + const unsigned char *image; + unsigned char *image_name; + unsigned int image_size; + struct image_metadata img; + struct register_offset off; + struct block_count blkcount; + struct physical_address phyaddr; + struct f34_v5v6_flash_properties flash_properties; + struct synaptics_rmi4_fn_desc f34_fd; + struct synaptics_rmi4_fn_desc f35_fd; + struct synaptics_rmi4_data *rmi4_data; + struct workqueue_struct *fwu_workqueue; + struct work_struct fwu_work; +}; + +#ifdef CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE_EXTRA_SYSFS +static struct bin_attribute dev_attr_data = { + .attr = { + .name = "data", + .mode = 0664, + }, + .size = 0, + .read = fwu_sysfs_show_image, + .write = fwu_sysfs_store_image, +}; +#endif + +static struct device_attribute attrs[] = { +#ifdef CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE_EXTRA_SYSFS + __ATTR(dorecovery, 0220, + synaptics_rmi4_show_error, + fwu_sysfs_do_recovery_store), + __ATTR(doreflash, 0220, + synaptics_rmi4_show_error, + fwu_sysfs_do_reflash_store), + __ATTR(writeconfig, 0220, + synaptics_rmi4_show_error, + fwu_sysfs_write_config_store), + __ATTR(readconfig, 0220, + synaptics_rmi4_show_error, + fwu_sysfs_read_config_store), + __ATTR(configarea, 0220, + synaptics_rmi4_show_error, + fwu_sysfs_config_area_store), + __ATTR(imagename, 0220, + synaptics_rmi4_show_error, + fwu_sysfs_image_name_store), + __ATTR(imagesize, 0220, + synaptics_rmi4_show_error, + fwu_sysfs_image_size_store), + __ATTR(blocksize, 0444, + fwu_sysfs_block_size_show, + synaptics_rmi4_store_error), + __ATTR(fwblockcount, 0444, + fwu_sysfs_firmware_block_count_show, + synaptics_rmi4_store_error), + __ATTR(configblockcount, 0444, + fwu_sysfs_configuration_block_count_show, + synaptics_rmi4_store_error), + __ATTR(dispconfigblockcount, 0444, + fwu_sysfs_disp_config_block_count_show, + synaptics_rmi4_store_error), + __ATTR(permconfigblockcount, 0444, + fwu_sysfs_perm_config_block_count_show, + synaptics_rmi4_store_error), + __ATTR(blconfigblockcount, 0444, + fwu_sysfs_bl_config_block_count_show, + synaptics_rmi4_store_error), + __ATTR(uppblockcount, 0444, + fwu_sysfs_utility_parameter_block_count_show, + synaptics_rmi4_store_error), + __ATTR(guestcodeblockcount, 0444, + fwu_sysfs_guest_code_block_count_show, + synaptics_rmi4_store_error), + __ATTR(writeguestcode, 0220, + synaptics_rmi4_show_error, + fwu_sysfs_write_guest_code_store), +#ifdef SYNA_TDDI + __ATTR(lockdowncode, 0664, + fwu_sysfs_read_lockdown_code_show, + fwu_sysfs_write_lockdown_code_store), +#endif +#endif +}; + +static struct synaptics_rmi4_fwu_handle *fwu; + +DECLARE_COMPLETION(fwu_remove_complete); + +#ifdef CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE_EXTRA_SYSFS +DEFINE_MUTEX(fwu_sysfs_mutex); +#endif + +static void calculate_checksum(unsigned short *data, unsigned long len, + unsigned long *result) +{ + unsigned long temp; + unsigned long sum1 = 0xffff; + unsigned long sum2 = 0xffff; + + *result = 0xffffffff; + + while (len--) { + temp = *data; + sum1 += temp; + sum2 += sum1; + sum1 = (sum1 & 0xffff) + (sum1 >> 16); + sum2 = (sum2 & 0xffff) + (sum2 >> 16); + data++; + } + + *result = sum2 << 16 | sum1; +} + +static void convert_to_little_endian(unsigned char *dest, unsigned long src) +{ + dest[0] = (unsigned char)(src & 0xff); + dest[1] = (unsigned char)((src >> 8) & 0xff); + dest[2] = (unsigned char)((src >> 16) & 0xff); + dest[3] = (unsigned char)((src >> 24) & 0xff); +} + +static unsigned int le_to_uint(const unsigned char *ptr) +{ + return (unsigned int)ptr[0] + + (unsigned int)ptr[1] * 0x100 + + (unsigned int)ptr[2] * 0x10000 + + (unsigned int)ptr[3] * 0x1000000; +} + +#ifdef F51_DISCRETE_FORCE +static int fwu_f51_force_data_init(void) +{ + int retval; + unsigned char query_count; + unsigned char packet_info; + unsigned char offset[2]; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f51_query_base_addr + 7, + offset, + sizeof(offset)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read force data offset\n", + __func__); + return retval; + } + + fwu->cal_data_off = offset[0] | offset[1] << 8; + + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f51_query_base_addr, + &query_count, + sizeof(query_count)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read number of F51 query registers\n", + __func__); + return retval; + } + + if (query_count >= 10) { + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f51_query_base_addr + 9, + &packet_info, + sizeof(packet_info)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read F51 packet register info\n", + __func__); + return retval; + } + + if (packet_info & MASK_1BIT) { + fwu->cal_packet_data_size = packet_info >> 1; + fwu->cal_packet_data_size *= 2; + } else { + fwu->cal_packet_data_size = 0; + } + } else { + fwu->cal_packet_data_size = 0; + } + + fwu->cal_data_size = CAL_DATA_SIZE + fwu->cal_packet_data_size; + if (fwu->cal_data_size > fwu->cal_data_buf_size) { + kfree(fwu->cal_data); + fwu->cal_data_buf_size = fwu->cal_data_size; + fwu->cal_data = kmalloc(fwu->cal_data_buf_size, GFP_KERNEL); + if (!fwu->cal_data) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for fwu->cal_data\n", + __func__); + fwu->cal_data_buf_size = 0; + return -ENOMEM; + } + } + + return 0; +} +#endif + +static int fwu_allocate_read_config_buf(unsigned int count) +{ + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + if (count > fwu->read_config_buf_size) { + kfree(fwu->read_config_buf); + fwu->read_config_buf = kzalloc(count, GFP_KERNEL); + if (!fwu->read_config_buf) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for fwu->read_config_buf\n", + __func__); + fwu->read_config_buf_size = 0; + return -ENOMEM; + } + fwu->read_config_buf_size = count; + } + + return 0; +} + +static void fwu_compare_partition_tables(void) +{ + fwu->incompatible_partition_tables = false; + + if (fwu->phyaddr.bl_image != fwu->img.phyaddr.bl_image) + fwu->incompatible_partition_tables = true; + else if (fwu->phyaddr.lockdown != fwu->img.phyaddr.lockdown) + fwu->incompatible_partition_tables = true; + else if (fwu->phyaddr.bl_config != fwu->img.phyaddr.bl_config) + fwu->incompatible_partition_tables = true; + else if (fwu->phyaddr.utility_param != fwu->img.phyaddr.utility_param) + fwu->incompatible_partition_tables = true; + + if (fwu->bl_version == BL_V7) { + if (fwu->phyaddr.fl_config != fwu->img.phyaddr.fl_config) + fwu->incompatible_partition_tables = true; + } + + fwu->new_partition_table = false; + + if (fwu->phyaddr.ui_firmware != fwu->img.phyaddr.ui_firmware) + fwu->new_partition_table = true; + else if (fwu->phyaddr.ui_config != fwu->img.phyaddr.ui_config) + fwu->new_partition_table = true; + + if (fwu->flash_properties.has_disp_config) { + if (fwu->phyaddr.dp_config != fwu->img.phyaddr.dp_config) + fwu->new_partition_table = true; + } + + if (fwu->has_guest_code) { + if (fwu->phyaddr.guest_code != fwu->img.phyaddr.guest_code) + fwu->new_partition_table = true; + } +} + +static void fwu_parse_partition_table(const unsigned char *partition_table, + struct block_count *blkcount, struct physical_address *phyaddr) +{ + unsigned char ii; + unsigned char index; + unsigned char offset; + unsigned short partition_length; + unsigned short physical_address; + struct partition_table *ptable; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + for (ii = 0; ii < fwu->partitions; ii++) { + index = ii * 8 + 2; + ptable = (struct partition_table *)&partition_table[index]; + partition_length = ptable->partition_length_15_8 << 8 | + ptable->partition_length_7_0; + physical_address = ptable->start_physical_address_15_8 << 8 | + ptable->start_physical_address_7_0; + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Partition entry %d:\n", + __func__, ii); + for (offset = 0; offset < 8; offset++) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: 0x%02x\n", + __func__, + partition_table[index + offset]); + } + switch (ptable->partition_id) { + case CORE_CODE_PARTITION: + blkcount->ui_firmware = partition_length; + phyaddr->ui_firmware = physical_address; + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Core code block count: %d\n", + __func__, blkcount->ui_firmware); + blkcount->total_count += partition_length; + break; + case CORE_CONFIG_PARTITION: + blkcount->ui_config = partition_length; + phyaddr->ui_config = physical_address; + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Core config block count: %d\n", + __func__, blkcount->ui_config); + blkcount->total_count += partition_length; + break; + case BOOTLOADER_PARTITION: + blkcount->bl_image = partition_length; + phyaddr->bl_image = physical_address; + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Bootloader block count: %d\n", + __func__, blkcount->bl_image); + blkcount->total_count += partition_length; + break; + case UTILITY_PARAMETER_PARTITION: + blkcount->utility_param = partition_length; + phyaddr->utility_param = physical_address; + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Utility parameter block count: %d\n", + __func__, blkcount->utility_param); + blkcount->total_count += partition_length; + break; + case DISPLAY_CONFIG_PARTITION: + blkcount->dp_config = partition_length; + phyaddr->dp_config = physical_address; + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Display config block count: %d\n", + __func__, blkcount->dp_config); + blkcount->total_count += partition_length; + break; + case FLASH_CONFIG_PARTITION: + blkcount->fl_config = partition_length; + phyaddr->fl_config = physical_address; + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Flash config block count: %d\n", + __func__, blkcount->fl_config); + blkcount->total_count += partition_length; + break; + case GUEST_CODE_PARTITION: + blkcount->guest_code = partition_length; + phyaddr->guest_code = physical_address; + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Guest code block count: %d\n", + __func__, blkcount->guest_code); + blkcount->total_count += partition_length; + break; + case GUEST_SERIALIZATION_PARTITION: + blkcount->pm_config = partition_length; + phyaddr->pm_config = physical_address; + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Guest serialization block count: %d\n", + __func__, blkcount->pm_config); + blkcount->total_count += partition_length; + break; + case GLOBAL_PARAMETERS_PARTITION: + blkcount->bl_config = partition_length; + phyaddr->bl_config = physical_address; + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Global parameters block count: %d\n", + __func__, blkcount->bl_config); + blkcount->total_count += partition_length; + break; + case DEVICE_CONFIG_PARTITION: + blkcount->lockdown = partition_length; + phyaddr->lockdown = physical_address; + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Device config block count: %d\n", + __func__, blkcount->lockdown); + blkcount->total_count += partition_length; + break; + }; + } +} + +static void fwu_parse_image_header_10_utility(const unsigned char *image) +{ + unsigned char ii; + unsigned char num_of_containers; + unsigned int addr; + unsigned int container_id; + unsigned int length; + const unsigned char *content; + struct container_descriptor *descriptor; + + num_of_containers = fwu->img.utility.size / 4; + + for (ii = 0; ii < num_of_containers; ii++) { + if (ii >= MAX_UTILITY_PARAMS) + continue; + addr = le_to_uint(fwu->img.utility.data + (ii * 4)); + descriptor = (struct container_descriptor *)(image + addr); + container_id = descriptor->container_id[0] | + descriptor->container_id[1] << 8; + content = image + le_to_uint(descriptor->content_address); + length = le_to_uint(descriptor->content_length); + switch (container_id) { + case UTILITY_PARAMETER_CONTAINER: + fwu->img.utility_param[ii].data = content; + fwu->img.utility_param[ii].size = length; + fwu->img.utility_param_id[ii] = content[0]; + break; + default: + break; + }; + } +} + +static void fwu_parse_image_header_10_bootloader(const unsigned char *image) +{ + unsigned char ii; + unsigned char num_of_containers; + unsigned int addr; + unsigned int container_id; + unsigned int length; + const unsigned char *content; + struct container_descriptor *descriptor; + + num_of_containers = (fwu->img.bootloader.size - 4) / 4; + + for (ii = 1; ii <= num_of_containers; ii++) { + addr = le_to_uint(fwu->img.bootloader.data + (ii * 4)); + descriptor = (struct container_descriptor *)(image + addr); + container_id = descriptor->container_id[0] | + descriptor->container_id[1] << 8; + content = image + le_to_uint(descriptor->content_address); + length = le_to_uint(descriptor->content_length); + switch (container_id) { + case BL_IMAGE_CONTAINER: + fwu->img.bl_image.data = content; + fwu->img.bl_image.size = length; + break; + case BL_CONFIG_CONTAINER: + case GLOBAL_PARAMETERS_CONTAINER: + fwu->img.bl_config.data = content; + fwu->img.bl_config.size = length; + break; + case BL_LOCKDOWN_INFO_CONTAINER: + case DEVICE_CONFIG_CONTAINER: + fwu->img.lockdown.data = content; + fwu->img.lockdown.size = length; + break; + default: + break; + }; + } +} + +static void fwu_parse_image_header_10(void) +{ + unsigned char ii; + unsigned char num_of_containers; + unsigned int addr; + unsigned int offset; + unsigned int container_id; + unsigned int length; + const unsigned char *image; + const unsigned char *content; + struct container_descriptor *descriptor; + struct image_header_10 *header; + + image = fwu->image; + header = (struct image_header_10 *)image; + + fwu->img.checksum = le_to_uint(header->checksum); + + /* address of top level container */ + offset = le_to_uint(header->top_level_container_start_addr); + descriptor = (struct container_descriptor *)(image + offset); + + /* address of top level container content */ + offset = le_to_uint(descriptor->content_address); + num_of_containers = le_to_uint(descriptor->content_length) / 4; + + for (ii = 0; ii < num_of_containers; ii++) { + addr = le_to_uint(image + offset); + offset += 4; + descriptor = (struct container_descriptor *)(image + addr); + container_id = descriptor->container_id[0] | + descriptor->container_id[1] << 8; + content = image + le_to_uint(descriptor->content_address); + length = le_to_uint(descriptor->content_length); + switch (container_id) { + case UI_CONTAINER: + case CORE_CODE_CONTAINER: + fwu->img.ui_firmware.data = content; + fwu->img.ui_firmware.size = length; + break; + case UI_CONFIG_CONTAINER: + case CORE_CONFIG_CONTAINER: + fwu->img.ui_config.data = content; + fwu->img.ui_config.size = length; + break; + case BL_CONTAINER: + fwu->img.bl_version = *content; + fwu->img.bootloader.data = content; + fwu->img.bootloader.size = length; + fwu_parse_image_header_10_bootloader(image); + break; + case UTILITY_CONTAINER: + fwu->img.utility.data = content; + fwu->img.utility.size = length; + fwu_parse_image_header_10_utility(image); + break; + case GUEST_CODE_CONTAINER: + fwu->img.contains_guest_code = true; + fwu->img.guest_code.data = content; + fwu->img.guest_code.size = length; + break; + case DISPLAY_CONFIG_CONTAINER: + fwu->img.contains_disp_config = true; + fwu->img.dp_config.data = content; + fwu->img.dp_config.size = length; + break; + case PERMANENT_CONFIG_CONTAINER: + case GUEST_SERIALIZATION_CONTAINER: + fwu->img.contains_perm_config = true; + fwu->img.pm_config.data = content; + fwu->img.pm_config.size = length; + break; + case FLASH_CONFIG_CONTAINER: + fwu->img.contains_flash_config = true; + fwu->img.fl_config.data = content; + fwu->img.fl_config.size = length; + break; + case GENERAL_INFORMATION_CONTAINER: + fwu->img.contains_firmware_id = true; + fwu->img.firmware_id = le_to_uint(content + 4); + break; + default: + break; + } + } +} + +static void fwu_parse_image_header_05_06(void) +{ + int retval; + const unsigned char *image; + struct image_header_05_06 *header; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + image = fwu->image; + header = (struct image_header_05_06 *)image; + + fwu->img.checksum = le_to_uint(header->checksum); + + fwu->img.bl_version = header->header_version; + + fwu->img.contains_bootloader = header->options_bootloader; + if (fwu->img.contains_bootloader) + fwu->img.bootloader_size = le_to_uint(header->bootloader_size); + + fwu->img.ui_firmware.size = le_to_uint(header->firmware_size); + if (fwu->img.ui_firmware.size) { + fwu->img.ui_firmware.data = image + IMAGE_AREA_OFFSET; + if (fwu->img.contains_bootloader) + fwu->img.ui_firmware.data += fwu->img.bootloader_size; + } + + if ((fwu->img.bl_version == BL_V6) && header->options_tddi) + fwu->img.ui_firmware.data = image + IMAGE_AREA_OFFSET; + + fwu->img.ui_config.size = le_to_uint(header->config_size); + if (fwu->img.ui_config.size) { + fwu->img.ui_config.data = fwu->img.ui_firmware.data + + fwu->img.ui_firmware.size; + } + + if (fwu->img.contains_bootloader || header->options_tddi) + fwu->img.contains_disp_config = true; + else + fwu->img.contains_disp_config = false; + + if (fwu->img.contains_disp_config) { + fwu->img.disp_config_offset = le_to_uint(header->dsp_cfg_addr); + fwu->img.dp_config.size = le_to_uint(header->dsp_cfg_size); + fwu->img.dp_config.data = image + fwu->img.disp_config_offset; + } else { + retval = secure_memcpy(fwu->img.cstmr_product_id, + sizeof(fwu->img.cstmr_product_id), + header->cstmr_product_id, + sizeof(header->cstmr_product_id), + PRODUCT_ID_SIZE); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy custom product ID string\n", + __func__); + } + fwu->img.cstmr_product_id[PRODUCT_ID_SIZE] = 0; + } + + fwu->img.contains_firmware_id = header->options_firmware_id; + if (fwu->img.contains_firmware_id) + fwu->img.firmware_id = le_to_uint(header->firmware_id); + + retval = secure_memcpy(fwu->img.product_id, + sizeof(fwu->img.product_id), + header->product_id, + sizeof(header->product_id), + PRODUCT_ID_SIZE); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy product ID string\n", + __func__); + } + fwu->img.product_id[PRODUCT_ID_SIZE] = 0; + + fwu->img.lockdown.size = LOCKDOWN_SIZE; + fwu->img.lockdown.data = image + IMAGE_AREA_OFFSET - LOCKDOWN_SIZE; +} + +static int fwu_parse_image_info(void) +{ + struct image_header_10 *header; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + header = (struct image_header_10 *)fwu->image; + + memset(&fwu->img, 0x00, sizeof(fwu->img)); + + switch (header->major_header_version) { + case IMAGE_HEADER_VERSION_10: + fwu_parse_image_header_10(); + break; + case IMAGE_HEADER_VERSION_05: + case IMAGE_HEADER_VERSION_06: + fwu_parse_image_header_05_06(); + break; + default: + dev_err(rmi4_data->pdev->dev.parent, + "%s: Unsupported image file format (0x%02x)\n", + __func__, header->major_header_version); + return -EINVAL; + } + + if (fwu->bl_version == BL_V7 || fwu->bl_version == BL_V8) { + if (!fwu->img.contains_flash_config) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: No flash config found in firmware image\n", + __func__); + return -EINVAL; + } + + fwu_parse_partition_table(fwu->img.fl_config.data, + &fwu->img.blkcount, &fwu->img.phyaddr); + + if (fwu->img.blkcount.utility_param) + fwu->img.contains_utility_param = true; + + fwu_compare_partition_tables(); + } else { + fwu->new_partition_table = false; + fwu->incompatible_partition_tables = false; + } + + return 0; +} + +static int fwu_read_flash_status(void) +{ + int retval; + unsigned char status; + unsigned char command; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fwu->f34_fd.data_base_addr + fwu->off.flash_status, + &status, + sizeof(status)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read flash status\n", + __func__); + return retval; + } + + fwu->in_bl_mode = status >> 7; + + if (fwu->bl_version == BL_V5) + fwu->flash_status = (status >> 4) & MASK_3BIT; + else if (fwu->bl_version == BL_V6) + fwu->flash_status = status & MASK_3BIT; + else if (fwu->bl_version == BL_V7 || fwu->bl_version == BL_V8) + fwu->flash_status = status & MASK_5BIT; + + if (fwu->write_bootloader) + fwu->flash_status = 0x00; + + if (fwu->flash_status != 0x00) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Flash status = %d, command = 0x%02x\n", + __func__, fwu->flash_status, fwu->command); + } + + if (fwu->bl_version == BL_V7 || fwu->bl_version == BL_V8) { + if (fwu->flash_status == 0x08) + fwu->flash_status = 0x00; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + fwu->f34_fd.data_base_addr + fwu->off.flash_cmd, + &command, + sizeof(command)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read flash command\n", + __func__); + return retval; + } + + if (fwu->bl_version == BL_V5) + fwu->command = command & MASK_4BIT; + else if (fwu->bl_version == BL_V6) + fwu->command = command & MASK_6BIT; + else if (fwu->bl_version == BL_V7 || fwu->bl_version == BL_V8) + fwu->command = command; + + if (fwu->write_bootloader) + fwu->command = 0x00; + + return 0; +} + +static int fwu_wait_for_idle(int timeout_ms, bool poll) +{ + int count = 0; + int timeout_count = ((timeout_ms * 1000) / MAX_SLEEP_TIME_US) + 1; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + do { + usleep_range(MIN_SLEEP_TIME_US, MAX_SLEEP_TIME_US); + + count++; + if (poll || (count == timeout_count)) + fwu_read_flash_status(); + + if ((fwu->command == CMD_IDLE) && (fwu->flash_status == 0x00)) + return 0; + } while (count < timeout_count); + + dev_err(rmi4_data->pdev->dev.parent, + "%s: Timed out waiting for idle status\n", + __func__); + + return -ETIMEDOUT; +} + +static int fwu_write_f34_v7_command_single_transaction(unsigned char cmd) +{ + int retval; + unsigned char data_base; + struct f34_v7_data_1_5 data_1_5; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + data_base = fwu->f34_fd.data_base_addr; + + memset(data_1_5.data, 0x00, sizeof(data_1_5.data)); + + switch (cmd) { + case CMD_ERASE_ALL: + data_1_5.partition_id = CORE_CODE_PARTITION; + data_1_5.command = CMD_V7_ERASE_AP; + break; + case CMD_ERASE_UI_FIRMWARE: + data_1_5.partition_id = CORE_CODE_PARTITION; + data_1_5.command = CMD_V7_ERASE; + break; + case CMD_ERASE_BL_CONFIG: + data_1_5.partition_id = GLOBAL_PARAMETERS_PARTITION; + data_1_5.command = CMD_V7_ERASE; + break; + case CMD_ERASE_UI_CONFIG: + data_1_5.partition_id = CORE_CONFIG_PARTITION; + data_1_5.command = CMD_V7_ERASE; + break; + case CMD_ERASE_DISP_CONFIG: + data_1_5.partition_id = DISPLAY_CONFIG_PARTITION; + data_1_5.command = CMD_V7_ERASE; + break; + case CMD_ERASE_FLASH_CONFIG: + data_1_5.partition_id = FLASH_CONFIG_PARTITION; + data_1_5.command = CMD_V7_ERASE; + break; + case CMD_ERASE_GUEST_CODE: + data_1_5.partition_id = GUEST_CODE_PARTITION; + data_1_5.command = CMD_V7_ERASE; + break; + case CMD_ERASE_BOOTLOADER: + data_1_5.partition_id = BOOTLOADER_PARTITION; + data_1_5.command = CMD_V7_ERASE; + break; + case CMD_ERASE_UTILITY_PARAMETER: + data_1_5.partition_id = UTILITY_PARAMETER_PARTITION; + data_1_5.command = CMD_V7_ERASE; + break; + case CMD_ENABLE_FLASH_PROG: + data_1_5.partition_id = BOOTLOADER_PARTITION; + data_1_5.command = CMD_V7_ENTER_BL; + break; + }; + + data_1_5.payload_0 = fwu->bootloader_id[0]; + data_1_5.payload_1 = fwu->bootloader_id[1]; + + retval = synaptics_rmi4_reg_write(rmi4_data, + data_base + fwu->off.partition_id, + data_1_5.data, + sizeof(data_1_5.data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write single transaction command\n", + __func__); + return retval; + } + + return 0; +} + +static int fwu_write_f34_v7_command(unsigned char cmd) +{ + int retval; + unsigned char data_base; + unsigned char command; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + data_base = fwu->f34_fd.data_base_addr; + + switch (cmd) { + case CMD_WRITE_FW: + case CMD_WRITE_CONFIG: + case CMD_WRITE_LOCKDOWN: + case CMD_WRITE_GUEST_CODE: + case CMD_WRITE_BOOTLOADER: + case CMD_WRITE_UTILITY_PARAM: + command = CMD_V7_WRITE; + break; + case CMD_READ_CONFIG: + command = CMD_V7_READ; + break; + case CMD_ERASE_ALL: + command = CMD_V7_ERASE_AP; + break; + case CMD_ERASE_UI_FIRMWARE: + case CMD_ERASE_BL_CONFIG: + case CMD_ERASE_UI_CONFIG: + case CMD_ERASE_DISP_CONFIG: + case CMD_ERASE_FLASH_CONFIG: + case CMD_ERASE_GUEST_CODE: + case CMD_ERASE_BOOTLOADER: + case CMD_ERASE_UTILITY_PARAMETER: + command = CMD_V7_ERASE; + break; + case CMD_ENABLE_FLASH_PROG: + command = CMD_V7_ENTER_BL; + break; + default: + dev_err(rmi4_data->pdev->dev.parent, + "%s: Invalid command 0x%02x\n", + __func__, cmd); + return -EINVAL; + }; + + fwu->command = command; + + switch (cmd) { + case CMD_ERASE_ALL: + case CMD_ERASE_UI_FIRMWARE: + case CMD_ERASE_BL_CONFIG: + case CMD_ERASE_UI_CONFIG: + case CMD_ERASE_DISP_CONFIG: + case CMD_ERASE_FLASH_CONFIG: + case CMD_ERASE_GUEST_CODE: + case CMD_ERASE_BOOTLOADER: + case CMD_ERASE_UTILITY_PARAMETER: + case CMD_ENABLE_FLASH_PROG: + retval = fwu_write_f34_v7_command_single_transaction(cmd); + if (retval < 0) + return retval; + else + return 0; + default: + break; + }; + + retval = synaptics_rmi4_reg_write(rmi4_data, + data_base + fwu->off.flash_cmd, + &command, + sizeof(command)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write flash command\n", + __func__); + return retval; + } + + return 0; +} + +static int fwu_write_f34_v5v6_command(unsigned char cmd) +{ + int retval; + unsigned char data_base; + unsigned char command; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + data_base = fwu->f34_fd.data_base_addr; + + switch (cmd) { + case CMD_IDLE: + command = CMD_V5V6_IDLE; + break; + case CMD_WRITE_FW: + command = CMD_V5V6_WRITE_FW; + break; + case CMD_WRITE_CONFIG: + command = CMD_V5V6_WRITE_CONFIG; + break; + case CMD_WRITE_LOCKDOWN: + command = CMD_V5V6_WRITE_LOCKDOWN; + break; + case CMD_WRITE_GUEST_CODE: + command = CMD_V5V6_WRITE_GUEST_CODE; + break; + case CMD_READ_CONFIG: + command = CMD_V5V6_READ_CONFIG; + break; + case CMD_ERASE_ALL: + command = CMD_V5V6_ERASE_ALL; + break; + case CMD_ERASE_UI_CONFIG: + command = CMD_V5V6_ERASE_UI_CONFIG; + break; + case CMD_ERASE_DISP_CONFIG: + command = CMD_V5V6_ERASE_DISP_CONFIG; + break; + case CMD_ERASE_GUEST_CODE: + command = CMD_V5V6_ERASE_GUEST_CODE; + break; + case CMD_ENABLE_FLASH_PROG: + command = CMD_V5V6_ENABLE_FLASH_PROG; + break; +#ifdef SYNA_TDDI + case CMD_ERASE_CHIP: + command = CMD_V5V6_ERASE_CHIP; + break; + case CMD_ERASE_FORCE_CONFIG: + command = CMD_V5V6_ERASE_FORCE_CONFIG; + break; + case CMD_READ_FORCE_CONFIG: + command = CMD_V5V6_READ_FORCE_CONFIG; + break; + case CMD_WRITE_FORCE_CONFIG: + command = CMD_V5V6_WRITE_CONFIG; + break; + case CMD_ERASE_LOCKDOWN_DATA: + command = CMD_V5V6_ERASE_LOCKDOWN_DATA; + break; + case CMD_READ_LOCKDOWN_DATA: + command = CMD_V5V6_READ_LOCKDOWN_DATA; + break; + case CMD_WRITE_LOCKDOWN_DATA: + command = CMD_V5V6_WRITE_LOCKDOWN_DATA; + break; + case CMD_ERASE_LCM_DATA: + command = CMD_V5V6_ERASE_LCM_DATA; + break; + case CMD_ERASE_OEM_DATA: + command = CMD_V5V6_ERASE_OEM_DATA; + break; +#endif + default: + dev_err(rmi4_data->pdev->dev.parent, + "%s: Invalid command 0x%02x\n", + __func__, cmd); + return -EINVAL; + } + + switch (cmd) { + case CMD_ERASE_ALL: + case CMD_ERASE_UI_CONFIG: + case CMD_ERASE_DISP_CONFIG: + case CMD_ERASE_GUEST_CODE: +#ifdef SYNA_TDDI + case CMD_ERASE_CHIP: + case CMD_ERASE_FORCE_CONFIG: + case CMD_ERASE_LOCKDOWN_DATA: + case CMD_ERASE_LCM_DATA: + case CMD_ERASE_OEM_DATA: +#endif + case CMD_ENABLE_FLASH_PROG: + retval = synaptics_rmi4_reg_write(rmi4_data, + data_base + fwu->off.payload, + fwu->bootloader_id, + sizeof(fwu->bootloader_id)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write bootloader ID\n", + __func__); + return retval; + } + break; + default: + break; + }; + + fwu->command = command; + + retval = synaptics_rmi4_reg_write(rmi4_data, + data_base + fwu->off.flash_cmd, + &command, + sizeof(command)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write command 0x%02x\n", + __func__, command); + return retval; + } + + return 0; +} + +static int fwu_write_f34_command(unsigned char cmd) +{ + int retval; + + if (fwu->bl_version == BL_V7 || fwu->bl_version == BL_V8) + retval = fwu_write_f34_v7_command(cmd); + else + retval = fwu_write_f34_v5v6_command(cmd); + + return retval; +} + +static int fwu_write_f34_v7_partition_id(unsigned char cmd) +{ + int retval; + unsigned char data_base; + unsigned char partition; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + data_base = fwu->f34_fd.data_base_addr; + + switch (cmd) { + case CMD_WRITE_FW: + partition = CORE_CODE_PARTITION; + break; + case CMD_WRITE_CONFIG: + case CMD_READ_CONFIG: + if (fwu->config_area == UI_CONFIG_AREA) + partition = CORE_CONFIG_PARTITION; + else if (fwu->config_area == DP_CONFIG_AREA) + partition = DISPLAY_CONFIG_PARTITION; + else if (fwu->config_area == PM_CONFIG_AREA) + partition = GUEST_SERIALIZATION_PARTITION; + else if (fwu->config_area == BL_CONFIG_AREA) + partition = GLOBAL_PARAMETERS_PARTITION; + else if (fwu->config_area == FLASH_CONFIG_AREA) + partition = FLASH_CONFIG_PARTITION; + else if (fwu->config_area == UPP_AREA) + partition = UTILITY_PARAMETER_PARTITION; + break; + case CMD_WRITE_LOCKDOWN: + partition = DEVICE_CONFIG_PARTITION; + break; + case CMD_WRITE_GUEST_CODE: + partition = GUEST_CODE_PARTITION; + break; + case CMD_WRITE_BOOTLOADER: + partition = BOOTLOADER_PARTITION; + break; + case CMD_WRITE_UTILITY_PARAM: + partition = UTILITY_PARAMETER_PARTITION; + break; + case CMD_ERASE_ALL: + partition = CORE_CODE_PARTITION; + break; + case CMD_ERASE_BL_CONFIG: + partition = GLOBAL_PARAMETERS_PARTITION; + break; + case CMD_ERASE_UI_CONFIG: + partition = CORE_CONFIG_PARTITION; + break; + case CMD_ERASE_DISP_CONFIG: + partition = DISPLAY_CONFIG_PARTITION; + break; + case CMD_ERASE_FLASH_CONFIG: + partition = FLASH_CONFIG_PARTITION; + break; + case CMD_ERASE_GUEST_CODE: + partition = GUEST_CODE_PARTITION; + break; + case CMD_ERASE_BOOTLOADER: + partition = BOOTLOADER_PARTITION; + break; + case CMD_ENABLE_FLASH_PROG: + partition = BOOTLOADER_PARTITION; + break; + default: + dev_err(rmi4_data->pdev->dev.parent, + "%s: Invalid command 0x%02x\n", + __func__, cmd); + return -EINVAL; + }; + + retval = synaptics_rmi4_reg_write(rmi4_data, + data_base + fwu->off.partition_id, + &partition, + sizeof(partition)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write partition ID\n", + __func__); + return retval; + } + + return 0; +} + +static int fwu_write_f34_partition_id(unsigned char cmd) +{ + int retval; + + if (fwu->bl_version == BL_V7 || fwu->bl_version == BL_V8) + retval = fwu_write_f34_v7_partition_id(cmd); + else + retval = 0; + + return retval; +} + +static int fwu_read_f34_v7_partition_table(unsigned char *partition_table) +{ + int retval; + unsigned char data_base; + unsigned char length[2]; + unsigned short block_number = 0; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + data_base = fwu->f34_fd.data_base_addr; + + fwu->config_area = FLASH_CONFIG_AREA; + + retval = fwu_write_f34_partition_id(CMD_READ_CONFIG); + if (retval < 0) + return retval; + + retval = synaptics_rmi4_reg_write(rmi4_data, + data_base + fwu->off.block_number, + (unsigned char *)&block_number, + sizeof(block_number)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write block number\n", + __func__); + return retval; + } + + length[0] = (unsigned char)(fwu->flash_config_length & MASK_8BIT); + length[1] = (unsigned char)(fwu->flash_config_length >> 8); + + retval = synaptics_rmi4_reg_write(rmi4_data, + data_base + fwu->off.transfer_length, + length, + sizeof(length)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write transfer length\n", + __func__); + return retval; + } + + retval = fwu_write_f34_command(CMD_READ_CONFIG); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write command\n", + __func__); + return retval; + } + + msleep(READ_CONFIG_WAIT_MS); + + retval = fwu_wait_for_idle(WRITE_WAIT_MS, true); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to wait for idle status\n", + __func__); + return retval; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + data_base + fwu->off.payload, + partition_table, + fwu->partition_table_bytes); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read block data\n", + __func__); + return retval; + } + + return 0; +} + +static int fwu_read_f34_v7_queries(void) +{ + int retval; + unsigned char ii; + unsigned char query_base; + unsigned char index; + unsigned char offset; + unsigned char *ptable; + struct f34_v7_query_0 query_0; + struct f34_v7_query_1_7 query_1_7; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + query_base = fwu->f34_fd.query_base_addr; + + retval = synaptics_rmi4_reg_read(rmi4_data, + query_base, + query_0.data, + sizeof(query_0.data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read query 0\n", + __func__); + return retval; + } + + offset = query_0.subpacket_1_size + 1; + + retval = synaptics_rmi4_reg_read(rmi4_data, + query_base + offset, + query_1_7.data, + sizeof(query_1_7.data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read queries 1 to 7\n", + __func__); + return retval; + } + + fwu->bootloader_id[0] = query_1_7.bl_minor_revision; + fwu->bootloader_id[1] = query_1_7.bl_major_revision; + + if (fwu->bootloader_id[1] == BL_V8) + fwu->bl_version = BL_V8; + + fwu->block_size = query_1_7.block_size_15_8 << 8 | + query_1_7.block_size_7_0; + + fwu->flash_config_length = query_1_7.flash_config_length_15_8 << 8 | + query_1_7.flash_config_length_7_0; + + fwu->payload_length = query_1_7.payload_length_15_8 << 8 | + query_1_7.payload_length_7_0; + + fwu->off.flash_status = V7_FLASH_STATUS_OFFSET; + fwu->off.partition_id = V7_PARTITION_ID_OFFSET; + fwu->off.block_number = V7_BLOCK_NUMBER_OFFSET; + fwu->off.transfer_length = V7_TRANSFER_LENGTH_OFFSET; + fwu->off.flash_cmd = V7_COMMAND_OFFSET; + fwu->off.payload = V7_PAYLOAD_OFFSET; + + index = sizeof(query_1_7.data) - V7_PARTITION_SUPPORT_BYTES; + + fwu->partitions = 0; + for (offset = 0; offset < V7_PARTITION_SUPPORT_BYTES; offset++) { + for (ii = 0; ii < 8; ii++) { + if (query_1_7.data[index + offset] & (1 << ii)) + fwu->partitions++; + } + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Supported partitions: 0x%02x\n", + __func__, query_1_7.data[index + offset]); + } + + fwu->partition_table_bytes = fwu->partitions * 8 + 2; + + ptable = kzalloc(fwu->partition_table_bytes, GFP_KERNEL); + if (!ptable) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for partition table\n", + __func__); + return -ENOMEM; + } + + retval = fwu_read_f34_v7_partition_table(ptable); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read partition table\n", + __func__); + kfree(ptable); + return retval; + } + + fwu_parse_partition_table(ptable, &fwu->blkcount, &fwu->phyaddr); + + if (fwu->blkcount.dp_config) + fwu->flash_properties.has_disp_config = 1; + else + fwu->flash_properties.has_disp_config = 0; + + if (fwu->blkcount.pm_config) + fwu->flash_properties.has_pm_config = 1; + else + fwu->flash_properties.has_pm_config = 0; + + if (fwu->blkcount.bl_config) + fwu->flash_properties.has_bl_config = 1; + else + fwu->flash_properties.has_bl_config = 0; + + if (fwu->blkcount.guest_code) + fwu->has_guest_code = 1; + else + fwu->has_guest_code = 0; + + if (fwu->blkcount.utility_param) + fwu->has_utility_param = 1; + else + fwu->has_utility_param = 0; + + kfree(ptable); + + return 0; +} + +static int fwu_read_f34_v5v6_queries(void) +{ + int retval; + unsigned char count; + unsigned char base; + unsigned char offset; + unsigned char buf[10]; + struct f34_v5v6_flash_properties_2 properties_2; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + base = fwu->f34_fd.query_base_addr; + + retval = synaptics_rmi4_reg_read(rmi4_data, + base + V5V6_BOOTLOADER_ID_OFFSET, + fwu->bootloader_id, + sizeof(fwu->bootloader_id)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read bootloader ID\n", + __func__); + return retval; + } + + if (fwu->bl_version == BL_V5) { + fwu->off.properties = V5_PROPERTIES_OFFSET; + fwu->off.block_size = V5_BLOCK_SIZE_OFFSET; + fwu->off.block_count = V5_BLOCK_COUNT_OFFSET; + fwu->off.block_number = V5_BLOCK_NUMBER_OFFSET; + fwu->off.payload = V5_BLOCK_DATA_OFFSET; + } else if (fwu->bl_version == BL_V6) { + fwu->off.properties = V6_PROPERTIES_OFFSET; + fwu->off.properties_2 = V6_PROPERTIES_2_OFFSET; + fwu->off.block_size = V6_BLOCK_SIZE_OFFSET; + fwu->off.block_count = V6_BLOCK_COUNT_OFFSET; + fwu->off.gc_block_count = V6_GUEST_CODE_BLOCK_COUNT_OFFSET; + fwu->off.block_number = V6_BLOCK_NUMBER_OFFSET; + fwu->off.payload = V6_BLOCK_DATA_OFFSET; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + base + fwu->off.block_size, + buf, + 2); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read block size info\n", + __func__); + return retval; + } + + batohs(&fwu->block_size, &(buf[0])); + + if (fwu->bl_version == BL_V5) { + fwu->off.flash_cmd = fwu->off.payload + fwu->block_size; + fwu->off.flash_status = fwu->off.flash_cmd; + } else if (fwu->bl_version == BL_V6) { + fwu->off.flash_cmd = V6_FLASH_COMMAND_OFFSET; + fwu->off.flash_status = V6_FLASH_STATUS_OFFSET; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + base + fwu->off.properties, + fwu->flash_properties.data, + sizeof(fwu->flash_properties.data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read flash properties\n", + __func__); + return retval; + } + + count = 4; + + if (fwu->flash_properties.has_pm_config) + count += 2; + + if (fwu->flash_properties.has_bl_config) + count += 2; + + if (fwu->flash_properties.has_disp_config) + count += 2; + + retval = synaptics_rmi4_reg_read(rmi4_data, + base + fwu->off.block_count, + buf, + count); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read block count info\n", + __func__); + return retval; + } + + batohs(&fwu->blkcount.ui_firmware, &(buf[0])); + batohs(&fwu->blkcount.ui_config, &(buf[2])); + + count = 4; + + if (fwu->flash_properties.has_pm_config) { + batohs(&fwu->blkcount.pm_config, &(buf[count])); + count += 2; + } + + if (fwu->flash_properties.has_bl_config) { + batohs(&fwu->blkcount.bl_config, &(buf[count])); + count += 2; + } + + if (fwu->flash_properties.has_disp_config) + batohs(&fwu->blkcount.dp_config, &(buf[count])); + + fwu->has_guest_code = false; +#ifdef SYNA_TDDI + fwu->has_force_config = false; + fwu->has_lockdown_data = false; + fwu->has_lcm_data = false; + fwu->has_oem_data = false; +#endif + + if (fwu->flash_properties.has_query4) { + retval = synaptics_rmi4_reg_read(rmi4_data, + base + fwu->off.properties_2, + properties_2.data, + sizeof(properties_2.data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read flash properties 2\n", + __func__); + return retval; + } + offset = fwu->off.properties_2 + 1; + count = 0; + if (properties_2.has_guest_code) { + retval = synaptics_rmi4_reg_read(rmi4_data, + base + offset + count, + buf, + 2); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read guest code block count\n", + __func__); + return retval; + } + + batohs(&fwu->blkcount.guest_code, &(buf[0])); + count++; + fwu->has_guest_code = true; + } +#ifdef SYNA_TDDI + if (properties_2.has_force_config) { + retval = synaptics_rmi4_reg_read(rmi4_data, + base + offset + count, + buf, + 2); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read tddi force block count\n", + __func__); + return retval; + } + batohs(&fwu->blkcount.tddi_force_config, &(buf[0])); + count++; + fwu->has_force_config = true; + } + if (properties_2.has_lockdown_data) { + retval = synaptics_rmi4_reg_read(rmi4_data, + base + offset + count, + buf, + 2); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read tddi lockdown block count\n", + __func__); + return retval; + } + batohs(&fwu->blkcount.tddi_lockdown_data, &(buf[0])); + count++; + fwu->has_lockdown_data = true; + } + if (properties_2.has_lcm_data) { + retval = synaptics_rmi4_reg_read(rmi4_data, + base + offset + count, + buf, + 2); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read tddi lcm block count\n", + __func__); + return retval; + } + batohs(&fwu->blkcount.tddi_lcm_data, &(buf[0])); + count++; + fwu->has_lcm_data = true; + } + if (properties_2.has_oem_data) { + retval = synaptics_rmi4_reg_read(rmi4_data, + base + offset + count, + buf, + 2); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read tddi oem block count\n", + __func__); + return retval; + } + batohs(&fwu->blkcount.tddi_oem_data, &(buf[0])); + fwu->has_oem_data = true; + } +#endif + } + + fwu->has_utility_param = false; + + return 0; +} + +static int fwu_read_f34_queries(void) +{ + int retval; + + memset(&fwu->blkcount, 0x00, sizeof(fwu->blkcount)); + memset(&fwu->phyaddr, 0x00, sizeof(fwu->phyaddr)); + + if (fwu->bl_version == BL_V7) + retval = fwu_read_f34_v7_queries(); + else + retval = fwu_read_f34_v5v6_queries(); + + return retval; +} + +static int fwu_write_f34_v7_blocks(unsigned char *block_ptr, + unsigned short block_cnt, unsigned char command) +{ + int retval; + unsigned char data_base; + unsigned char length[2]; + unsigned short transfer; + unsigned short remaining = block_cnt; + unsigned short block_number = 0; + unsigned short left_bytes; + unsigned short write_size; + unsigned short max_write_size; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + data_base = fwu->f34_fd.data_base_addr; + + retval = fwu_write_f34_partition_id(command); + if (retval < 0) + return retval; + + retval = synaptics_rmi4_reg_write(rmi4_data, + data_base + fwu->off.block_number, + (unsigned char *)&block_number, + sizeof(block_number)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write block number\n", + __func__); + return retval; + } + + do { + if (remaining / fwu->payload_length) + transfer = fwu->payload_length; + else + transfer = remaining; + + length[0] = (unsigned char)(transfer & MASK_8BIT); + length[1] = (unsigned char)(transfer >> 8); + + retval = synaptics_rmi4_reg_write(rmi4_data, + data_base + fwu->off.transfer_length, + length, + sizeof(length)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write transfer length (remaining = %d)\n", + __func__, remaining); + return retval; + } + + retval = fwu_write_f34_command(command); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write command (remaining = %d)\n", + __func__, remaining); + return retval; + } + +#ifdef MAX_WRITE_SIZE + max_write_size = MAX_WRITE_SIZE; + if (max_write_size >= transfer * fwu->block_size) + max_write_size = transfer * fwu->block_size; + else if (max_write_size > fwu->block_size) + max_write_size -= max_write_size % fwu->block_size; + else + max_write_size = fwu->block_size; +#else + max_write_size = transfer * fwu->block_size; +#endif + left_bytes = transfer * fwu->block_size; + + do { + if (left_bytes / max_write_size) + write_size = max_write_size; + else + write_size = left_bytes; + + retval = synaptics_rmi4_reg_write(rmi4_data, + data_base + fwu->off.payload, + block_ptr, + write_size); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write block data (remaining = %d)\n", + __func__, remaining); + return retval; + } + + block_ptr += write_size; + left_bytes -= write_size; + } while (left_bytes); + + retval = fwu_wait_for_idle(WRITE_WAIT_MS, false); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to wait for idle status (remaining = %d)\n", + __func__, remaining); + return retval; + } + + remaining -= transfer; + } while (remaining); + + return 0; +} + +static int fwu_write_f34_v5v6_blocks(unsigned char *block_ptr, + unsigned short block_cnt, unsigned char command) +{ + int retval; + unsigned char data_base; + unsigned char block_number[] = {0, 0}; + unsigned short blk; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + data_base = fwu->f34_fd.data_base_addr; + + block_number[1] |= (fwu->config_area << 5); + + retval = synaptics_rmi4_reg_write(rmi4_data, + data_base + fwu->off.block_number, + block_number, + sizeof(block_number)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write block number\n", + __func__); + return retval; + } + + for (blk = 0; blk < block_cnt; blk++) { + retval = synaptics_rmi4_reg_write(rmi4_data, + data_base + fwu->off.payload, + block_ptr, + fwu->block_size); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write block data (block %d)\n", + __func__, blk); + return retval; + } + + retval = fwu_write_f34_command(command); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write command for block %d\n", + __func__, blk); + return retval; + } + + retval = fwu_wait_for_idle(WRITE_WAIT_MS, false); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to wait for idle status (block %d)\n", + __func__, blk); + return retval; + } + + block_ptr += fwu->block_size; + } + + return 0; +} + +static int fwu_write_f34_blocks(unsigned char *block_ptr, + unsigned short block_cnt, unsigned char cmd) +{ + int retval; + + if (fwu->bl_version == BL_V7 || fwu->bl_version == BL_V8) + retval = fwu_write_f34_v7_blocks(block_ptr, block_cnt, cmd); + else + retval = fwu_write_f34_v5v6_blocks(block_ptr, block_cnt, cmd); + + return retval; +} + +static int fwu_read_f34_v7_blocks(unsigned short block_cnt, + unsigned char command) +{ + int retval; + unsigned char data_base; + unsigned char length[2]; + unsigned short transfer; + unsigned short remaining = block_cnt; + unsigned short block_number = 0; + unsigned short index = 0; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + data_base = fwu->f34_fd.data_base_addr; + + retval = fwu_write_f34_partition_id(command); + if (retval < 0) + return retval; + + retval = synaptics_rmi4_reg_write(rmi4_data, + data_base + fwu->off.block_number, + (unsigned char *)&block_number, + sizeof(block_number)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write block number\n", + __func__); + return retval; + } + + do { + if (remaining / fwu->payload_length) + transfer = fwu->payload_length; + else + transfer = remaining; + + length[0] = (unsigned char)(transfer & MASK_8BIT); + length[1] = (unsigned char)(transfer >> 8); + + retval = synaptics_rmi4_reg_write(rmi4_data, + data_base + fwu->off.transfer_length, + length, + sizeof(length)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write transfer length (remaining = %d)\n", + __func__, remaining); + return retval; + } + + retval = fwu_write_f34_command(command); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write command (remaining = %d)\n", + __func__, remaining); + return retval; + } + + retval = fwu_wait_for_idle(WRITE_WAIT_MS, false); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to wait for idle status (remaining = %d)\n", + __func__, remaining); + return retval; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + data_base + fwu->off.payload, + &fwu->read_config_buf[index], + transfer * fwu->block_size); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read block data (remaining = %d)\n", + __func__, remaining); + return retval; + } + + index += (transfer * fwu->block_size); + remaining -= transfer; + } while (remaining); + + return 0; +} + +static int fwu_read_f34_v5v6_blocks(unsigned short block_cnt, + unsigned char command) +{ + int retval; + unsigned char data_base; + unsigned char block_number[] = {0, 0}; + unsigned short blk; + unsigned short index = 0; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + data_base = fwu->f34_fd.data_base_addr; + + block_number[1] |= (fwu->config_area << 5); + + retval = synaptics_rmi4_reg_write(rmi4_data, + data_base + fwu->off.block_number, + block_number, + sizeof(block_number)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write block number\n", + __func__); + return retval; + } + + for (blk = 0; blk < block_cnt; blk++) { + retval = fwu_write_f34_command(command); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write read config command\n", + __func__); + return retval; + } + + retval = fwu_wait_for_idle(WRITE_WAIT_MS, false); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to wait for idle status\n", + __func__); + return retval; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + data_base + fwu->off.payload, + &fwu->read_config_buf[index], + fwu->block_size); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read block data (block %d)\n", + __func__, blk); + return retval; + } + + index += fwu->block_size; + } + + return 0; +} + +static int fwu_read_f34_blocks(unsigned short block_cnt, unsigned char cmd) +{ + int retval; + + if (fwu->bl_version == BL_V7 || fwu->bl_version == BL_V8) + retval = fwu_read_f34_v7_blocks(block_cnt, cmd); + else + retval = fwu_read_f34_v5v6_blocks(block_cnt, cmd); + + return retval; +} + +static int fwu_get_image_firmware_id(unsigned int *fw_id) +{ + int retval; + unsigned char index = 0; + char *strptr; + char *firmware_id; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + if (fwu->img.contains_firmware_id) { + *fw_id = fwu->img.firmware_id; + } else { + strptr = strnstr(fwu->image_name, "PR", MAX_IMAGE_NAME_LEN); + if (!strptr) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: No valid PR number (PRxxxxxxx) found in image file name (%s)\n", + __func__, fwu->image_name); + return -EINVAL; + } + + strptr += 2; + firmware_id = kzalloc(MAX_FIRMWARE_ID_LEN, GFP_KERNEL); + if (!firmware_id) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for firmware_id\n", + __func__); + return -ENOMEM; + } + while (strptr[index] >= '0' && strptr[index] <= '9') { + firmware_id[index] = strptr[index]; + index++; + if (index == MAX_FIRMWARE_ID_LEN - 1) + break; + } + + retval = sstrtoul(firmware_id, 10, (unsigned long *)fw_id); + kfree(firmware_id); + if (retval) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to obtain image firmware ID\n", + __func__); + return -EINVAL; + } + } + + return 0; +} + +static int fwu_get_device_config_id(void) +{ + int retval; + unsigned char config_id_size; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + if (fwu->bl_version == BL_V7 || fwu->bl_version == BL_V8) + config_id_size = V7_CONFIG_ID_SIZE; + else + config_id_size = V5V6_CONFIG_ID_SIZE; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fwu->f34_fd.ctrl_base_addr, + fwu->config_id, + config_id_size); + if (retval < 0) + return retval; + + return 0; +} + +static enum flash_area fwu_go_nogo(void) +{ + int retval; + enum flash_area flash_area = NONE; + unsigned char ii; + unsigned char config_id_size; + unsigned int device_fw_id; + unsigned int image_fw_id; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + if (fwu->force_update) { + flash_area = UI_FIRMWARE; + goto exit; + } + + /* Update both UI and config if device is in bootloader mode */ + if (fwu->bl_mode_device) { + flash_area = UI_FIRMWARE; + goto exit; + } + + /* Get device firmware ID */ + device_fw_id = rmi4_data->firmware_id; + dev_info(rmi4_data->pdev->dev.parent, + "%s: Device firmware ID = %d\n", + __func__, device_fw_id); + + /* Get image firmware ID */ + retval = fwu_get_image_firmware_id(&image_fw_id); + if (retval < 0) { + flash_area = NONE; + goto exit; + } + dev_info(rmi4_data->pdev->dev.parent, + "%s: Image firmware ID = %d\n", + __func__, image_fw_id); + + if (image_fw_id > device_fw_id) { + flash_area = UI_FIRMWARE; + goto exit; + } else if (image_fw_id < device_fw_id) { + dev_info(rmi4_data->pdev->dev.parent, + "%s: Image firmware ID older than device firmware ID\n", + __func__); + flash_area = NONE; + goto exit; + } + + /* Get device config ID */ + retval = fwu_get_device_config_id(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read device config ID\n", + __func__); + flash_area = NONE; + goto exit; + } + + if (fwu->bl_version == BL_V7 || fwu->bl_version == BL_V8) + config_id_size = V7_CONFIG_ID_SIZE; + else + config_id_size = V5V6_CONFIG_ID_SIZE; + + for (ii = 0; ii < config_id_size; ii++) { + if (fwu->img.ui_config.data[ii] > fwu->config_id[ii]) { + flash_area = UI_CONFIG; + goto exit; + } else if (fwu->img.ui_config.data[ii] < fwu->config_id[ii]) { + flash_area = NONE; + goto exit; + } + } + + flash_area = NONE; + +exit: + if (flash_area == NONE) { + dev_info(rmi4_data->pdev->dev.parent, + "%s: No need to do reflash\n", + __func__); + } else { + dev_info(rmi4_data->pdev->dev.parent, + "%s: Updating %s\n", + __func__, + flash_area == UI_FIRMWARE ? + "UI firmware and config" : + "UI config only"); + } + + return flash_area; +} + +static int fwu_scan_pdt(void) +{ + int retval; + unsigned char ii; + unsigned char intr_count = 0; + unsigned char intr_off; + unsigned char intr_src; + unsigned short addr; + bool f01found = false; + bool f34found = false; + bool f35found = false; + struct synaptics_rmi4_fn_desc rmi_fd; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + fwu->in_ub_mode = false; + + for (addr = PDT_START; addr > PDT_END; addr -= PDT_ENTRY_SIZE) { + retval = synaptics_rmi4_reg_read(rmi4_data, + addr, + (unsigned char *)&rmi_fd, + sizeof(rmi_fd)); + if (retval < 0) + return retval; + + if (rmi_fd.fn_number) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Found F%02x\n", + __func__, rmi_fd.fn_number); + switch (rmi_fd.fn_number) { + case SYNAPTICS_RMI4_F01: + f01found = true; + + rmi4_data->f01_query_base_addr = + rmi_fd.query_base_addr; + rmi4_data->f01_ctrl_base_addr = + rmi_fd.ctrl_base_addr; + rmi4_data->f01_data_base_addr = + rmi_fd.data_base_addr; + rmi4_data->f01_cmd_base_addr = + rmi_fd.cmd_base_addr; + break; + case SYNAPTICS_RMI4_F34: + f34found = true; + fwu->f34_fd.query_base_addr = + rmi_fd.query_base_addr; + fwu->f34_fd.ctrl_base_addr = + rmi_fd.ctrl_base_addr; + fwu->f34_fd.data_base_addr = + rmi_fd.data_base_addr; + + switch (rmi_fd.fn_version) { + case F34_V0: + fwu->bl_version = BL_V5; + break; + case F34_V1: + fwu->bl_version = BL_V6; + break; + case F34_V2: + fwu->bl_version = BL_V7; + break; + default: + dev_err(rmi4_data->pdev->dev.parent, + "%s: Unrecognized F34 version\n", + __func__); + return -EINVAL; + } + + fwu->intr_mask = 0; + intr_src = rmi_fd.intr_src_count; + intr_off = intr_count % 8; + for (ii = intr_off; + ii < (intr_src + intr_off); + ii++) { + fwu->intr_mask |= 1 << ii; + } + break; + case SYNAPTICS_RMI4_F35: + f35found = true; + fwu->f35_fd.query_base_addr = + rmi_fd.query_base_addr; + fwu->f35_fd.ctrl_base_addr = + rmi_fd.ctrl_base_addr; + fwu->f35_fd.data_base_addr = + rmi_fd.data_base_addr; + fwu->f35_fd.cmd_base_addr = + rmi_fd.cmd_base_addr; + break; + } + } else { + break; + } + + intr_count += rmi_fd.intr_src_count; + } + + if (!f01found || !f34found) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to find both F01 and F34\n", + __func__); + if (!f35found) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to find F35\n", + __func__); + return -EINVAL; + } else { + fwu->in_ub_mode = true; + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: In microbootloader mode\n", + __func__); + fwu_recovery_check_status(); + return 0; + } + } + + rmi4_data->intr_mask[0] |= fwu->intr_mask; + + addr = rmi4_data->f01_ctrl_base_addr + 1; + + retval = synaptics_rmi4_reg_write(rmi4_data, + addr, + &(rmi4_data->intr_mask[0]), + sizeof(rmi4_data->intr_mask[0])); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set interrupt enable bit\n", + __func__); + return retval; + } + + return 0; +} + +static int fwu_enter_flash_prog(void) +{ + int retval; + struct f01_device_control f01_device_control; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + retval = fwu_read_flash_status(); + if (retval < 0) + return retval; + + if (fwu->in_bl_mode) + return 0; + + retval = rmi4_data->irq_enable(rmi4_data, false, true); + if (retval < 0) + return retval; + + msleep(INT_DISABLE_WAIT_MS); + + retval = fwu_write_f34_command(CMD_ENABLE_FLASH_PROG); + if (retval < 0) + return retval; + + retval = fwu_wait_for_idle(ENABLE_WAIT_MS, false); + if (retval < 0) + return retval; + + if (!fwu->in_bl_mode) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: BL mode not entered\n", + __func__); + return -EINVAL; + } + + if (rmi4_data->hw_if->bl_hw_init) { + retval = rmi4_data->hw_if->bl_hw_init(rmi4_data); + if (retval < 0) + return retval; + } + + retval = fwu_scan_pdt(); + if (retval < 0) + return retval; + + retval = fwu_read_f34_queries(); + if (retval < 0) + return retval; + + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_ctrl_base_addr, + f01_device_control.data, + sizeof(f01_device_control.data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read F01 device control\n", + __func__); + return retval; + } + + f01_device_control.nosleep = true; + f01_device_control.sleep_mode = SLEEP_MODE_NORMAL; + + retval = synaptics_rmi4_reg_write(rmi4_data, + rmi4_data->f01_ctrl_base_addr, + f01_device_control.data, + sizeof(f01_device_control.data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write F01 device control\n", + __func__); + return retval; + } + + msleep(ENTER_FLASH_PROG_WAIT_MS); + + return retval; +} + +static int fwu_check_ui_firmware_size(void) +{ + unsigned short block_count; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + block_count = fwu->img.ui_firmware.size / fwu->block_size; + + if (block_count != fwu->blkcount.ui_firmware) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: UI firmware size mismatch\n", + __func__); + return -EINVAL; + } + + return 0; +} + +static int fwu_check_ui_configuration_size(void) +{ + unsigned short block_count; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + block_count = fwu->img.ui_config.size / fwu->block_size; + + if (block_count != fwu->blkcount.ui_config) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: UI configuration size mismatch\n", + __func__); + return -EINVAL; + } + + return 0; +} + +static int fwu_check_dp_configuration_size(void) +{ + unsigned short block_count; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + block_count = fwu->img.dp_config.size / fwu->block_size; + + if (block_count != fwu->blkcount.dp_config) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Display configuration size mismatch\n", + __func__); + return -EINVAL; + } + + return 0; +} + +#ifdef CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE_EXTRA_SYSFS +static int fwu_check_pm_configuration_size(void) +{ + unsigned short block_count; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + block_count = fwu->img.pm_config.size / fwu->block_size; + + if (block_count != fwu->blkcount.pm_config) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Permanent configuration size mismatch\n", + __func__); + return -EINVAL; + } + + return 0; +} +#endif + +static int fwu_check_bl_configuration_size(void) +{ + unsigned short block_count; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + block_count = fwu->img.bl_config.size / fwu->block_size; + + if (block_count != fwu->blkcount.bl_config) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Bootloader configuration size mismatch\n", + __func__); + return -EINVAL; + } + + return 0; +} + +static int fwu_check_guest_code_size(void) +{ + unsigned short block_count; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + block_count = fwu->img.guest_code.size / fwu->block_size; + if (block_count != fwu->blkcount.guest_code) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Guest code size mismatch\n", + __func__); + return -EINVAL; + } + + return 0; +} + +static int fwu_erase_configuration(void) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + switch (fwu->config_area) { + case UI_CONFIG_AREA: + retval = fwu_write_f34_command(CMD_ERASE_UI_CONFIG); + if (retval < 0) + return retval; + break; + case DP_CONFIG_AREA: + retval = fwu_write_f34_command(CMD_ERASE_DISP_CONFIG); + if (retval < 0) + return retval; + break; + case BL_CONFIG_AREA: + retval = fwu_write_f34_command(CMD_ERASE_BL_CONFIG); + if (retval < 0) + return retval; + break; + case FLASH_CONFIG_AREA: + retval = fwu_write_f34_command(CMD_ERASE_FLASH_CONFIG); + if (retval < 0) + return retval; + break; + case UPP_AREA: + retval = fwu_write_f34_command(CMD_ERASE_UTILITY_PARAMETER); + if (retval < 0) + return retval; + default: + dev_err(rmi4_data->pdev->dev.parent, + "%s: Invalid config area\n", + __func__); + return -EINVAL; + } + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Erase command written\n", + __func__); + + retval = fwu_wait_for_idle(ERASE_WAIT_MS, false); + if (retval < 0) + return retval; + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Idle status detected\n", + __func__); + + return retval; +} + +static int fwu_erase_bootloader(void) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + retval = fwu_write_f34_command(CMD_ERASE_BOOTLOADER); + if (retval < 0) + return retval; + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Erase command written\n", + __func__); + + retval = fwu_wait_for_idle(ERASE_WAIT_MS, false); + if (retval < 0) + return retval; + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Idle status detected\n", + __func__); + + return 0; +} + +#ifdef SYNA_TDDI +static int fwu_erase_lockdown_data(void) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + retval = fwu_write_f34_command(CMD_ERASE_LOCKDOWN_DATA); + if (retval < 0) + return retval; + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Erase command written\n", + __func__); + + msleep(100); + + retval = fwu_wait_for_idle(ERASE_WAIT_MS, false); + if (retval < 0) + return retval; + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Idle status detected\n", + __func__); + + return 0; +} + +#endif + +static int fwu_erase_guest_code(void) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + retval = fwu_write_f34_command(CMD_ERASE_GUEST_CODE); + if (retval < 0) + return retval; + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Erase command written\n", + __func__); + + retval = fwu_wait_for_idle(ERASE_WAIT_MS, false); + if (retval < 0) + return retval; + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Idle status detected\n", + __func__); + + return 0; +} + +static int fwu_erase_all(void) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + if (fwu->bl_version == BL_V7) { + retval = fwu_write_f34_command(CMD_ERASE_UI_FIRMWARE); + if (retval < 0) + return retval; + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Erase command written\n", + __func__); + + retval = fwu_wait_for_idle(ERASE_WAIT_MS, false); + if (retval < 0) + return retval; + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Idle status detected\n", + __func__); + + fwu->config_area = UI_CONFIG_AREA; + retval = fwu_erase_configuration(); + if (retval < 0) + return retval; + } else { + retval = fwu_write_f34_command(CMD_ERASE_ALL); + if (retval < 0) + return retval; + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Erase all command written\n", + __func__); + + retval = fwu_wait_for_idle(ERASE_WAIT_MS, false); + if (!(fwu->bl_version == BL_V8 && + fwu->flash_status == BAD_PARTITION_TABLE)) { + if (retval < 0) + return retval; + } + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Idle status detected\n", + __func__); + + if (fwu->bl_version == BL_V8) + return 0; + } + + if (fwu->flash_properties.has_disp_config) { + fwu->config_area = DP_CONFIG_AREA; + retval = fwu_erase_configuration(); + if (retval < 0) + return retval; + } + + if (fwu->has_guest_code) { + retval = fwu_erase_guest_code(); + if (retval < 0) + return retval; + } + + return 0; +} + +static int fwu_write_firmware(void) +{ + unsigned short firmware_block_count; + + firmware_block_count = fwu->img.ui_firmware.size / fwu->block_size; + + return fwu_write_f34_blocks((unsigned char *)fwu->img.ui_firmware.data, + firmware_block_count, CMD_WRITE_FW); +} + +static int fwu_write_bootloader(void) +{ + int retval; + unsigned short bootloader_block_count; + + bootloader_block_count = fwu->img.bl_image.size / fwu->block_size; + + fwu->write_bootloader = true; + retval = fwu_write_f34_blocks((unsigned char *)fwu->img.bl_image.data, + bootloader_block_count, CMD_WRITE_BOOTLOADER); + fwu->write_bootloader = false; + + return retval; +} + +static int fwu_write_utility_parameter(void) +{ + int retval; + unsigned char ii; + unsigned char checksum_array[4]; + unsigned char *pbuf; + unsigned short remaining_size; + unsigned short utility_param_size; + unsigned long checksum; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + utility_param_size = fwu->blkcount.utility_param * fwu->block_size; + retval = fwu_allocate_read_config_buf(utility_param_size); + if (retval < 0) + return retval; + memset(fwu->read_config_buf, 0x00, utility_param_size); + + pbuf = fwu->read_config_buf; + remaining_size = utility_param_size - 4; + + for (ii = 0; ii < MAX_UTILITY_PARAMS; ii++) { + if (fwu->img.utility_param_id[ii] == UNUSED) + continue; + +#ifdef F51_DISCRETE_FORCE + if (fwu->img.utility_param_id[ii] == FORCE_PARAMETER) { + if (fwu->bl_mode_device) { + dev_info(rmi4_data->pdev->dev.parent, + "%s: Device in bootloader mode, skipping calibration data restoration\n", + __func__); + goto image_param; + } + retval = secure_memcpy(&(pbuf[4]), + remaining_size - 4, + fwu->cal_data, + fwu->cal_data_buf_size, + fwu->cal_data_size); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy force calibration data\n", + __func__); + return retval; + } + pbuf[0] = FORCE_PARAMETER; + pbuf[1] = 0x00; + pbuf[2] = (4 + fwu->cal_data_size) / 2; + pbuf += (fwu->cal_data_size + 4); + remaining_size -= (fwu->cal_data_size + 4); + continue; + } +image_param: +#endif + + retval = secure_memcpy(pbuf, + remaining_size, + fwu->img.utility_param[ii].data, + fwu->img.utility_param[ii].size, + fwu->img.utility_param[ii].size); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy utility parameter data\n", + __func__); + return retval; + } + pbuf += fwu->img.utility_param[ii].size; + remaining_size -= fwu->img.utility_param[ii].size; + } + + calculate_checksum((unsigned short *)fwu->read_config_buf, + ((utility_param_size - 4) / 2), + &checksum); + + convert_to_little_endian(checksum_array, checksum); + + fwu->read_config_buf[utility_param_size - 4] = checksum_array[0]; + fwu->read_config_buf[utility_param_size - 3] = checksum_array[1]; + fwu->read_config_buf[utility_param_size - 2] = checksum_array[2]; + fwu->read_config_buf[utility_param_size - 1] = checksum_array[3]; + + retval = fwu_write_f34_blocks((unsigned char *)fwu->read_config_buf, + fwu->blkcount.utility_param, CMD_WRITE_UTILITY_PARAM); + if (retval < 0) + return retval; + + return 0; +} + +static int fwu_write_configuration(void) +{ + return fwu_write_f34_blocks((unsigned char *)fwu->config_data, + fwu->config_block_count, CMD_WRITE_CONFIG); +} + +static int fwu_write_ui_configuration(void) +{ + fwu->config_area = UI_CONFIG_AREA; + fwu->config_data = fwu->img.ui_config.data; + fwu->config_size = fwu->img.ui_config.size; + fwu->config_block_count = fwu->config_size / fwu->block_size; + + return fwu_write_configuration(); +} + +static int fwu_write_dp_configuration(void) +{ + fwu->config_area = DP_CONFIG_AREA; + fwu->config_data = fwu->img.dp_config.data; + fwu->config_size = fwu->img.dp_config.size; + fwu->config_block_count = fwu->config_size / fwu->block_size; + + return fwu_write_configuration(); +} + +#ifdef CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE_EXTRA_SYSFS +static int fwu_write_pm_configuration(void) +{ + fwu->config_area = PM_CONFIG_AREA; + fwu->config_data = fwu->img.pm_config.data; + fwu->config_size = fwu->img.pm_config.size; + fwu->config_block_count = fwu->config_size / fwu->block_size; + + return fwu_write_configuration(); +} + +#ifdef SYNA_TDDI +static int fwu_write_tddi_lockdown_data(void) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + retval = fwu_write_f34_blocks(fwu->read_config_buf, + fwu->blkcount.tddi_lockdown_data, + CMD_WRITE_LOCKDOWN_DATA); + if (retval < 0) + return retval; + rmi4_data->reset_device(rmi4_data, false); + return 0; +} +#endif +#endif + +static int fwu_write_flash_configuration(void) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + fwu->config_area = FLASH_CONFIG_AREA; + fwu->config_data = fwu->img.fl_config.data; + fwu->config_size = fwu->img.fl_config.size; + fwu->config_block_count = fwu->config_size / fwu->block_size; + + if (fwu->config_block_count != fwu->blkcount.fl_config) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Flash configuration size mismatch\n", + __func__); + return -EINVAL; + } + + retval = fwu_erase_configuration(); + if (retval < 0) + return retval; + + retval = fwu_write_configuration(); + if (retval < 0) + return retval; + + rmi4_data->reset_device(rmi4_data, false); + + return 0; +} + +static int fwu_write_guest_code(void) +{ + int retval; + unsigned short guest_code_block_count; + + guest_code_block_count = fwu->img.guest_code.size / fwu->block_size; + + retval = fwu_write_f34_blocks((unsigned char *)fwu->img.guest_code.data, + guest_code_block_count, CMD_WRITE_GUEST_CODE); + if (retval < 0) + return retval; + + return 0; +} + +static int fwu_write_lockdown(void) +{ + unsigned short lockdown_block_count; + + lockdown_block_count = fwu->img.lockdown.size / fwu->block_size; + + return fwu_write_f34_blocks((unsigned char *)fwu->img.lockdown.data, + lockdown_block_count, CMD_WRITE_LOCKDOWN); +} + +static int fwu_write_partition_table_v8(void) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + fwu->config_area = FLASH_CONFIG_AREA; + fwu->config_data = fwu->img.fl_config.data; + fwu->config_size = fwu->img.fl_config.size; + fwu->config_block_count = fwu->config_size / fwu->block_size; + + if (fwu->config_block_count != fwu->blkcount.fl_config) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Flash configuration size mismatch\n", + __func__); + return -EINVAL; + } + + retval = fwu_write_configuration(); + if (retval < 0) + return retval; + + rmi4_data->reset_device(rmi4_data, false); + + return 0; +} + +static int fwu_write_partition_table_v7(void) +{ + int retval; + unsigned short block_count; + + block_count = fwu->blkcount.bl_config; + fwu->config_area = BL_CONFIG_AREA; + fwu->config_size = fwu->block_size * block_count; + + retval = fwu_allocate_read_config_buf(fwu->config_size); + if (retval < 0) + return retval; + + retval = fwu_read_f34_blocks(block_count, CMD_READ_CONFIG); + if (retval < 0) + return retval; + + retval = fwu_erase_configuration(); + if (retval < 0) + return retval; + + retval = fwu_write_flash_configuration(); + if (retval < 0) + return retval; + + fwu->config_area = BL_CONFIG_AREA; + fwu->config_data = fwu->read_config_buf; + fwu->config_size = fwu->img.bl_config.size; + fwu->config_block_count = fwu->config_size / fwu->block_size; + + retval = fwu_write_configuration(); + if (retval < 0) + return retval; + + return 0; +} + +static int fwu_write_bl_area_v7(void) +{ + int retval; + bool has_utility_param; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + has_utility_param = fwu->has_utility_param; + + if (fwu->has_utility_param) { + fwu->config_area = UPP_AREA; + retval = fwu_erase_configuration(); + if (retval < 0) + return retval; + } + + fwu->config_area = BL_CONFIG_AREA; + retval = fwu_erase_configuration(); + if (retval < 0) + return retval; + + fwu->config_area = FLASH_CONFIG_AREA; + retval = fwu_erase_configuration(); + if (retval < 0) + return retval; + + retval = fwu_erase_bootloader(); + if (retval < 0) + return retval; + + retval = fwu_write_bootloader(); + if (retval < 0) + return retval; + + msleep(rmi4_data->hw_if->board_data->reset_delay_ms); + rmi4_data->reset_device(rmi4_data, false); + + fwu->config_area = FLASH_CONFIG_AREA; + fwu->config_data = fwu->img.fl_config.data; + fwu->config_size = fwu->img.fl_config.size; + fwu->config_block_count = fwu->config_size / fwu->block_size; + retval = fwu_write_configuration(); + if (retval < 0) + return retval; + rmi4_data->reset_device(rmi4_data, false); + + fwu->config_area = BL_CONFIG_AREA; + fwu->config_data = fwu->img.bl_config.data; + fwu->config_size = fwu->img.bl_config.size; + fwu->config_block_count = fwu->config_size / fwu->block_size; + retval = fwu_write_configuration(); + if (retval < 0) + return retval; + + if (fwu->img.contains_utility_param) { + retval = fwu_write_utility_parameter(); + if (retval < 0) + return retval; + } + + return 0; +} + +static int fwu_do_reflash(void) +{ + int retval; + bool do_bl_update = false; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + if (!fwu->new_partition_table) { + retval = fwu_check_ui_firmware_size(); + if (retval < 0) + return retval; + + retval = fwu_check_ui_configuration_size(); + if (retval < 0) + return retval; + + if (fwu->flash_properties.has_disp_config && + fwu->img.contains_disp_config) { + retval = fwu_check_dp_configuration_size(); + if (retval < 0) + return retval; + } + + if (fwu->has_guest_code && fwu->img.contains_guest_code) { + retval = fwu_check_guest_code_size(); + if (retval < 0) + return retval; + } + } else if (fwu->bl_version == BL_V7) { + retval = fwu_check_bl_configuration_size(); + if (retval < 0) + return retval; + } + + if (!fwu->has_utility_param && fwu->img.contains_utility_param) { + if (fwu->bl_version == BL_V7 || fwu->bl_version == BL_V8) + do_bl_update = true; + } + + if (fwu->has_utility_param && !fwu->img.contains_utility_param) { + if (fwu->bl_version == BL_V7 || fwu->bl_version == BL_V8) + do_bl_update = true; + } + + if (!do_bl_update && fwu->incompatible_partition_tables) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Incompatible partition tables\n", + __func__); + return -EINVAL; + } else if (!do_bl_update && fwu->new_partition_table) { + if (!fwu->force_update) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Partition table mismatch\n", + __func__); + return -EINVAL; + } + } + + retval = fwu_erase_all(); + if (retval < 0) + return retval; + + if (do_bl_update) { + retval = fwu_write_bl_area_v7(); + if (retval < 0) + return retval; + pr_notice("%s: Bootloader area programmed\n", __func__); + } else if (fwu->bl_version == BL_V7 && fwu->new_partition_table) { + retval = fwu_write_partition_table_v7(); + if (retval < 0) + return retval; + pr_notice("%s: Partition table programmed\n", __func__); + } else if (fwu->bl_version == BL_V8) { + retval = fwu_write_partition_table_v8(); + if (retval < 0) + return retval; + pr_notice("%s: Partition table programmed\n", __func__); + } + + fwu->config_area = UI_CONFIG_AREA; + if (fwu->flash_properties.has_disp_config && + fwu->img.contains_disp_config) { + retval = fwu_write_dp_configuration(); + if (retval < 0) + return retval; + pr_notice("%s: Display configuration programmed\n", __func__); + } + + retval = fwu_write_ui_configuration(); + if (retval < 0) + return retval; + pr_notice("%s: Configuration programmed\n", __func__); + + if (fwu->has_guest_code && fwu->img.contains_guest_code) { + retval = fwu_write_guest_code(); + if (retval < 0) + return retval; + pr_notice("%s: Guest code programmed\n", __func__); + } + + retval = fwu_write_firmware(); + if (retval < 0) + return retval; + pr_notice("%s: Firmware programmed\n", __func__); + + return retval; +} + +#ifdef CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE_EXTRA_SYSFS +static int fwu_do_read_config(void) +{ + int retval; + unsigned short block_count; + unsigned short config_area; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + switch (fwu->config_area) { + case UI_CONFIG_AREA: + block_count = fwu->blkcount.ui_config; + break; + case DP_CONFIG_AREA: + if (!fwu->flash_properties.has_disp_config) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Display configuration not supported\n", + __func__); + return -EINVAL; + } + block_count = fwu->blkcount.dp_config; + break; + case PM_CONFIG_AREA: + if (!fwu->flash_properties.has_pm_config) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Permanent configuration not supported\n", + __func__); + return -EINVAL; + } + block_count = fwu->blkcount.pm_config; + break; + case BL_CONFIG_AREA: + if (!fwu->flash_properties.has_bl_config) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Bootloader configuration not supported\n", + __func__); + return -EINVAL; + } + block_count = fwu->blkcount.bl_config; + break; + case UPP_AREA: + if (!fwu->has_utility_param) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Utility parameter not supported\n", + __func__); + return -EINVAL; + } + block_count = fwu->blkcount.utility_param; + break; +#ifdef SYNA_TDDI + case TDDI_FORCE_CONFIG_AREA: + if (!fwu->has_force_config) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: force configuration not supported\n", + __func__); + return -EINVAL; + } + block_count = fwu->blkcount.tddi_force_config; + break; + case TDDI_OEM_DATA_AREA: + if (!fwu->has_oem_data) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: oem data not supported\n", + __func__); + return -EINVAL; + } + block_count = fwu->blkcount.tddi_oem_data; + break; + case TDDI_LCM_DATA_AREA: + if (!fwu->has_lcm_data) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: lcm data not supported\n", + __func__); + return -EINVAL; + } + block_count = fwu->blkcount.tddi_lcm_data; + break; +#endif + default: + dev_err(rmi4_data->pdev->dev.parent, + "%s: Invalid config area\n", + __func__); + return -EINVAL; + } + + if (block_count == 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Invalid block count\n", + __func__); + return -EINVAL; + } + + mutex_lock(&rmi4_data->rmi4_exp_init_mutex); + + if (fwu->bl_version == BL_V5 || fwu->bl_version == BL_V6) { + config_area = fwu->config_area; + retval = fwu_enter_flash_prog(); + fwu->config_area = config_area; + if (retval < 0) + goto exit; + } + + fwu->config_size = fwu->block_size * block_count; + + retval = fwu_allocate_read_config_buf(fwu->config_size); + if (retval < 0) + goto exit; + + retval = fwu_read_f34_blocks(block_count, CMD_READ_CONFIG); + +exit: + if (fwu->bl_version == BL_V5 || fwu->bl_version == BL_V6) + rmi4_data->reset_device(rmi4_data, false); + + mutex_unlock(&rmi4_data->rmi4_exp_init_mutex); + + return retval; +} + +#ifdef SYNA_TDDI +static int fwu_do_read_tddi_lockdown_data(void) +{ + int retval = -EINVAL; + unsigned short block_count; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + block_count = fwu->blkcount.tddi_lockdown_data; + fwu->config_size = fwu->block_size * block_count; + + if (fwu->bl_version != BL_V6) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Not support lockdown data in bl v.%d\n", + __func__, + fwu->bl_version); + goto exit; + } else if (!fwu->has_lockdown_data) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Not support lockdown data\n", __func__); + goto exit; + } + + kfree(fwu->read_config_buf); + + fwu->read_config_buf = kzalloc(fwu->config_size, GFP_KERNEL); + + if (!fwu->read_config_buf) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for fwu->read_config_buf\n", + __func__); + fwu->read_config_buf_size = 0; + retval = -ENOMEM; + goto exit; + } + fwu->read_config_buf_size = fwu->config_size; + retval = fwu_read_f34_blocks(block_count, CMD_READ_LOCKDOWN_DATA); +exit: + return retval; +} + +int get_tddi_lockdown_data(unsigned char *lockdown_data, unsigned short leng) +{ + int retval; + + retval = fwu_do_read_tddi_lockdown_data(); + if (retval < 0) + return retval; + memcpy(lockdown_data, fwu->read_config_buf, leng); + return retval; +} + +int set_tddi_lockdown_data(unsigned char *lockdown_data, unsigned short leng) +{ + int retval = -EINVAL; + unsigned long checksum; + unsigned char checksum_array[4]; + unsigned short blk_cnt; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + if (fwu->bl_version != BL_V6) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Not support lockdown data in bl v.%d\n", + __func__, + fwu->bl_version); + goto exit; + } else if (!fwu->has_lockdown_data) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Not support lockdown data\n", __func__); + goto exit; + } + + retval = fwu_enter_flash_prog(); + if (retval < 0) + goto exit; + + retval = fwu_erase_lockdown_data(); + if (retval < 0) + goto exit; + + blk_cnt = fwu->blkcount.tddi_lockdown_data; + + fwu->config_size = fwu->blkcount.tddi_lockdown_data * fwu->block_size; + retval = fwu_allocate_read_config_buf(fwu->config_size); + if (retval < 0) + goto exit; + memset(fwu->read_config_buf, 0x00, fwu->config_size); + retval = secure_memcpy(fwu->read_config_buf, fwu->config_size, + lockdown_data, leng, leng); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy tddi lockdwon data\n", + __func__); + goto exit; + } + + calculate_checksum((unsigned short *)fwu->read_config_buf, + ((fwu->config_size - 4) / 2), + &checksum); + + convert_to_little_endian(checksum_array, checksum); + + fwu->read_config_buf[blk_cnt * fwu->block_size - 4] = checksum_array[0]; + fwu->read_config_buf[blk_cnt * fwu->block_size - 3] = checksum_array[1]; + fwu->read_config_buf[blk_cnt * fwu->block_size - 2] = checksum_array[2]; + fwu->read_config_buf[blk_cnt * fwu->block_size - 1] = checksum_array[3]; + retval = fwu_write_tddi_lockdown_data(); +exit: + return retval; +} +#endif +#endif + +static int fwu_do_lockdown_v7(void) +{ + int retval; + struct f34_v7_data0 status; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + retval = fwu_enter_flash_prog(); + if (retval < 0) + return retval; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fwu->f34_fd.data_base_addr + fwu->off.flash_status, + status.data, + sizeof(status.data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read flash status\n", + __func__); + return retval; + } + + if (status.device_cfg_status == 2) { + dev_info(rmi4_data->pdev->dev.parent, + "%s: Device already locked down\n", + __func__); + return 0; + } + + retval = fwu_write_lockdown(); + if (retval < 0) + return retval; + + pr_notice("%s: Lockdown programmed\n", __func__); + + return retval; +} + +static int fwu_do_lockdown_v5v6(void) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; +#ifdef SYNA_TDDI + unsigned char *img_ld; + + img_ld = (unsigned char *)fwu->img.lockdown.data; + if (fwu->has_lockdown_data) { + retval = set_tddi_lockdown_data(img_ld, + LOCKDOWN_SIZE); + if (retval < 0) + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write lockdown data\n", + __func__); + return retval; + } +#endif + + retval = fwu_enter_flash_prog(); + if (retval < 0) + return retval; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fwu->f34_fd.query_base_addr + fwu->off.properties, + fwu->flash_properties.data, + sizeof(fwu->flash_properties.data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read flash properties\n", + __func__); + return retval; + } + + if (fwu->flash_properties.unlocked == 0) { + dev_info(rmi4_data->pdev->dev.parent, + "%s: Device already locked down\n", + __func__); + return 0; + } + + retval = fwu_write_lockdown(); + if (retval < 0) + return retval; + + pr_notice("%s: Lockdown programmed\n", __func__); + + return retval; +} + +#ifdef F51_DISCRETE_FORCE +static int fwu_do_restore_f51_cal_data(void) +{ + int retval; + unsigned char checksum_array[4]; + unsigned short block_count; + unsigned long checksum; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + block_count = fwu->blkcount.ui_config; + fwu->config_size = fwu->block_size * block_count; + fwu->config_area = UI_CONFIG_AREA; + + retval = fwu_allocate_read_config_buf(fwu->config_size); + if (retval < 0) + return retval; + + retval = fwu_read_f34_blocks(block_count, CMD_READ_CONFIG); + if (retval < 0) + return retval; + + retval = secure_memcpy(&fwu->read_config_buf[fwu->cal_data_off], + fwu->cal_data_size, fwu->cal_data, + fwu->cal_data_buf_size, fwu->cal_data_size); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to restore calibration data\n", + __func__); + return retval; + } + + calculate_checksum((unsigned short *)fwu->read_config_buf, + ((fwu->config_size - 4) / 2), + &checksum); + + convert_to_little_endian(checksum_array, checksum); + + fwu->read_config_buf[fwu->config_size - 4] = checksum_array[0]; + fwu->read_config_buf[fwu->config_size - 3] = checksum_array[1]; + fwu->read_config_buf[fwu->config_size - 2] = checksum_array[2]; + fwu->read_config_buf[fwu->config_size - 1] = checksum_array[3]; + + retval = fwu_enter_flash_prog(); + if (retval < 0) + return retval; + + fwu->config_area = UI_CONFIG_AREA; + fwu->config_data = fwu->read_config_buf; + fwu->config_block_count = fwu->config_size / fwu->block_size; + + retval = fwu_erase_configuration(); + if (retval < 0) + return retval; + + retval = fwu_write_configuration(); + if (retval < 0) + return retval; + + return 0; +} +#endif + +#ifdef CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE_EXTRA_SYSFS +static int fwu_start_write_guest_code(void) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + retval = fwu_parse_image_info(); + if (retval < 0) + return -EINVAL; + + if (!fwu->has_guest_code) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Guest code not supported\n", + __func__); + return -EINVAL; + } + + if (!fwu->img.contains_guest_code) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: No guest code in firmware image\n", + __func__); + return -EINVAL; + } + + if (rmi4_data->sensor_sleep) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Sensor sleeping\n", + __func__); + return -ENODEV; + } + + rmi4_data->stay_awake = true; + + mutex_lock(&rmi4_data->rmi4_exp_init_mutex); + + pr_notice("%s: Start of write guest code process\n", __func__); + + retval = fwu_enter_flash_prog(); + if (retval < 0) + goto exit; + + retval = fwu_check_guest_code_size(); + if (retval < 0) + goto exit; + + retval = fwu_erase_guest_code(); + if (retval < 0) + goto exit; + + retval = fwu_write_guest_code(); + if (retval < 0) + goto exit; + + pr_notice("%s: Guest code programmed\n", __func__); + +exit: + rmi4_data->reset_device(rmi4_data, false); + + pr_notice("%s: End of write guest code process\n", __func__); + + mutex_unlock(&rmi4_data->rmi4_exp_init_mutex); + + rmi4_data->stay_awake = false; + + return retval; +} + +static int fwu_start_write_config(void) +{ + int retval; + unsigned short config_area; + unsigned int device_fw_id; + unsigned int image_fw_id; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + retval = fwu_parse_image_info(); + if (retval < 0) + return -EINVAL; + + switch (fwu->config_area) { + case UI_CONFIG_AREA: + device_fw_id = rmi4_data->firmware_id; + retval = fwu_get_image_firmware_id(&image_fw_id); + if (retval < 0) + return retval; + if (device_fw_id != image_fw_id) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Device and image firmware IDs don't match\n", + __func__); + return -EINVAL; + } + retval = fwu_check_ui_configuration_size(); + if (retval < 0) + return retval; + break; + case DP_CONFIG_AREA: + if (!fwu->flash_properties.has_disp_config) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Display configuration not supported\n", + __func__); + return -EINVAL; + } + if (!fwu->img.contains_disp_config) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: No display configuration in firmware image\n", + __func__); + return -EINVAL; + } + retval = fwu_check_dp_configuration_size(); + if (retval < 0) + return retval; + break; + case PM_CONFIG_AREA: + if (!fwu->flash_properties.has_pm_config) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Permanent configuration not supported\n", + __func__); + return -EINVAL; + } + if (!fwu->img.contains_perm_config) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: No permanent configuration in firmware image\n", + __func__); + return -EINVAL; + } + retval = fwu_check_pm_configuration_size(); + if (retval < 0) + return retval; + break; + default: + dev_err(rmi4_data->pdev->dev.parent, + "%s: Configuration not supported\n", + __func__); + return -EINVAL; + } + + if (rmi4_data->sensor_sleep) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Sensor sleeping\n", + __func__); + return -ENODEV; + } + + rmi4_data->stay_awake = true; + + mutex_lock(&rmi4_data->rmi4_exp_init_mutex); + + pr_notice("%s: Start of write config process\n", __func__); + + config_area = fwu->config_area; + + retval = fwu_enter_flash_prog(); + if (retval < 0) + goto exit; + + fwu->config_area = config_area; + + if (fwu->config_area != PM_CONFIG_AREA) { + retval = fwu_erase_configuration(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to erase config\n", + __func__); + goto exit; + } + } + + switch (fwu->config_area) { + case UI_CONFIG_AREA: + retval = fwu_write_ui_configuration(); + if (retval < 0) + goto exit; + break; + case DP_CONFIG_AREA: + retval = fwu_write_dp_configuration(); + if (retval < 0) + goto exit; + break; + case PM_CONFIG_AREA: + retval = fwu_write_pm_configuration(); + if (retval < 0) + goto exit; + break; + } + + pr_notice("%s: Config written\n", __func__); + +exit: + switch (fwu->config_area) { + case UI_CONFIG_AREA: + rmi4_data->reset_device(rmi4_data, true); + break; + case DP_CONFIG_AREA: + case PM_CONFIG_AREA: + rmi4_data->reset_device(rmi4_data, false); + break; + } + + pr_notice("%s: End of write config process\n", __func__); + + mutex_unlock(&rmi4_data->rmi4_exp_init_mutex); + + rmi4_data->stay_awake = false; + + return retval; +} +#endif + +static int fwu_start_reflash(void) +{ + int retval = 0; + enum flash_area flash_area; + bool do_rebuild = false; + const struct firmware *fw_entry = NULL; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + if (rmi4_data->sensor_sleep) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Sensor sleeping\n", + __func__); + return -ENODEV; + } + + rmi4_data->stay_awake = true; + + mutex_lock(&rmi4_data->rmi4_exp_init_mutex); + + pr_notice("%s: Start of reflash process\n", __func__); + + if (fwu->image == NULL) { + retval = secure_memcpy(fwu->image_name, MAX_IMAGE_NAME_LEN, + FW_IMAGE_NAME, sizeof(FW_IMAGE_NAME), + sizeof(FW_IMAGE_NAME)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy image file name\n", + __func__); + goto exit; + } + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Requesting firmware image %s\n", + __func__, fwu->image_name); + + retval = request_firmware(&fw_entry, fwu->image_name, + rmi4_data->pdev->dev.parent); + if (retval != 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Firmware image %s not available\n", + __func__, fwu->image_name); + retval = -EINVAL; + goto exit; + } + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Firmware image size = %d\n", + __func__, (unsigned int)fw_entry->size); + + fwu->image = fw_entry->data; + } + + retval = fwu_parse_image_info(); + if (retval < 0) + goto exit; + + if (fwu->blkcount.total_count != fwu->img.blkcount.total_count) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Flash size mismatch\n", + __func__); + retval = -EINVAL; + goto exit; + } + + if (fwu->bl_version != fwu->img.bl_version) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Bootloader version mismatch\n", + __func__); + retval = -EINVAL; + goto exit; + } + + retval = fwu_read_flash_status(); + if (retval < 0) + goto exit; + + if (fwu->in_bl_mode) { + fwu->bl_mode_device = true; + dev_info(rmi4_data->pdev->dev.parent, + "%s: Device in bootloader mode\n", + __func__); + } else { + fwu->bl_mode_device = false; + } + + flash_area = fwu_go_nogo(); + + if (flash_area != NONE) { + retval = fwu_enter_flash_prog(); + if (retval < 0) { + rmi4_data->reset_device(rmi4_data, false); + goto exit; + } + } + +#ifdef F51_DISCRETE_FORCE + if (flash_area != NONE && !fwu->bl_mode_device) { + fwu->config_size = fwu->block_size * fwu->blkcount.ui_config; + fwu->config_area = UI_CONFIG_AREA; + + retval = fwu_allocate_read_config_buf(fwu->config_size); + if (retval < 0) { + rmi4_data->reset_device(rmi4_data, false); + goto exit; + } + + retval = fwu_read_f34_blocks(fwu->blkcount.ui_config, + CMD_READ_CONFIG); + if (retval < 0) { + rmi4_data->reset_device(rmi4_data, false); + goto exit; + } + + retval = secure_memcpy(fwu->cal_data, fwu->cal_data_buf_size, + &fwu->read_config_buf[fwu->cal_data_off], + fwu->cal_data_size, fwu->cal_data_size); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to save calibration data\n", + __func__); + rmi4_data->reset_device(rmi4_data, false); + goto exit; + } + } +#endif + + switch (flash_area) { + case UI_FIRMWARE: + do_rebuild = true; + retval = fwu_do_reflash(); +#ifdef F51_DISCRETE_FORCE + if (retval < 0) + break; + + if (fwu->has_utility_param || fwu->img.contains_utility_param) + break; + + rmi4_data->reset_device(rmi4_data, false); + + if (fwu->bl_mode_device || fwu->in_bl_mode) { + dev_info(rmi4_data->pdev->dev.parent, + "%s: Device in bootloader mode, skipping calibration data restoration\n", + __func__); + break; + } + + retval = fwu_do_restore_f51_cal_data(); +#endif + break; + case UI_CONFIG: + do_rebuild = true; + retval = fwu_check_ui_configuration_size(); + if (retval < 0) + break; + fwu->config_area = UI_CONFIG_AREA; + retval = fwu_erase_configuration(); + if (retval < 0) + break; + retval = fwu_write_ui_configuration(); +#ifdef F51_DISCRETE_FORCE + if (retval < 0) + break; + + if (fwu->has_utility_param) + break; + + retval = fwu_do_restore_f51_cal_data(); +#endif + break; + case NONE: + default: + break; + } + + if (retval < 0) { + do_rebuild = false; + rmi4_data->reset_device(rmi4_data, false); + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to do reflash\n", + __func__); + goto exit; + } + + if (fwu->do_lockdown && (fwu->img.lockdown.data != NULL)) { + switch (fwu->bl_version) { + case BL_V5: + case BL_V6: + retval = fwu_do_lockdown_v5v6(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to do lockdown\n", + __func__); + } + rmi4_data->reset_device(rmi4_data, false); + break; + case BL_V7: + case BL_V8: + retval = fwu_do_lockdown_v7(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to do lockdown\n", + __func__); + } + rmi4_data->reset_device(rmi4_data, false); + break; + default: + break; + } + } + +exit: + if (fw_entry) + release_firmware(fw_entry); + + if (do_rebuild) + rmi4_data->reset_device(rmi4_data, true); + + pr_notice("%s: End of reflash process\n", __func__); + + mutex_unlock(&rmi4_data->rmi4_exp_init_mutex); + + rmi4_data->stay_awake = false; + + return retval; +} + +static int fwu_recovery_check_status(void) +{ + int retval; + unsigned char data_base; + unsigned char status; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + data_base = fwu->f35_fd.data_base_addr; + + retval = synaptics_rmi4_reg_read(rmi4_data, + data_base + F35_ERROR_CODE_OFFSET, + &status, + 1); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read status\n", + __func__); + return retval; + } + + status = status & MASK_5BIT; + + if (status != 0x00) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Recovery mode status = %d\n", + __func__, status); + return -EINVAL; + } + + return 0; +} + +static int fwu_recovery_erase_completion(void) +{ + int retval; + unsigned char data_base; + unsigned char command; + unsigned char status; + unsigned int timeout = F35_ERASE_ALL_WAIT_MS / 20; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + data_base = fwu->f35_fd.data_base_addr; + + do { + command = 0x01; + retval = synaptics_rmi4_reg_write(rmi4_data, + fwu->f35_fd.cmd_base_addr, + &command, + sizeof(command)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to issue command\n", + __func__); + return retval; + } + + do { + retval = synaptics_rmi4_reg_read(rmi4_data, + fwu->f35_fd.cmd_base_addr, + &command, + sizeof(command)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read command status\n", + __func__); + return retval; + } + + if ((command & 0x01) == 0x00) + break; + + msleep(20); + timeout--; + } while (timeout > 0); + + if (timeout == 0) + goto exit; + + retval = synaptics_rmi4_reg_read(rmi4_data, + data_base + F35_FLASH_STATUS_OFFSET, + &status, + sizeof(status)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read flash status\n", + __func__); + return retval; + } + + if ((status & 0x01) == 0x00) + break; + + msleep(20); + timeout--; + } while (timeout > 0); + +exit: + if (timeout == 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Timed out waiting for flash erase completion\n", + __func__); + return -ETIMEDOUT; + } + + return 0; +} + +static int fwu_recovery_erase_all(void) +{ + int retval; + unsigned char ctrl_base; + unsigned char command = CMD_F35_ERASE_ALL; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + ctrl_base = fwu->f35_fd.ctrl_base_addr; + + retval = synaptics_rmi4_reg_write(rmi4_data, + ctrl_base + F35_CHUNK_COMMAND_OFFSET, + &command, + sizeof(command)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to issue erase all command\n", + __func__); + return retval; + } + + if (fwu->f35_fd.cmd_base_addr) { + retval = fwu_recovery_erase_completion(); + if (retval < 0) + return retval; + } else { + msleep(F35_ERASE_ALL_WAIT_MS); + } + + retval = fwu_recovery_check_status(); + if (retval < 0) + return retval; + + return 0; +} + +static int fwu_recovery_write_chunk(void) +{ + int retval; + unsigned char ctrl_base; + unsigned char chunk_number[] = {0, 0}; + unsigned char chunk_spare; + unsigned char chunk_size; + unsigned char buf[F35_CHUNK_SIZE + 1]; + unsigned short chunk; + unsigned short chunk_total; + unsigned short bytes_written = 0; + unsigned char *chunk_ptr = (unsigned char *)fwu->image; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + ctrl_base = fwu->f35_fd.ctrl_base_addr; + + retval = synaptics_rmi4_reg_write(rmi4_data, + ctrl_base + F35_CHUNK_NUM_LSB_OFFSET, + chunk_number, + sizeof(chunk_number)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write chunk number\n", + __func__); + return retval; + } + + buf[sizeof(buf) - 1] = CMD_F35_WRITE_CHUNK; + + chunk_total = fwu->image_size / F35_CHUNK_SIZE; + chunk_spare = fwu->image_size % F35_CHUNK_SIZE; + if (chunk_spare) + chunk_total++; + + for (chunk = 0; chunk < chunk_total; chunk++) { + if (chunk_spare && chunk == chunk_total - 1) + chunk_size = chunk_spare; + else + chunk_size = F35_CHUNK_SIZE; + + memset(buf, 0x00, F35_CHUNK_SIZE); + secure_memcpy(buf, sizeof(buf), chunk_ptr, + fwu->image_size - bytes_written, + chunk_size); + + retval = synaptics_rmi4_reg_write(rmi4_data, + ctrl_base + F35_CHUNK_DATA_OFFSET, + buf, + sizeof(buf)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write chunk data (chunk %d)\n", + __func__, chunk); + return retval; + } + chunk_ptr += chunk_size; + bytes_written += chunk_size; + } + + retval = fwu_recovery_check_status(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write chunk data\n", + __func__); + return retval; + } + + return 0; +} + +static int fwu_recovery_reset(void) +{ + int retval; + unsigned char ctrl_base; + unsigned char command = CMD_F35_RESET; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + ctrl_base = fwu->f35_fd.ctrl_base_addr; + + retval = synaptics_rmi4_reg_write(rmi4_data, + ctrl_base + F35_CHUNK_COMMAND_OFFSET, + &command, + sizeof(command)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to issue reset command\n", + __func__); + return retval; + } + + msleep(F35_RESET_WAIT_MS); + + return 0; +} + +static int fwu_start_recovery(void) +{ + int retval; + const struct firmware *fw_entry = NULL; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + if (rmi4_data->sensor_sleep) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Sensor sleeping\n", + __func__); + return -ENODEV; + } + + rmi4_data->stay_awake = true; + + mutex_lock(&rmi4_data->rmi4_exp_init_mutex); + + pr_notice("%s: Start of recovery process\n", __func__); + + if (fwu->image == NULL) { + retval = secure_memcpy(fwu->image_name, MAX_IMAGE_NAME_LEN, + FW_IHEX_NAME, sizeof(FW_IHEX_NAME), + sizeof(FW_IHEX_NAME)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy ihex file name\n", + __func__); + goto exit; + } + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Requesting firmware ihex %s\n", + __func__, fwu->image_name); + + retval = request_firmware(&fw_entry, fwu->image_name, + rmi4_data->pdev->dev.parent); + if (retval != 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Firmware ihex %s not available\n", + __func__, fwu->image_name); + retval = -EINVAL; + goto exit; + } + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Firmware image size = %d\n", + __func__, (unsigned int)fw_entry->size); + + fwu->image = fw_entry->data; + fwu->image_size = fw_entry->size; + } + + retval = rmi4_data->irq_enable(rmi4_data, false, false); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to disable interrupt\n", + __func__); + goto exit; + } + + retval = fwu_recovery_erase_all(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to do erase all in recovery mode\n", + __func__); + goto exit; + } + + pr_notice("%s: External flash erased\n", __func__); + + retval = fwu_recovery_write_chunk(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write chunk data in recovery mode\n", + __func__); + goto exit; + } + + pr_notice("%s: Chunk data programmed\n", __func__); + + retval = fwu_recovery_reset(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to reset device in recovery mode\n", + __func__); + goto exit; + } + + pr_notice("%s: Recovery mode reset issued\n", __func__); + + rmi4_data->reset_device(rmi4_data, true); + + retval = 0; + +exit: + if (fw_entry) + release_firmware(fw_entry); + + pr_notice("%s: End of recovery process\n", __func__); + + mutex_unlock(&rmi4_data->rmi4_exp_init_mutex); + + rmi4_data->stay_awake = false; + + return retval; +} + +int synaptics_fw_updater(const unsigned char *fw_data) +{ + int retval; + + if (!fwu) + return -ENODEV; + + if (!fwu->initialized) + return -ENODEV; + + if (fwu->in_ub_mode) { + fwu->image = NULL; + retval = fwu_start_recovery(); + if (retval < 0) + return retval; + } + + fwu->image = fw_data; + + retval = fwu_start_reflash(); + + fwu->image = NULL; + + return retval; +} +EXPORT_SYMBOL(synaptics_fw_updater); + +#ifdef DO_STARTUP_FW_UPDATE +static void fwu_startup_fw_update_work(struct work_struct *work) +{ + static unsigned char do_once = 1; +#ifdef WAIT_FOR_FB_READY + unsigned int timeout; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; +#endif + + if (!do_once) + return; + do_once = 0; + +#ifdef WAIT_FOR_FB_READY + timeout = FB_READY_TIMEOUT_S * 1000 / FB_READY_WAIT_MS + 1; + + while (!rmi4_data->fb_ready) { + msleep(FB_READY_WAIT_MS); + timeout--; + if (timeout == 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Timed out waiting for FB ready\n", + __func__); + return; + } + } +#endif + + synaptics_fw_updater(NULL); +} +#endif + +#ifdef CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE_EXTRA_SYSFS +static ssize_t fwu_sysfs_show_image(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + if (!mutex_trylock(&fwu_sysfs_mutex)) + return -EBUSY; + + if (count < fwu->config_size) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Not enough space (%d bytes) in buffer\n", + __func__, (unsigned int)count); + retval = -EINVAL; + goto exit; + } + + retval = secure_memcpy(buf, count, fwu->read_config_buf, + fwu->read_config_buf_size, fwu->config_size); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy config data\n", + __func__); + goto exit; + } else { + retval = fwu->config_size; + } + +exit: + mutex_unlock(&fwu_sysfs_mutex); + return retval; +} + +static ssize_t fwu_sysfs_store_image(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + if (!mutex_trylock(&fwu_sysfs_mutex)) + return -EBUSY; + + retval = secure_memcpy(&fwu->ext_data_source[fwu->data_pos], + fwu->image_size - fwu->data_pos, buf, count, count); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy image data\n", + __func__); + goto exit; + } else { + retval = count; + } + + fwu->data_pos += count; + +exit: + mutex_unlock(&fwu_sysfs_mutex); + return retval; +} + +static ssize_t fwu_sysfs_do_recovery_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned int input; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + if (!mutex_trylock(&fwu_sysfs_mutex)) + return -EBUSY; + + if (kstrtouint(buf, 10, &input) != 1) { + retval = -EINVAL; + goto exit; + } + + if (!fwu->in_ub_mode) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Not in microbootloader mode\n", + __func__); + retval = -EINVAL; + goto exit; + } + + if (!fwu->ext_data_source) { + retval = -EINVAL; + goto exit; + } else { + fwu->image = fwu->ext_data_source; + } + + retval = fwu_start_recovery(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to do recovery\n", + __func__); + goto exit; + } + + retval = count; + +exit: + kfree(fwu->ext_data_source); + fwu->ext_data_source = NULL; + fwu->image = NULL; + mutex_unlock(&fwu_sysfs_mutex); + return retval; +} + +static ssize_t fwu_sysfs_do_reflash_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned int input; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + if (!mutex_trylock(&fwu_sysfs_mutex)) + return -EBUSY; + + if (kstrtouint(buf, 10, &input) != 1) { + retval = -EINVAL; + goto exit; + } + + if (fwu->in_ub_mode) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: In microbootloader mode\n", + __func__); + retval = -EINVAL; + goto exit; + } + + if (!fwu->ext_data_source) { + retval = -EINVAL; + goto exit; + } else { + fwu->image = fwu->ext_data_source; + } + + if (input & LOCKDOWN) { + fwu->do_lockdown = true; + input &= ~LOCKDOWN; + } + + if ((input != NORMAL) && (input != FORCE)) { + retval = -EINVAL; + goto exit; + } + + if (input == FORCE) + fwu->force_update = true; + + retval = synaptics_fw_updater(fwu->image); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to do reflash\n", + __func__); + goto exit; + } + + retval = count; + +exit: + kfree(fwu->ext_data_source); + fwu->ext_data_source = NULL; + fwu->image = NULL; + fwu->force_update = FORCE_UPDATE; + fwu->do_lockdown = DO_LOCKDOWN; + mutex_unlock(&fwu_sysfs_mutex); + return retval; +} + +static ssize_t fwu_sysfs_write_config_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned int input; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + if (!mutex_trylock(&fwu_sysfs_mutex)) + return -EBUSY; + + if (kstrtouint(buf, 10, &input) != 1) { + retval = -EINVAL; + goto exit; + } + + if (input != 1) { + retval = -EINVAL; + goto exit; + } + + if (fwu->in_ub_mode) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: In microbootloader mode\n", + __func__); + retval = -EINVAL; + goto exit; + } + + if (!fwu->ext_data_source) { + retval = -EINVAL; + goto exit; + } else { + fwu->image = fwu->ext_data_source; + } + + retval = fwu_start_write_config(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write config\n", + __func__); + goto exit; + } + + retval = count; + +exit: + kfree(fwu->ext_data_source); + fwu->ext_data_source = NULL; + fwu->image = NULL; + mutex_unlock(&fwu_sysfs_mutex); + return retval; +} + +static ssize_t fwu_sysfs_read_config_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned int input; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + if (kstrtouint(buf, 10, &input) != 1) + return -EINVAL; + + if (input != 1) + return -EINVAL; + + if (!mutex_trylock(&fwu_sysfs_mutex)) + return -EBUSY; + + if (fwu->in_ub_mode) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: In microbootloader mode\n", + __func__); + retval = -EINVAL; + goto exit; + } + + retval = fwu_do_read_config(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read config\n", + __func__); + goto exit; + } + + retval = count; + +exit: + mutex_unlock(&fwu_sysfs_mutex); + return retval; +} + +static ssize_t fwu_sysfs_config_area_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned long config_area; + + retval = sstrtoul(buf, 10, &config_area); + if (retval) + return retval; + + if (!mutex_trylock(&fwu_sysfs_mutex)) + return -EBUSY; + + fwu->config_area = config_area; + + mutex_unlock(&fwu_sysfs_mutex); + + return count; +} + +static ssize_t fwu_sysfs_image_name_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + if (!mutex_trylock(&fwu_sysfs_mutex)) + return -EBUSY; + + retval = secure_memcpy(fwu->image_name, MAX_IMAGE_NAME_LEN, + buf, count, count); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy image file name\n", + __func__); + } else { + retval = count; + } + + mutex_unlock(&fwu_sysfs_mutex); + + return retval; +} + +static ssize_t fwu_sysfs_image_size_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned long size; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + retval = sstrtoul(buf, 10, &size); + if (retval) + return retval; + + if (!mutex_trylock(&fwu_sysfs_mutex)) + return -EBUSY; + + fwu->image_size = size; + fwu->data_pos = 0; + + kfree(fwu->ext_data_source); + fwu->ext_data_source = kzalloc(fwu->image_size, GFP_KERNEL); + if (!fwu->ext_data_source) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for image data\n", + __func__); + retval = -ENOMEM; + } + retval = count; + + mutex_unlock(&fwu_sysfs_mutex); + + return retval; +} + +static ssize_t fwu_sysfs_block_size_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + + if (!mutex_trylock(&fwu_sysfs_mutex)) + return -EBUSY; + + retval = snprintf(buf, PAGE_SIZE, "%u\n", fwu->block_size); + + mutex_unlock(&fwu_sysfs_mutex); + + return retval; +} + +static ssize_t fwu_sysfs_firmware_block_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + + if (!mutex_trylock(&fwu_sysfs_mutex)) + return -EBUSY; + + retval = snprintf(buf, PAGE_SIZE, "%u\n", fwu->blkcount.ui_firmware); + + mutex_unlock(&fwu_sysfs_mutex); + + return retval; +} + +static ssize_t fwu_sysfs_configuration_block_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + + if (!mutex_trylock(&fwu_sysfs_mutex)) + return -EBUSY; + + retval = snprintf(buf, PAGE_SIZE, "%u\n", fwu->blkcount.ui_config); + + mutex_unlock(&fwu_sysfs_mutex); + + return retval; +} + +static ssize_t fwu_sysfs_disp_config_block_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + + if (!mutex_trylock(&fwu_sysfs_mutex)) + return -EBUSY; + + retval = snprintf(buf, PAGE_SIZE, "%u\n", fwu->blkcount.dp_config); + + mutex_unlock(&fwu_sysfs_mutex); + + return retval; +} + +static ssize_t fwu_sysfs_perm_config_block_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + + if (!mutex_trylock(&fwu_sysfs_mutex)) + return -EBUSY; + + retval = snprintf(buf, PAGE_SIZE, "%u\n", fwu->blkcount.pm_config); + + mutex_unlock(&fwu_sysfs_mutex); + + return retval; +} + +static ssize_t fwu_sysfs_bl_config_block_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + + if (!mutex_trylock(&fwu_sysfs_mutex)) + return -EBUSY; + + retval = snprintf(buf, PAGE_SIZE, "%u\n", fwu->blkcount.bl_config); + + mutex_unlock(&fwu_sysfs_mutex); + + return retval; +} + +static ssize_t fwu_sysfs_utility_parameter_block_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + + if (!mutex_trylock(&fwu_sysfs_mutex)) + return -EBUSY; + + retval = snprintf(buf, PAGE_SIZE, "%u\n", fwu->blkcount.utility_param); + + mutex_unlock(&fwu_sysfs_mutex); + + return retval; +} + +static ssize_t fwu_sysfs_guest_code_block_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + + if (!mutex_trylock(&fwu_sysfs_mutex)) + return -EBUSY; + + retval = snprintf(buf, PAGE_SIZE, "%u\n", fwu->blkcount.guest_code); + + mutex_unlock(&fwu_sysfs_mutex); + + return retval; +} + +static ssize_t fwu_sysfs_write_guest_code_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned int input; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + if (!mutex_trylock(&fwu_sysfs_mutex)) + return -EBUSY; + + if (kstrtouint(buf, 10, &input) != 1) { + retval = -EINVAL; + goto exit; + } + + if (input != 1) { + retval = -EINVAL; + goto exit; + } + + if (fwu->in_ub_mode) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: In microbootloader mode\n", + __func__); + retval = -EINVAL; + goto exit; + } + + if (!fwu->ext_data_source) { + retval = -EINVAL; + goto exit; + } else { + fwu->image = fwu->ext_data_source; + } + + retval = fwu_start_write_guest_code(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write guest code\n", + __func__); + goto exit; + } + + retval = count; + +exit: + kfree(fwu->ext_data_source); + fwu->ext_data_source = NULL; + fwu->image = NULL; + mutex_unlock(&fwu_sysfs_mutex); + return retval; +} + +#ifdef SYNA_TDDI +static ssize_t fwu_sysfs_read_lockdown_code_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned short lockdown_data_size; + unsigned char *lockdown_data; + char ld_val[2]; + int retval = 0; + int i = 0; + + if (!mutex_trylock(&fwu_sysfs_mutex)) + return -EBUSY; + + lockdown_data_size = fwu->blkcount.tddi_lockdown_data * fwu->block_size; + lockdown_data = kzalloc(lockdown_data_size, GFP_KERNEL); + if (!lockdown_data) { + mutex_unlock(&fwu_sysfs_mutex); + return -ENOMEM; + } + + if (get_tddi_lockdown_data(lockdown_data, lockdown_data_size) < 0) { + kfree(lockdown_data); + mutex_unlock(&fwu_sysfs_mutex); + return -EINVAL; + } + + for (i = 0; i < lockdown_data_size; i++) { + retval += snprintf(ld_val, PAGE_SIZE, "%02x", + *(lockdown_data + i)); + strlcat(buf, ld_val, lockdown_data_size); + } + *(buf + retval) = '\n'; + kfree(lockdown_data); + mutex_unlock(&fwu_sysfs_mutex); + return retval + 1; +} + +static ssize_t fwu_sysfs_write_lockdown_code_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned short lockdown_data_size = (count - 1) / 2; + unsigned char *lockdown_data; + unsigned char temp[2]; + int ld_val; + int i = 0; + + for (i = 0; i < (count - 1); i++) { + if (((*buf >= '0') && (*buf <= '9')) || + (('a' < *buf) && (*buf > 'f')) || + (('A' < *buf) && (*buf > 'F'))) + continue; + else + return -EINVAL; + } + + if (count % 2 != 1) + return -EINVAL; + + lockdown_data = kzalloc(lockdown_data_size, GFP_KERNEL); + if (!lockdown_data) + return -ENOMEM; + + for (i = 0; i < lockdown_data_size; i++) { + memcpy(temp, (buf + 2 * i), sizeof(temp)); + if (kstrtoint(temp, 16, &ld_val) == 1) + *(lockdown_data + i) = ld_val & 0xff; + } + + if (!mutex_trylock(&fwu_sysfs_mutex)) + return -EBUSY; + + if (set_tddi_lockdown_data(lockdown_data, lockdown_data_size) < 0) { + kfree(lockdown_data); + mutex_unlock(&fwu_sysfs_mutex); + return -EINVAL; + } + kfree(lockdown_data); + mutex_unlock(&fwu_sysfs_mutex); + return count; +} +#endif +#endif +static void synaptics_rmi4_fwu_attn(struct synaptics_rmi4_data *rmi4_data, + unsigned char intr_mask) +{ + if (!fwu) + return; + + if (fwu->intr_mask & intr_mask) + fwu_read_flash_status(); + + return; +} + +static int synaptics_rmi4_fwu_init(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + unsigned char attr_count; + struct pdt_properties pdt_props; + + if (fwu) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Handle already exists\n", + __func__); + return 0; + } + + fwu = kzalloc(sizeof(*fwu), GFP_KERNEL); + if (!fwu) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for fwu\n", + __func__); + retval = -ENOMEM; + goto exit; + } + + fwu->image_name = kzalloc(MAX_IMAGE_NAME_LEN, GFP_KERNEL); + if (!fwu->image_name) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for image name\n", + __func__); + retval = -ENOMEM; + goto exit_free_fwu; + } + + fwu->rmi4_data = rmi4_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + PDT_PROPS, + pdt_props.data, + sizeof(pdt_props.data)); + if (retval < 0) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Failed to read PDT properties, assuming 0x00\n", + __func__); + } else if (pdt_props.has_bsr) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Reflash for LTS not currently supported\n", + __func__); + retval = -ENODEV; + goto exit_free_mem; + } + + retval = fwu_scan_pdt(); + if (retval < 0) + goto exit_free_mem; + + if (!fwu->in_ub_mode) { + retval = fwu_read_f34_queries(); + if (retval < 0) + goto exit_free_mem; + + retval = fwu_get_device_config_id(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read device config ID\n", + __func__); + goto exit_free_mem; + } + } + + fwu->force_update = FORCE_UPDATE; + fwu->do_lockdown = DO_LOCKDOWN; + fwu->initialized = true; + +#ifdef DO_STARTUP_FW_UPDATE + fwu->fwu_workqueue = create_singlethread_workqueue("fwu_workqueue"); + INIT_WORK(&fwu->fwu_work, fwu_startup_fw_update_work); + queue_work(fwu->fwu_workqueue, + &fwu->fwu_work); +#endif + +#ifdef F51_DISCRETE_FORCE + fwu_read_flash_status(); + if (!fwu->in_bl_mode) { + retval = fwu_f51_force_data_init(); + if (retval < 0) + goto exit_free_mem; + } +#endif + + if (ENABLE_SYS_REFLASH == false) + return 0; + +#ifdef CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE_EXTRA_SYSFS + retval = sysfs_create_bin_file(&rmi4_data->input_dev->dev.kobj, + &dev_attr_data); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create sysfs bin file\n", + __func__); + goto exit_free_mem; + } +#endif + + for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) { + retval = sysfs_create_file(&rmi4_data->input_dev->dev.kobj, + &attrs[attr_count].attr); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create sysfs attributes\n", + __func__); + retval = -ENODEV; + goto exit_remove_attrs; + } + } + + return 0; + +exit_remove_attrs: + for (attr_count--; attr_count >= 0; attr_count--) { + sysfs_remove_file(&rmi4_data->input_dev->dev.kobj, + &attrs[attr_count].attr); + } + +#ifdef CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE_EXTRA_SYSFS + sysfs_remove_bin_file(&rmi4_data->input_dev->dev.kobj, &dev_attr_data); +#endif + +exit_free_mem: + kfree(fwu->image_name); + +exit_free_fwu: + kfree(fwu); + fwu = NULL; + +exit: + return retval; +} + +static void synaptics_rmi4_fwu_remove(struct synaptics_rmi4_data *rmi4_data) +{ + unsigned char attr_count; + + if (!fwu) + goto exit; + +#ifdef DO_STARTUP_FW_UPDATE + cancel_work_sync(&fwu->fwu_work); + flush_workqueue(fwu->fwu_workqueue); + destroy_workqueue(fwu->fwu_workqueue); +#endif + +#ifdef F51_DISCRETE_FORCE + kfree(fwu->cal_data); +#endif + kfree(fwu->read_config_buf); + kfree(fwu->image_name); + kfree(fwu); + fwu = NULL; + + if (ENABLE_SYS_REFLASH == false) + goto exit; + + for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) { + sysfs_remove_file(&rmi4_data->input_dev->dev.kobj, + &attrs[attr_count].attr); + } + +#ifdef CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE_EXTRA_SYSFS + sysfs_remove_bin_file(&rmi4_data->input_dev->dev.kobj, &dev_attr_data); +#endif + +exit: + complete(&fwu_remove_complete); +} + +static void synaptics_rmi4_fwu_reset(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + + if (!fwu) { + synaptics_rmi4_fwu_init(rmi4_data); + return; + } + + retval = fwu_scan_pdt(); + if (retval < 0) + return; + + if (!fwu->in_ub_mode) + fwu_read_f34_queries(); + +#ifdef F51_DISCRETE_FORCE + fwu_read_flash_status(); + if (!fwu->in_bl_mode) + fwu_f51_force_data_init(); +#endif + + return; +} + +static struct synaptics_rmi4_exp_fn fwu_module = { + .fn_type = RMI_FW_UPDATER, + .init = synaptics_rmi4_fwu_init, + .remove = synaptics_rmi4_fwu_remove, + .reset = synaptics_rmi4_fwu_reset, + .reinit = NULL, + .early_suspend = NULL, + .suspend = NULL, + .resume = NULL, + .late_resume = NULL, + .attn = synaptics_rmi4_fwu_attn, +}; + +static int __init rmi4_fw_update_module_init(void) +{ + synaptics_rmi4_new_function(&fwu_module, true); + + return 0; +} + +static void __exit rmi4_fw_update_module_exit(void) +{ + synaptics_rmi4_new_function(&fwu_module, false); + + wait_for_completion(&fwu_remove_complete); +} + +module_init(rmi4_fw_update_module_init); +module_exit(rmi4_fw_update_module_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics DSX FW Update Module"); +MODULE_LICENSE("GPL v2"); diff --git a/qcom/opensource/touch-drivers/synaptics_dsx/synaptics_dsx_gesture.c b/qcom/opensource/touch-drivers/synaptics_dsx/synaptics_dsx_gesture.c new file mode 100644 index 0000000000..64f960efb1 --- /dev/null +++ b/qcom/opensource/touch-drivers/synaptics_dsx/synaptics_dsx_gesture.c @@ -0,0 +1,2291 @@ +/* + * Synaptics DSX touchscreen driver + * + * Copyright (C) 2012-2016 Synaptics Incorporated. All rights reserved. + * + * Copyright (c) 2018-2021 The Linux Foundation. All rights reserved. + * Copyright (C) 2012 Alexandra Chin + * Copyright (C) 2012 Scott Lin + * + * 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 +#include +#include +#include "synaptics_dsx_core.h" + +#define GESTURE_PHYS_NAME "synaptics_dsx/gesture" + +#define TUNING_SYSFS_DIR_NAME "tuning" + +#define STORE_GESTURES +#ifdef STORE_GESTURES +#define GESTURES_TO_STORE 10 +#endif + +#define CTRL23_FINGER_REPORT_ENABLE_BIT 0 +#define CTRL27_UDG_ENABLE_BIT 4 +#define WAKEUP_GESTURE_MODE 0x02 + +static ssize_t udg_sysfs_engine_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t udg_sysfs_detection_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t udg_sysfs_detection_score_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t udg_sysfs_detection_index_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t udg_sysfs_registration_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t udg_sysfs_registration_begin_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t udg_sysfs_registration_status_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t udg_sysfs_template_size_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t udg_sysfs_template_max_index_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t udg_sysfs_template_detection_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t udg_sysfs_template_index_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t udg_sysfs_template_valid_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t udg_sysfs_template_valid_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t udg_sysfs_template_clear_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t udg_sysfs_trace_size_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t udg_sysfs_template_data_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t udg_sysfs_template_data_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t udg_sysfs_trace_data_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t udg_sysfs_template_displacement_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t udg_sysfs_template_displacement_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t udg_sysfs_rotation_invariance_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t udg_sysfs_rotation_invariance_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t udg_sysfs_scale_invariance_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t udg_sysfs_scale_invariance_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t udg_sysfs_threshold_factor_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t udg_sysfs_threshold_factor_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t udg_sysfs_match_metric_threshold_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t udg_sysfs_match_metric_threshold_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t udg_sysfs_max_inter_stroke_time_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t udg_sysfs_max_inter_stroke_time_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static int udg_read_tuning_params(void); + +static int udg_write_tuning_params(void); + +static int udg_detection_enable(bool enable); + +static int udg_engine_enable(bool enable); + +static int udg_set_index(unsigned char index); + +#ifdef STORE_GESTURES +static int udg_read_valid_data(void); +static int udg_write_valid_data(void); +static int udg_read_template_data(unsigned char index); +static int udg_write_template_data(void); +#endif + +enum gesture_type { + DETECTION = 0x0f, + REGISTRATION = 0x10, +}; + +struct udg_tuning { + union { + struct { + unsigned char maximum_number_of_templates; + unsigned char template_size; + unsigned char template_disp_lsb; + unsigned char template_disp_msb; + unsigned char rotation_inv_lsb; + unsigned char rotation_inv_msb; + unsigned char scale_inv_lsb; + unsigned char scale_inv_msb; + unsigned char thres_factor_lsb; + unsigned char thres_factor_msb; + unsigned char metric_thres_lsb; + unsigned char metric_thres_msb; + unsigned char inter_stroke_lsb; + unsigned char inter_stroke_msb; + } __packed; + unsigned char data[14]; + }; +}; + +struct udg_addr { + unsigned short data_4; + unsigned short ctrl_18; + unsigned short ctrl_20; + unsigned short ctrl_23; + unsigned short ctrl_27; + unsigned short ctrl_41; + unsigned short trace_x; + unsigned short trace_y; + unsigned short trace_segment; + unsigned short template_helper; + unsigned short template_data; + unsigned short template_flags; +}; + +struct synaptics_rmi4_f12_query_0 { + union { + struct { + struct { + unsigned char has_register_descriptors:1; + unsigned char has_closed_cover:1; + unsigned char has_fast_glove_detect:1; + unsigned char has_dribble:1; + unsigned char has_4p4_jitter_filter_strength:1; + unsigned char f12_query0_s0_b5__7:3; + } __packed; + struct { + unsigned char max_num_templates:4; + unsigned char f12_query0_s1_b4__7:4; + unsigned char template_size_lsb; + unsigned char template_size_msb; + } __packed; + }; + unsigned char data[4]; + }; +}; + +struct synaptics_rmi4_f12_query_5 { + union { + struct { + unsigned char size_of_query6; + struct { + unsigned char ctrl0_is_present:1; + unsigned char ctrl1_is_present:1; + unsigned char ctrl2_is_present:1; + unsigned char ctrl3_is_present:1; + unsigned char ctrl4_is_present:1; + unsigned char ctrl5_is_present:1; + unsigned char ctrl6_is_present:1; + unsigned char ctrl7_is_present:1; + } __packed; + struct { + unsigned char ctrl8_is_present:1; + unsigned char ctrl9_is_present:1; + unsigned char ctrl10_is_present:1; + unsigned char ctrl11_is_present:1; + unsigned char ctrl12_is_present:1; + unsigned char ctrl13_is_present:1; + unsigned char ctrl14_is_present:1; + unsigned char ctrl15_is_present:1; + } __packed; + struct { + unsigned char ctrl16_is_present:1; + unsigned char ctrl17_is_present:1; + unsigned char ctrl18_is_present:1; + unsigned char ctrl19_is_present:1; + unsigned char ctrl20_is_present:1; + unsigned char ctrl21_is_present:1; + unsigned char ctrl22_is_present:1; + unsigned char ctrl23_is_present:1; + } __packed; + struct { + unsigned char ctrl24_is_present:1; + unsigned char ctrl25_is_present:1; + unsigned char ctrl26_is_present:1; + unsigned char ctrl27_is_present:1; + unsigned char ctrl28_is_present:1; + unsigned char ctrl29_is_present:1; + unsigned char ctrl30_is_present:1; + unsigned char ctrl31_is_present:1; + } __packed; + struct { + unsigned char ctrl32_is_present:1; + unsigned char ctrl33_is_present:1; + unsigned char ctrl34_is_present:1; + unsigned char ctrl35_is_present:1; + unsigned char ctrl36_is_present:1; + unsigned char ctrl37_is_present:1; + unsigned char ctrl38_is_present:1; + unsigned char ctrl39_is_present:1; + } __packed; + struct { + unsigned char ctrl40_is_present:1; + unsigned char ctrl41_is_present:1; + unsigned char ctrl42_is_present:1; + unsigned char ctrl43_is_present:1; + unsigned char ctrl44_is_present:1; + unsigned char ctrl45_is_present:1; + unsigned char ctrl46_is_present:1; + unsigned char ctrl47_is_present:1; + } __packed; + }; + unsigned char data[7]; + }; +}; + +struct synaptics_rmi4_f12_query_8 { + union { + struct { + unsigned char size_of_query9; + struct { + unsigned char data0_is_present:1; + unsigned char data1_is_present:1; + unsigned char data2_is_present:1; + unsigned char data3_is_present:1; + unsigned char data4_is_present:1; + unsigned char data5_is_present:1; + unsigned char data6_is_present:1; + unsigned char data7_is_present:1; + } __packed; + struct { + unsigned char data8_is_present:1; + unsigned char data9_is_present:1; + unsigned char data10_is_present:1; + unsigned char data11_is_present:1; + unsigned char data12_is_present:1; + unsigned char data13_is_present:1; + unsigned char data14_is_present:1; + unsigned char data15_is_present:1; + } __packed; + struct { + unsigned char data16_is_present:1; + unsigned char data17_is_present:1; + unsigned char data18_is_present:1; + unsigned char data19_is_present:1; + unsigned char data20_is_present:1; + unsigned char data21_is_present:1; + unsigned char data22_is_present:1; + unsigned char data23_is_present:1; + } __packed; + }; + unsigned char data[4]; + }; +}; + +struct synaptics_rmi4_f12_control_41 { + union { + struct { + unsigned char enable_registration:1; + unsigned char template_index:4; + unsigned char begin:1; + unsigned char f12_ctrl41_b6__7:2; + } __packed; + unsigned char data[1]; + }; +}; + +struct synaptics_rmi4_udg_handle { + atomic_t attn_event; + unsigned char intr_mask; + unsigned char report_flags; + unsigned char object_type_enable1; + unsigned char object_type_enable2; + unsigned char trace_size; + unsigned char template_index; + unsigned char max_num_templates; + unsigned char detection_score; + unsigned char detection_index; + unsigned char detection_status; + unsigned char registration_status; + unsigned char *ctrl_buf; + unsigned char *trace_data_buf; + unsigned char *template_data_buf; +#ifdef STORE_GESTURES + unsigned char gestures_to_store; + unsigned char *storage_buf; + unsigned char valid_buf[2]; +#endif + unsigned short trace_data_buf_size; + unsigned short template_size; + unsigned short template_data_size; + unsigned short query_base_addr; + unsigned short control_base_addr; + unsigned short data_base_addr; + unsigned short command_base_addr; + unsigned short ctrl_18_sub10_off; + unsigned short ctrl_20_sub1_off; + unsigned short ctrl_23_sub3_off; + unsigned short ctrl_27_sub5_off; + struct input_dev *udg_dev; + struct kobject *tuning_dir; + struct udg_addr addr; + struct udg_tuning tuning; + struct synaptics_rmi4_data *rmi4_data; +}; + +static struct device_attribute attrs[] = { + __ATTR(engine_enable, 0220, + synaptics_rmi4_show_error, + udg_sysfs_engine_enable_store), + __ATTR(detection_enable, 0220, + synaptics_rmi4_show_error, + udg_sysfs_detection_enable_store), + __ATTR(detection_score, 0444, + udg_sysfs_detection_score_show, + synaptics_rmi4_store_error), + __ATTR(detection_index, 0444, + udg_sysfs_detection_index_show, + synaptics_rmi4_store_error), + __ATTR(registration_enable, 0220, + synaptics_rmi4_show_error, + udg_sysfs_registration_enable_store), + __ATTR(registration_begin, 0220, + synaptics_rmi4_show_error, + udg_sysfs_registration_begin_store), + __ATTR(registration_status, 0444, + udg_sysfs_registration_status_show, + synaptics_rmi4_store_error), + __ATTR(template_size, 0444, + udg_sysfs_template_size_show, + synaptics_rmi4_store_error), + __ATTR(template_max_index, 0444, + udg_sysfs_template_max_index_show, + synaptics_rmi4_store_error), + __ATTR(template_detection, 0444, + udg_sysfs_template_detection_show, + synaptics_rmi4_store_error), + __ATTR(template_index, 0220, + synaptics_rmi4_show_error, + udg_sysfs_template_index_store), + __ATTR(template_valid, 0664, + udg_sysfs_template_valid_show, + udg_sysfs_template_valid_store), + __ATTR(template_clear, 0220, + synaptics_rmi4_show_error, + udg_sysfs_template_clear_store), + __ATTR(trace_size, 0444, + udg_sysfs_trace_size_show, + synaptics_rmi4_store_error), +}; + +static struct bin_attribute template_data = { + .attr = { + .name = "template_data", + .mode = 0664, + }, + .size = 0, + .read = udg_sysfs_template_data_show, + .write = udg_sysfs_template_data_store, +}; + +static struct bin_attribute trace_data = { + .attr = { + .name = "trace_data", + .mode = 0444, + }, + .size = 0, + .read = udg_sysfs_trace_data_show, + .write = NULL, +}; + +static struct device_attribute params[] = { + __ATTR(template_displacement, 0664, + udg_sysfs_template_displacement_show, + udg_sysfs_template_displacement_store), + __ATTR(rotation_invariance, 0664, + udg_sysfs_rotation_invariance_show, + udg_sysfs_rotation_invariance_store), + __ATTR(scale_invariance, 0664, + udg_sysfs_scale_invariance_show, + udg_sysfs_scale_invariance_store), + __ATTR(threshold_factor, 0664, + udg_sysfs_threshold_factor_show, + udg_sysfs_threshold_factor_store), + __ATTR(match_metric_threshold, 0664, + udg_sysfs_match_metric_threshold_show, + udg_sysfs_match_metric_threshold_store), + __ATTR(max_inter_stroke_time, 0664, + udg_sysfs_max_inter_stroke_time_show, + udg_sysfs_max_inter_stroke_time_store), +}; + +static struct synaptics_rmi4_udg_handle *udg; + +static unsigned char ctrl_18_sub_size[] = {10, 10, 10, 2, 3, 4, 3, 3, 1, 1}; +static unsigned char ctrl_20_sub_size[] = {2}; +static unsigned char ctrl_23_sub_size[] = {1, 1, 1}; +static unsigned char ctrl_27_sub_size[] = {1, 5, 2, 1, 7}; + +DECLARE_COMPLETION(udg_remove_complete); + +static ssize_t udg_sysfs_engine_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + bool enable; + unsigned int input; + + if (kstrtouint(buf, 10, &input) != 1) + return -EINVAL; + + if (input == 1) + enable = true; + else if (input == 0) + enable = false; + else + return -EINVAL; + + retval = udg_engine_enable(enable); + if (retval < 0) + return retval; + + return count; +} + +static ssize_t udg_sysfs_detection_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + bool enable; + unsigned int input; + + if (kstrtouint(buf, 10, &input) != 1) + return -EINVAL; + + if (input == 1) + enable = true; + else if (input == 0) + enable = false; + else + return -EINVAL; + + udg->detection_status = 0; + + retval = udg_detection_enable(enable); + if (retval < 0) + return retval; + + return count; +} + +static ssize_t udg_sysfs_detection_score_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", udg->detection_score); +} + +static ssize_t udg_sysfs_detection_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", udg->detection_index); +} + +static ssize_t udg_sysfs_registration_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + bool enable; + unsigned int input; + struct synaptics_rmi4_f12_control_41 control_41; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + if (kstrtouint(buf, 10, &input) != 1) + return -EINVAL; + + if (input == 1) + enable = true; + else if (input == 0) + enable = false; + else + return -EINVAL; + + if (enable) { + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.ctrl_23, + udg->ctrl_buf, + udg->ctrl_23_sub3_off + 1); + if (retval < 0) + return retval; + + udg->ctrl_buf[0] = 0; + udg->ctrl_buf[0] |= (1 << CTRL23_FINGER_REPORT_ENABLE_BIT); + if (udg->ctrl_23_sub3_off) + udg->ctrl_buf[udg->ctrl_23_sub3_off] = 0; + + retval = synaptics_rmi4_reg_write(rmi4_data, + udg->addr.ctrl_23, + udg->ctrl_buf, + udg->ctrl_23_sub3_off + 1); + if (retval < 0) + return retval; + } else { + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.ctrl_23, + udg->ctrl_buf, + udg->ctrl_23_sub3_off + 1); + if (retval < 0) + return retval; + + udg->ctrl_buf[0] = udg->object_type_enable1; + if (udg->ctrl_23_sub3_off) { + udg->ctrl_buf[udg->ctrl_23_sub3_off] = + udg->object_type_enable2; + } + + retval = synaptics_rmi4_reg_write(rmi4_data, + udg->addr.ctrl_23, + udg->ctrl_buf, + udg->ctrl_23_sub3_off + 1); + if (retval < 0) + return retval; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.ctrl_41, + control_41.data, + sizeof(control_41.data)); + if (retval < 0) + return retval; + + control_41.enable_registration = enable ? 1 : 0; + + retval = synaptics_rmi4_reg_write(rmi4_data, + udg->addr.ctrl_41, + control_41.data, + sizeof(control_41.data)); + if (retval < 0) + return retval; + + return count; +} + +static ssize_t udg_sysfs_registration_begin_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + bool begin; + unsigned int input; + struct synaptics_rmi4_f12_control_41 control_41; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + if (kstrtouint(buf, 10, &input) != 1) + return -EINVAL; + + if (input == 1) + begin = true; + else if (input == 0) + begin = false; + else + return -EINVAL; + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.ctrl_41, + control_41.data, + sizeof(control_41.data)); + if (retval < 0) + return retval; + + control_41.begin = begin ? 1 : 0; + + retval = synaptics_rmi4_reg_write(rmi4_data, + udg->addr.ctrl_41, + control_41.data, + sizeof(control_41.data)); + if (retval < 0) + return retval; + + return count; +} + +static ssize_t udg_sysfs_registration_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "0x%02x\n", udg->registration_status); +} + +static ssize_t udg_sysfs_template_size_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", udg->template_size); +} + +static ssize_t udg_sysfs_template_max_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", udg->max_num_templates - 1); +} + +static ssize_t udg_sysfs_template_detection_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + int attn_event; + unsigned char detection_status; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + attn_event = atomic_read(&udg->attn_event); + atomic_set(&udg->attn_event, 0); + + if (attn_event == 0) + return snprintf(buf, PAGE_SIZE, "0\n"); + + if (udg->detection_status == 0) { + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.data_4, + rmi4_data->gesture_detection, + sizeof(rmi4_data->gesture_detection)); + if (retval < 0) + return retval; + + udg->detection_status = rmi4_data->gesture_detection[0]; + } + + detection_status = udg->detection_status; + udg->detection_status = 0; + + switch (detection_status) { + case DETECTION: + udg->detection_score = rmi4_data->gesture_detection[1]; + udg->detection_index = rmi4_data->gesture_detection[4]; + udg->trace_size = rmi4_data->gesture_detection[3]; + break; + case REGISTRATION: + udg->registration_status = rmi4_data->gesture_detection[1]; + udg->trace_size = rmi4_data->gesture_detection[3]; + break; + default: + return snprintf(buf, PAGE_SIZE, "0\n"); + } + + return snprintf(buf, PAGE_SIZE, "0x%02x\n", detection_status); +} + +static ssize_t udg_sysfs_template_index_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned long index; + + retval = sstrtoul(buf, 10, &index); + if (retval) + return retval; + + retval = udg_set_index((unsigned char)index); + if (retval < 0) + return retval; + + return count; +} + +static ssize_t udg_sysfs_template_valid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + unsigned char valid; + unsigned char offset; + unsigned char byte_num; + unsigned char template_flags[2]; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + byte_num = udg->template_index / 8; + offset = udg->template_index % 8; + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.template_flags, + template_flags, + sizeof(template_flags)); + if (retval < 0) + return retval; + + valid = (template_flags[byte_num] & (1 << offset)) >> offset; + + return snprintf(buf, PAGE_SIZE, "%u\n", valid); +} + +static ssize_t udg_sysfs_template_valid_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned long valid; + unsigned char offset; + unsigned char byte_num; + unsigned char template_flags[2]; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + retval = sstrtoul(buf, 10, &valid); + if (retval) + return retval; + + if (valid > 0) + valid = 1; + + byte_num = udg->template_index / 8; + offset = udg->template_index % 8; + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.template_flags, + template_flags, + sizeof(template_flags)); + if (retval < 0) + return retval; + + if (valid) + template_flags[byte_num] |= (1 << offset); + else + template_flags[byte_num] &= ~(1 << offset); + + retval = synaptics_rmi4_reg_write(rmi4_data, + udg->addr.template_flags, + template_flags, + sizeof(template_flags)); + if (retval < 0) + return retval; + +#ifdef STORE_GESTURES + udg_read_valid_data(); +#endif + + return count; +} + +static ssize_t udg_sysfs_template_clear_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned int input; + const char cmd[] = {'0', 0}; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + if (kstrtouint(buf, 10, &input) != 1) + return -EINVAL; + + if (input != 1) + return -EINVAL; + + memset(udg->template_data_buf, 0x00, udg->template_data_size); + + retval = synaptics_rmi4_reg_write(rmi4_data, + udg->addr.template_data, + udg->template_data_buf, + udg->template_data_size); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to clear template data\n", + __func__); + return retval; + } + + retval = udg_sysfs_template_valid_store(dev, attr, cmd, 1); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to clear valid bit\n", + __func__); + return retval; + } + +#ifdef STORE_GESTURES + udg_read_template_data(udg->template_index); + udg_read_valid_data(); +#endif + + return count; +} + +static ssize_t udg_sysfs_trace_size_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", udg->trace_size); +} + +static ssize_t udg_sysfs_trace_data_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + unsigned short index = 0; + unsigned short trace_data_size; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + trace_data_size = udg->trace_size * 5; + + if (trace_data_size == 0) + return -EINVAL; + + if (count < trace_data_size) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Not enough space (%d bytes) in buffer\n", + __func__, (unsigned int)count); + return -EINVAL; + } + + if (udg->trace_data_buf_size < trace_data_size) { + if (udg->trace_data_buf_size) + kfree(udg->trace_data_buf); + udg->trace_data_buf = kzalloc(trace_data_size, GFP_KERNEL); + if (!udg->trace_data_buf) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for trace data buffer\n", + __func__); + udg->trace_data_buf_size = 0; + return -ENOMEM; + } + udg->trace_data_buf_size = trace_data_size; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.trace_x, + &udg->trace_data_buf[index], + udg->trace_size * 2); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read trace X data\n", + __func__); + return retval; + } + index += udg->trace_size * 2; + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.trace_y, + &udg->trace_data_buf[index], + udg->trace_size * 2); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read trace Y data\n", + __func__); + return retval; + } + index += udg->trace_size * 2; + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.trace_segment, + &udg->trace_data_buf[index], + udg->trace_size); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read trace segment data\n", + __func__); + return retval; + } + + retval = secure_memcpy(buf, count, udg->trace_data_buf, + udg->trace_data_buf_size, trace_data_size); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy trace data\n", + __func__); + return retval; + } + + return trace_data_size; +} + +static ssize_t udg_sysfs_template_data_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + if (count < udg->template_data_size) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Not enough space (%d bytes) in buffer\n", + __func__, (unsigned int)count); + return -EINVAL; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.template_data, + udg->template_data_buf, + udg->template_data_size); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read template data\n", + __func__); + return retval; + } + + retval = secure_memcpy(buf, count, udg->template_data_buf, + udg->template_data_size, udg->template_data_size); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy template data\n", + __func__); + return retval; + } + +#ifdef STORE_GESTURES + udg_read_template_data(udg->template_index); + udg_read_valid_data(); +#endif + + return udg->template_data_size; +} + +static ssize_t udg_sysfs_template_data_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + retval = secure_memcpy(udg->template_data_buf, udg->template_data_size, + buf, count, count); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy template data\n", + __func__); + return retval; + } + + retval = synaptics_rmi4_reg_write(rmi4_data, + udg->addr.template_data, + udg->template_data_buf, + count); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write template data\n", + __func__); + return retval; + } + +#ifdef STORE_GESTURES + udg_read_template_data(udg->template_index); + udg_read_valid_data(); +#endif + + return count; +} + +static ssize_t udg_sysfs_template_displacement_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + unsigned short template_displacement; + + retval = udg_read_tuning_params(); + if (retval < 0) + return retval; + + template_displacement = + ((unsigned short)udg->tuning.template_disp_lsb << 0) | + ((unsigned short)udg->tuning.template_disp_msb << 8); + + return snprintf(buf, PAGE_SIZE, "%u\n", template_displacement); +} + +static ssize_t udg_sysfs_template_displacement_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned long input; + + retval = sstrtoul(buf, 10, &input); + if (retval) + return retval; + + retval = udg_read_tuning_params(); + if (retval < 0) + return retval; + + udg->tuning.template_disp_lsb = (unsigned char)(input >> 0); + udg->tuning.template_disp_msb = (unsigned char)(input >> 8); + + retval = udg_write_tuning_params(); + if (retval < 0) + return retval; + + return count; +} + +static ssize_t udg_sysfs_rotation_invariance_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + unsigned short rotation_invariance; + + retval = udg_read_tuning_params(); + if (retval < 0) + return retval; + + rotation_invariance = + ((unsigned short)udg->tuning.rotation_inv_lsb << 0) | + ((unsigned short)udg->tuning.rotation_inv_msb << 8); + + return snprintf(buf, PAGE_SIZE, "%u\n", rotation_invariance); +} + +static ssize_t udg_sysfs_rotation_invariance_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned long input; + + retval = sstrtoul(buf, 10, &input); + if (retval) + return retval; + + retval = udg_read_tuning_params(); + if (retval < 0) + return retval; + + udg->tuning.rotation_inv_lsb = (unsigned char)(input >> 0); + udg->tuning.rotation_inv_msb = (unsigned char)(input >> 8); + + retval = udg_write_tuning_params(); + if (retval < 0) + return retval; + + return count; +} + +static ssize_t udg_sysfs_scale_invariance_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + unsigned short scale_invariance; + + retval = udg_read_tuning_params(); + if (retval < 0) + return retval; + + scale_invariance = + ((unsigned short)udg->tuning.scale_inv_lsb << 0) | + ((unsigned short)udg->tuning.scale_inv_msb << 8); + + return snprintf(buf, PAGE_SIZE, "%u\n", scale_invariance); +} + +static ssize_t udg_sysfs_scale_invariance_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned long input; + + retval = sstrtoul(buf, 10, &input); + if (retval) + return retval; + + retval = udg_read_tuning_params(); + if (retval < 0) + return retval; + + udg->tuning.scale_inv_lsb = (unsigned char)(input >> 0); + udg->tuning.scale_inv_msb = (unsigned char)(input >> 8); + + retval = udg_write_tuning_params(); + if (retval < 0) + return retval; + + return count; +} + +static ssize_t udg_sysfs_threshold_factor_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + unsigned short threshold_factor; + + retval = udg_read_tuning_params(); + if (retval < 0) + return retval; + + threshold_factor = + ((unsigned short)udg->tuning.thres_factor_lsb << 0) | + ((unsigned short)udg->tuning.thres_factor_msb << 8); + + return snprintf(buf, PAGE_SIZE, "%u\n", threshold_factor); +} + +static ssize_t udg_sysfs_threshold_factor_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned long input; + + retval = sstrtoul(buf, 10, &input); + if (retval) + return retval; + + retval = udg_read_tuning_params(); + if (retval < 0) + return retval; + + udg->tuning.thres_factor_lsb = (unsigned char)(input >> 0); + udg->tuning.thres_factor_msb = (unsigned char)(input >> 8); + + retval = udg_write_tuning_params(); + if (retval < 0) + return retval; + + return count; +} + +static ssize_t udg_sysfs_match_metric_threshold_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + unsigned short match_metric_threshold; + + retval = udg_read_tuning_params(); + if (retval < 0) + return retval; + + match_metric_threshold = + ((unsigned short)udg->tuning.metric_thres_lsb << 0) | + ((unsigned short)udg->tuning.metric_thres_msb << 8); + + return snprintf(buf, PAGE_SIZE, "%u\n", match_metric_threshold); +} + +static ssize_t udg_sysfs_match_metric_threshold_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned long input; + + retval = sstrtoul(buf, 10, &input); + if (retval) + return retval; + + retval = udg_read_tuning_params(); + if (retval < 0) + return retval; + + udg->tuning.metric_thres_lsb = (unsigned char)(input >> 0); + udg->tuning.metric_thres_msb = (unsigned char)(input >> 8); + + retval = udg_write_tuning_params(); + if (retval < 0) + return retval; + + return count; +} + +static ssize_t udg_sysfs_max_inter_stroke_time_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + unsigned short max_inter_stroke_time; + + retval = udg_read_tuning_params(); + if (retval < 0) + return retval; + + max_inter_stroke_time = + ((unsigned short)udg->tuning.inter_stroke_lsb << 0) | + ((unsigned short)udg->tuning.inter_stroke_msb << 8); + + return snprintf(buf, PAGE_SIZE, "%u\n", max_inter_stroke_time); +} + +static ssize_t udg_sysfs_max_inter_stroke_time_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned long input; + + retval = sstrtoul(buf, 10, &input); + if (retval) + return retval; + + retval = udg_read_tuning_params(); + if (retval < 0) + return retval; + + udg->tuning.inter_stroke_lsb = (unsigned char)(input >> 0); + udg->tuning.inter_stroke_msb = (unsigned char)(input >> 8); + + retval = udg_write_tuning_params(); + if (retval < 0) + return retval; + + return count; +} + +static int udg_ctrl_subpacket(unsigned char ctrlreg, + unsigned char subpacket, + struct synaptics_rmi4_f12_query_5 *query_5) +{ + int retval; + unsigned char cnt; + unsigned char regnum; + unsigned char bitnum; + unsigned char q5_index; + unsigned char q6_index; + unsigned char offset; + unsigned char max_ctrlreg; + unsigned char *query_6; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + max_ctrlreg = (sizeof(query_5->data) - 1) * 8 - 1; + + if (ctrlreg > max_ctrlreg) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Control register number (%d) over limit\n", + __func__, ctrlreg); + return -EINVAL; + } + + q5_index = ctrlreg / 8 + 1; + bitnum = ctrlreg % 8; + if ((query_5->data[q5_index] & (1 << bitnum)) == 0x00) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Control %d is not present\n", + __func__, ctrlreg); + return -EINVAL; + } + + query_6 = kmalloc(query_5->size_of_query6, GFP_KERNEL); + if (!query_6) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for query 6\n", + __func__); + return -ENOMEM; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->query_base_addr + 6, + query_6, + query_5->size_of_query6); + if (retval < 0) + goto exit; + + q6_index = 0; + + for (regnum = 0; regnum < ctrlreg; regnum++) { + q5_index = regnum / 8 + 1; + bitnum = regnum % 8; + if ((query_5->data[q5_index] & (1 << bitnum)) == 0x00) + continue; + + if (query_6[q6_index] == 0x00) + q6_index += 3; + else + q6_index++; + + while (query_6[q6_index] & ~MASK_7BIT) + q6_index++; + + q6_index++; + } + + cnt = 0; + q6_index++; + offset = subpacket / 7; + bitnum = subpacket % 7; + + do { + if (cnt == offset) { + if (query_6[q6_index + cnt] & (1 << bitnum)) + retval = 1; + else + retval = 0; + goto exit; + } + cnt++; + } while (query_6[q6_index + cnt - 1] & ~MASK_7BIT); + + retval = 0; + +exit: + kfree(query_6); + + return retval; +} + +static int udg_read_tuning_params(void) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.ctrl_18, + udg->ctrl_buf, + udg->ctrl_18_sub10_off + sizeof(struct udg_tuning)); + if (retval < 0) + return retval; + + secure_memcpy(udg->tuning.data, + sizeof(udg->tuning.data), + (unsigned char *)&udg->ctrl_buf[udg->ctrl_18_sub10_off], + sizeof(struct udg_tuning), + sizeof(struct udg_tuning)); + + return 0; +} + +static int udg_write_tuning_params(void) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + secure_memcpy((unsigned char *)&udg->ctrl_buf[udg->ctrl_18_sub10_off], + sizeof(struct udg_tuning), + udg->tuning.data, + sizeof(udg->tuning.data), + sizeof(struct udg_tuning)); + + retval = synaptics_rmi4_reg_write(rmi4_data, + udg->addr.ctrl_18, + udg->ctrl_buf, + udg->ctrl_18_sub10_off + sizeof(struct udg_tuning)); + if (retval < 0) + return retval; + + return 0; +} + +static int udg_detection_enable(bool enable) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.ctrl_20, + udg->ctrl_buf, + udg->ctrl_20_sub1_off + 1); + if (retval < 0) + return retval; + + if (enable) + udg->ctrl_buf[udg->ctrl_20_sub1_off] = WAKEUP_GESTURE_MODE; + else + udg->ctrl_buf[udg->ctrl_20_sub1_off] = udg->report_flags; + + retval = synaptics_rmi4_reg_write(rmi4_data, + udg->addr.ctrl_20, + udg->ctrl_buf, + udg->ctrl_20_sub1_off + 1); + if (retval < 0) + return retval; + + return 0; +} + +static int udg_engine_enable(bool enable) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + if (enable) { + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.ctrl_27, + udg->ctrl_buf, + udg->ctrl_27_sub5_off + 1); + if (retval < 0) + return retval; + + udg->ctrl_buf[udg->ctrl_27_sub5_off] |= + (1 << CTRL27_UDG_ENABLE_BIT); + + retval = synaptics_rmi4_reg_write(rmi4_data, + udg->addr.ctrl_27, + udg->ctrl_buf, + udg->ctrl_27_sub5_off + 1); + if (retval < 0) + return retval; + } else { + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.ctrl_27, + udg->ctrl_buf, + udg->ctrl_27_sub5_off + 1); + if (retval < 0) + return retval; + + udg->ctrl_buf[udg->ctrl_27_sub5_off] &= + ~(1 << CTRL27_UDG_ENABLE_BIT); + + retval = synaptics_rmi4_reg_write(rmi4_data, + udg->addr.ctrl_27, + udg->ctrl_buf, + udg->ctrl_27_sub5_off + 1); + if (retval < 0) + return retval; + } + + return 0; +} + +static void udg_report(void) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + atomic_set(&udg->attn_event, 1); + + if (rmi4_data->suspend) { + if (rmi4_data->gesture_detection[0] == 0) { + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.data_4, + rmi4_data->gesture_detection, + sizeof(rmi4_data->gesture_detection)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read gesture detection\n", + __func__); + return; + } + } + + udg->detection_status = rmi4_data->gesture_detection[0]; + rmi4_data->gesture_detection[0] = 0; + + if (udg->detection_status == DETECTION) { + input_report_key(udg->udg_dev, KEY_WAKEUP, 1); + input_sync(udg->udg_dev); + input_report_key(udg->udg_dev, KEY_WAKEUP, 0); + input_sync(udg->udg_dev); + rmi4_data->suspend = false; + } + } + + return; +} + +static int udg_set_index(unsigned char index) +{ + int retval; + struct synaptics_rmi4_f12_control_41 control_41; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + if (index >= udg->max_num_templates) + return -EINVAL; + + udg->template_index = index; + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.ctrl_41, + control_41.data, + sizeof(control_41.data)); + if (retval < 0) + return retval; + + control_41.template_index = udg->template_index; + + retval = synaptics_rmi4_reg_write(rmi4_data, + udg->addr.ctrl_41, + control_41.data, + sizeof(control_41.data)); + if (retval < 0) + return retval; + + return 0; +} + +#ifdef STORE_GESTURES +static int udg_read_valid_data(void) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.template_flags, + udg->valid_buf, + sizeof(udg->valid_buf)); + if (retval < 0) + return retval; + + return 0; +} + +static int udg_write_valid_data(void) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + retval = synaptics_rmi4_reg_write(rmi4_data, + udg->addr.template_flags, + udg->valid_buf, + sizeof(udg->valid_buf)); + if (retval < 0) + return retval; + + return 0; +} + +static int udg_read_template_data(unsigned char index) +{ + int retval; + unsigned char *storage; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + udg_set_index(index); + storage = &(udg->storage_buf[index * udg->template_data_size]); + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.template_data, + storage, + udg->template_data_size); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read template data\n", + __func__); + return retval; + } + + return 0; +} + +static int udg_write_template_data(void) +{ + int retval; + unsigned char ii; + unsigned char *storage; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + for (ii = 0; ii < udg->gestures_to_store; ii++) { + udg_set_index(ii); + storage = &(udg->storage_buf[ii * udg->template_data_size]); + + retval = synaptics_rmi4_reg_write(rmi4_data, + udg->addr.template_data, + storage, + udg->template_data_size); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write template data\n", + __func__); + return retval; + } + } + + return 0; +} +#endif + +static int udg_reg_init(void) +{ + int retval; + unsigned char ii; + unsigned char data_offset; + unsigned char size_of_query; + unsigned char ctrl_18_offset; + unsigned char ctrl_20_offset; + unsigned char ctrl_23_offset; + unsigned char ctrl_27_offset; + unsigned char ctrl_41_offset; + struct synaptics_rmi4_f12_query_0 query_0; + struct synaptics_rmi4_f12_query_5 query_5; + struct synaptics_rmi4_f12_query_8 query_8; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->query_base_addr + 7, + &size_of_query, + sizeof(size_of_query)); + if (retval < 0) + return retval; + + if (size_of_query < 4) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: User defined gesture support unavailable (missing data registers)\n", + __func__); + retval = -ENODEV; + return retval; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->query_base_addr + 8, + query_8.data, + sizeof(query_8.data)); + if (retval < 0) + return retval; + + if ((query_8.data16_is_present) && + (query_8.data17_is_present) && + (query_8.data18_is_present) && + (query_8.data19_is_present) && + (query_8.data20_is_present) && + (query_8.data21_is_present)) { + data_offset = query_8.data0_is_present + + query_8.data1_is_present + + query_8.data2_is_present + + query_8.data3_is_present; + udg->addr.data_4 = udg->data_base_addr + data_offset; + data_offset = data_offset + + query_8.data4_is_present + + query_8.data5_is_present + + query_8.data6_is_present + + query_8.data7_is_present + + query_8.data8_is_present + + query_8.data9_is_present + + query_8.data10_is_present + + query_8.data11_is_present + + query_8.data12_is_present + + query_8.data13_is_present + + query_8.data14_is_present + + query_8.data15_is_present; + udg->addr.trace_x = udg->data_base_addr + data_offset; + udg->addr.trace_y = udg->addr.trace_x + 1; + udg->addr.trace_segment = udg->addr.trace_y + 1; + udg->addr.template_helper = udg->addr.trace_segment + 1; + udg->addr.template_data = udg->addr.template_helper + 1; + udg->addr.template_flags = udg->addr.template_data + 1; + } else { + dev_err(rmi4_data->pdev->dev.parent, + "%s: User defined gesture support unavailable (missing data registers)\n", + __func__); + retval = -ENODEV; + return retval; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->query_base_addr + 4, + &size_of_query, + sizeof(size_of_query)); + if (retval < 0) + return retval; + + if (size_of_query < 7) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: User defined gesture support unavailable (missing control registers)\n", + __func__); + retval = -ENODEV; + return retval; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->query_base_addr + 5, + query_5.data, + sizeof(query_5.data)); + if (retval < 0) + return retval; + + ctrl_18_offset = query_5.ctrl0_is_present + + query_5.ctrl1_is_present + + query_5.ctrl2_is_present + + query_5.ctrl3_is_present + + query_5.ctrl4_is_present + + query_5.ctrl5_is_present + + query_5.ctrl6_is_present + + query_5.ctrl7_is_present + + query_5.ctrl8_is_present + + query_5.ctrl9_is_present + + query_5.ctrl10_is_present + + query_5.ctrl11_is_present + + query_5.ctrl12_is_present + + query_5.ctrl13_is_present + + query_5.ctrl14_is_present + + query_5.ctrl15_is_present + + query_5.ctrl16_is_present + + query_5.ctrl17_is_present; + + ctrl_20_offset = ctrl_18_offset + + query_5.ctrl18_is_present + + query_5.ctrl19_is_present; + + ctrl_23_offset = ctrl_20_offset + + query_5.ctrl20_is_present + + query_5.ctrl21_is_present + + query_5.ctrl22_is_present; + + ctrl_27_offset = ctrl_23_offset+ + query_5.ctrl23_is_present + + query_5.ctrl24_is_present + + query_5.ctrl25_is_present + + query_5.ctrl26_is_present; + + ctrl_41_offset = ctrl_27_offset+ + query_5.ctrl27_is_present + + query_5.ctrl28_is_present + + query_5.ctrl29_is_present + + query_5.ctrl30_is_present + + query_5.ctrl31_is_present + + query_5.ctrl32_is_present + + query_5.ctrl33_is_present + + query_5.ctrl34_is_present + + query_5.ctrl35_is_present + + query_5.ctrl36_is_present + + query_5.ctrl37_is_present + + query_5.ctrl38_is_present + + query_5.ctrl39_is_present + + query_5.ctrl40_is_present; + + udg->addr.ctrl_18 = udg->control_base_addr + ctrl_18_offset; + udg->addr.ctrl_20 = udg->control_base_addr + ctrl_20_offset; + udg->addr.ctrl_23 = udg->control_base_addr + ctrl_23_offset; + udg->addr.ctrl_27 = udg->control_base_addr + ctrl_27_offset; + udg->addr.ctrl_41 = udg->control_base_addr + ctrl_41_offset; + + udg->ctrl_18_sub10_off = 0; + for (ii = 0; ii < 10; ii++) { + retval = udg_ctrl_subpacket(18, ii, &query_5); + if (retval == 1) + udg->ctrl_18_sub10_off += ctrl_18_sub_size[ii]; + else if (retval < 0) + return retval; + } + + udg->ctrl_20_sub1_off = 0; + for (ii = 0; ii < 1; ii++) { + retval = udg_ctrl_subpacket(20, ii, &query_5); + if (retval == 1) + udg->ctrl_20_sub1_off += ctrl_20_sub_size[ii]; + else if (retval < 0) + return retval; + } + + udg->ctrl_23_sub3_off = 0; + for (ii = 0; ii < 3; ii++) { + retval = udg_ctrl_subpacket(23, ii, &query_5); + if (retval == 1) + udg->ctrl_23_sub3_off += ctrl_23_sub_size[ii]; + else if (retval < 0) + return retval; + } + + retval = udg_ctrl_subpacket(23, 3, &query_5); + if (retval == 0) + udg->ctrl_23_sub3_off = 0; + else if (retval < 0) + return retval; + + udg->ctrl_27_sub5_off = 0; + for (ii = 0; ii < 5; ii++) { + retval = udg_ctrl_subpacket(27, ii, &query_5); + if (retval == 1) + udg->ctrl_27_sub5_off += ctrl_27_sub_size[ii]; + else if (retval < 0) + return retval; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->query_base_addr + 0, + query_0.data, + sizeof(query_0.data)); + if (retval < 0) + return retval; + + udg->max_num_templates = query_0.max_num_templates; + udg->template_size = + ((unsigned short)query_0.template_size_lsb << 0) | + ((unsigned short)query_0.template_size_msb << 8); + udg->template_data_size = udg->template_size * 4 * 2 + 4 + 1; + +#ifdef STORE_GESTURES + udg->gestures_to_store = udg->max_num_templates; + if (GESTURES_TO_STORE < udg->gestures_to_store) + udg->gestures_to_store = GESTURES_TO_STORE; +#endif + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.ctrl_20, + udg->ctrl_buf, + udg->ctrl_20_sub1_off + 1); + if (retval < 0) + return retval; + + udg->report_flags = udg->ctrl_buf[udg->ctrl_20_sub1_off]; + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.ctrl_23, + udg->ctrl_buf, + udg->ctrl_23_sub3_off + 1); + if (retval < 0) + return retval; + + udg->object_type_enable1 = udg->ctrl_buf[0]; + if (udg->ctrl_23_sub3_off) + udg->object_type_enable2 = udg->ctrl_buf[udg->ctrl_23_sub3_off]; + + return retval; +} + +static int udg_scan_pdt(void) +{ + int retval; + unsigned char ii; + unsigned char page; + unsigned char intr_count = 0; + unsigned char intr_off; + unsigned char intr_src; + unsigned short addr; + struct synaptics_rmi4_fn_desc fd; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + for (page = 0; page < PAGES_TO_SERVICE; page++) { + for (addr = PDT_START; addr > PDT_END; addr -= PDT_ENTRY_SIZE) { + addr |= (page << 8); + + retval = synaptics_rmi4_reg_read(rmi4_data, + addr, + (unsigned char *)&fd, + sizeof(fd)); + if (retval < 0) + return retval; + + addr &= ~(MASK_8BIT << 8); + + if (fd.fn_number) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Found F%02x\n", + __func__, fd.fn_number); + switch (fd.fn_number) { + case SYNAPTICS_RMI4_F12: + goto f12_found; + break; + } + } else { + break; + } + + intr_count += fd.intr_src_count; + } + } + + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to find F12\n", + __func__); + return -EINVAL; + +f12_found: + udg->query_base_addr = fd.query_base_addr | (page << 8); + udg->control_base_addr = fd.ctrl_base_addr | (page << 8); + udg->data_base_addr = fd.data_base_addr | (page << 8); + udg->command_base_addr = fd.cmd_base_addr | (page << 8); + + retval = udg_reg_init(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to initialize user defined gesture registers\n", + __func__); + return retval; + } + + udg->intr_mask = 0; + intr_src = fd.intr_src_count; + intr_off = intr_count % 8; + for (ii = intr_off; + ii < (intr_src + intr_off); + ii++) { + udg->intr_mask |= 1 << ii; + } + + rmi4_data->intr_mask[0] |= udg->intr_mask; + + addr = rmi4_data->f01_ctrl_base_addr + 1; + + retval = synaptics_rmi4_reg_write(rmi4_data, + addr, + &rmi4_data->intr_mask[0], + sizeof(rmi4_data->intr_mask[0])); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set interrupt enable bit\n", + __func__); + return retval; + } + + return 0; +} + +static void synaptics_rmi4_udg_attn(struct synaptics_rmi4_data *rmi4_data, + unsigned char intr_mask) +{ + if (!udg) + return; + + if (udg->intr_mask & intr_mask) + udg_report(); + + return; +} + +static int synaptics_rmi4_udg_init(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + unsigned char ii; + unsigned char size; + unsigned char attr_count; + unsigned char param_count; + + if (udg) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Handle already exists\n", + __func__); + return 0; + } + + udg = kzalloc(sizeof(*udg), GFP_KERNEL); + if (!udg) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for udg\n", + __func__); + retval = -ENOMEM; + goto exit; + } + + size = 0; + for (ii = 0; ii < sizeof(ctrl_18_sub_size); ii++) + size += ctrl_18_sub_size[ii]; + size += sizeof(struct udg_tuning); + udg->ctrl_buf = kzalloc(size, GFP_KERNEL); + if (!udg->ctrl_buf) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for ctrl_buf\n", + __func__); + retval = -ENOMEM; + goto exit_free_udg; + } + + udg->rmi4_data = rmi4_data; + + retval = udg_scan_pdt(); + if (retval < 0) + goto exit_free_ctrl_buf; + + udg->template_data_buf = kzalloc(udg->template_data_size, GFP_KERNEL); + if (!udg->template_data_buf) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for template_data_buf\n", + __func__); + retval = -ENOMEM; + goto exit_free_ctrl_buf; + } + +#ifdef STORE_GESTURES + udg->storage_buf = kzalloc( + udg->template_data_size * udg->gestures_to_store, + GFP_KERNEL); + if (!udg->storage_buf) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for storage_buf\n", + __func__); + kfree(udg->template_data_buf); + retval = -ENOMEM; + goto exit_free_ctrl_buf; + } +#endif + + udg->udg_dev = input_allocate_device(); + if (udg->udg_dev == NULL) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to allocate gesture device\n", + __func__); + retval = -ENOMEM; + goto exit_free_template_data_buf; + } + + udg->udg_dev->name = GESTURE_DRIVER_NAME; + udg->udg_dev->phys = GESTURE_PHYS_NAME; + udg->udg_dev->id.product = SYNAPTICS_DSX_DRIVER_PRODUCT; + udg->udg_dev->id.version = SYNAPTICS_DSX_DRIVER_VERSION; + udg->udg_dev->dev.parent = rmi4_data->pdev->dev.parent; + input_set_drvdata(udg->udg_dev, rmi4_data); + + set_bit(EV_KEY, udg->udg_dev->evbit); + set_bit(KEY_WAKEUP, udg->udg_dev->keybit); + input_set_capability(udg->udg_dev, EV_KEY, KEY_WAKEUP); + + retval = input_register_device(udg->udg_dev); + if (retval) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to register gesture device\n", + __func__); + input_free_device(udg->udg_dev); + goto exit_free_template_data_buf; + } + + udg->tuning_dir = kobject_create_and_add(TUNING_SYSFS_DIR_NAME, + &udg->udg_dev->dev.kobj); + if (!udg->tuning_dir) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create tuning sysfs directory\n", + __func__); + goto exit_unregister_input_device; + } + + retval = sysfs_create_bin_file(&udg->udg_dev->dev.kobj, &template_data); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create template data bin file\n", + __func__); + goto exit_remove_sysfs_directory; + } + + retval = sysfs_create_bin_file(&udg->udg_dev->dev.kobj, &trace_data); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create trace data bin file\n", + __func__); + goto exit_remove_bin_file; + } + + for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) { + retval = sysfs_create_file(&udg->udg_dev->dev.kobj, + &attrs[attr_count].attr); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create sysfs attributes\n", + __func__); + retval = -ENODEV; + goto exit_remove_attrs; + } + } + + for (param_count = 0; param_count < ARRAY_SIZE(params); param_count++) { + retval = sysfs_create_file(udg->tuning_dir, + ¶ms[param_count].attr); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create tuning parameters\n", + __func__); + retval = -ENODEV; + goto exit_remove_params; + } + } + + retval = udg_engine_enable(true); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to enable gesture engine\n", + __func__); + goto exit_remove_params; + } + + return 0; + +exit_remove_params: + for (param_count--; param_count >= 0; param_count--) { + sysfs_remove_file(udg->tuning_dir, + ¶ms[param_count].attr); + } + +exit_remove_attrs: + for (attr_count--; attr_count >= 0; attr_count--) { + sysfs_remove_file(&udg->udg_dev->dev.kobj, + &attrs[attr_count].attr); + } + + sysfs_remove_bin_file(&udg->udg_dev->dev.kobj, &trace_data); + +exit_remove_bin_file: + sysfs_remove_bin_file(&udg->udg_dev->dev.kobj, &template_data); + +exit_remove_sysfs_directory: + kobject_put(udg->tuning_dir); + +exit_unregister_input_device: + input_unregister_device(udg->udg_dev); + +exit_free_template_data_buf: +#ifdef STORE_GESTURES + kfree(udg->storage_buf); +#endif + kfree(udg->template_data_buf); + +exit_free_ctrl_buf: + kfree(udg->ctrl_buf); + +exit_free_udg: + kfree(udg); + udg = NULL; + +exit: + return retval; +} + +static void synaptics_rmi4_udg_remove(struct synaptics_rmi4_data *rmi4_data) +{ + unsigned char count; + + if (!udg) + goto exit; + + for (count = 0; count < ARRAY_SIZE(params); count++) { + sysfs_remove_file(udg->tuning_dir, + ¶ms[count].attr); + } + + for (count = 0; count < ARRAY_SIZE(attrs); count++) { + sysfs_remove_file(&udg->udg_dev->dev.kobj, + &attrs[count].attr); + } + + sysfs_remove_bin_file(&udg->udg_dev->dev.kobj, &trace_data); + sysfs_remove_bin_file(&udg->udg_dev->dev.kobj, &template_data); + kobject_put(udg->tuning_dir); + + input_unregister_device(udg->udg_dev); +#ifdef STORE_GESTURES + kfree(udg->storage_buf); +#endif + kfree(udg->template_data_buf); + kfree(udg->trace_data_buf); + kfree(udg->ctrl_buf); + kfree(udg); + udg = NULL; + +exit: + complete(&udg_remove_complete); +} + +static void synaptics_rmi4_udg_reset(struct synaptics_rmi4_data *rmi4_data) +{ + if (!udg) { + synaptics_rmi4_udg_init(rmi4_data); + return; + } + + udg_scan_pdt(); + udg_engine_enable(true); +#ifdef STORE_GESTURES + udg_write_template_data(); + udg_write_valid_data(); +#endif +} + +static void synaptics_rmi4_udg_reinit(struct synaptics_rmi4_data *rmi4_data) +{ + if (!udg) + return; + + udg_engine_enable(true); +#ifdef STORE_GESTURES + udg_write_template_data(); + udg_write_valid_data(); +#endif +} + +static void synaptics_rmi4_udg_e_suspend(struct synaptics_rmi4_data *rmi4_data) +{ + if (!udg) + return; + + rmi4_data->sleep_enable(rmi4_data, false); + rmi4_data->irq_enable(rmi4_data, true, false); + enable_irq_wake(rmi4_data->irq); + + udg_engine_enable(true); + udg_detection_enable(true); +} + +static void synaptics_rmi4_udg_suspend(struct synaptics_rmi4_data *rmi4_data) +{ + if (!udg) + return; + + rmi4_data->sleep_enable(rmi4_data, false); + rmi4_data->irq_enable(rmi4_data, true, false); + enable_irq_wake(rmi4_data->irq); + + udg_engine_enable(true); + udg_detection_enable(true); +} + +static void synaptics_rmi4_udg_resume(struct synaptics_rmi4_data *rmi4_data) +{ + if (!udg) + return; + + disable_irq_wake(rmi4_data->irq); + udg_detection_enable(false); +} + +static void synaptics_rmi4_udg_l_resume(struct synaptics_rmi4_data *rmi4_data) +{ + if (!udg) + return; + + disable_irq_wake(rmi4_data->irq); + udg_detection_enable(false); +} + +static struct synaptics_rmi4_exp_fn gesture_module = { + .fn_type = RMI_GESTURE, + .init = synaptics_rmi4_udg_init, + .remove = synaptics_rmi4_udg_remove, + .reset = synaptics_rmi4_udg_reset, + .reinit = synaptics_rmi4_udg_reinit, + .early_suspend = synaptics_rmi4_udg_e_suspend, + .suspend = synaptics_rmi4_udg_suspend, + .resume = synaptics_rmi4_udg_resume, + .late_resume = synaptics_rmi4_udg_l_resume, + .attn = synaptics_rmi4_udg_attn, +}; + +static int __init rmi4_gesture_module_init(void) +{ + synaptics_rmi4_new_function(&gesture_module, true); + + return 0; +} + +static void __exit rmi4_gesture_module_exit(void) +{ + synaptics_rmi4_new_function(&gesture_module, false); + + wait_for_completion(&udg_remove_complete); +} + +module_init(rmi4_gesture_module_init); +module_exit(rmi4_gesture_module_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics DSX User Defined Gesture Module"); +MODULE_LICENSE("GPL v2"); diff --git a/qcom/opensource/touch-drivers/synaptics_dsx/synaptics_dsx_i2c.c b/qcom/opensource/touch-drivers/synaptics_dsx/synaptics_dsx_i2c.c new file mode 100644 index 0000000000..bc37323c1c --- /dev/null +++ b/qcom/opensource/touch-drivers/synaptics_dsx/synaptics_dsx_i2c.c @@ -0,0 +1,672 @@ +/* + * Synaptics DSX touchscreen driver + * + * Copyright (C) 2012-2016 Synaptics Incorporated. All rights reserved. + * + * Copyright (c) 2018-2021 The Linux Foundation. All rights reserved. + * Copyright (C) 2012 Alexandra Chin + * Copyright (C) 2012 Scott Lin + * + * 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 +#include +#include +#include +#include +#include "synaptics_dsx_core.h" +#include "linux/moduleparam.h" + +#define SYN_I2C_RETRY_TIMES 10 +#define rd_msgs 1 + +#ifdef CONFIG_DRM +#include +struct drm_panel *active_panel; +#endif + +static unsigned char *wr_buf; + +static struct synaptics_dsx_hw_interface hw_if; + +static struct platform_device *synaptics_dsx_i2c_device; + + +#ifdef CONFIG_OF +static int parse_dt(struct device *dev, struct synaptics_dsx_board_data *bdata) +{ + int retval; + u32 value; + const char *name; + struct property *prop; + struct device_node *np = dev->of_node; + + bdata->irq_gpio = of_get_named_gpio_flags(np, + "synaptics,irq-gpio", 0, + (enum of_gpio_flags *)&bdata->irq_flags); + + 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); + retval = of_property_read_u32(np, "synaptics,power-on-state", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,power-on-state property\n", + __func__); + return retval; + } + bdata->power_on_state = value; + } else { + bdata->power_gpio = -1; + } + + 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) { + dev_err(dev, "%s: Unable to read synaptics,power-delay-ms property\n", + __func__); + return retval; + } + bdata->power_delay_ms = value; + } else { + bdata->power_delay_ms = 0; + } + + prop = of_find_property(np, "synaptics,reset-gpio", NULL); + if (prop && prop->length) { + bdata->reset_gpio = of_get_named_gpio_flags(np, + "synaptics,reset-gpio", 0, NULL); + retval = of_property_read_u32(np, "synaptics,reset-on-state", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,reset-on-state property\n", + __func__); + return retval; + } + bdata->reset_on_state = value; + retval = of_property_read_u32(np, "synaptics,reset-active-ms", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,reset-active-ms property\n", + __func__); + return retval; + } + bdata->reset_active_ms = value; + } else { + bdata->reset_gpio = -1; + } + + 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) { + dev_err(dev, "%s: Unable to read synaptics,reset-delay-ms property\n", + __func__); + return retval; + } + bdata->reset_delay_ms = value; + } else { + bdata->reset_delay_ms = 0; + } + + prop = of_find_property(np, "synaptics,max-y-for-2d", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,max-y-for-2d", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,max-y-for-2d property\n", + __func__); + return retval; + } + bdata->max_y_for_2d = value; + } else { + bdata->max_y_for_2d = -1; + } + + bdata->swap_axes = of_property_read_bool(np, "synaptics,swap-axes"); + bdata->x_flip = of_property_read_bool(np, "synaptics,x-flip"); + bdata->y_flip = of_property_read_bool(np, "synaptics,y-flip"); + + prop = of_find_property(np, "synaptics,ub-i2c-addr", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,ub-i2c-addr", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,ub-i2c-addr property\n", + __func__); + return retval; + } + bdata->ub_i2c_addr = (unsigned short)value; + } else { + bdata->ub_i2c_addr = -1; + } + + prop = of_find_property(np, "synaptics,cap-button-codes", NULL); + if (prop && prop->length) { + bdata->cap_button_map->map = devm_kzalloc(dev, + prop->length, + GFP_KERNEL); + if (!bdata->cap_button_map->map) + return -ENOMEM; + bdata->cap_button_map->nbuttons = prop->length / sizeof(u32); + retval = of_property_read_u32_array(np, + "synaptics,cap-button-codes", + bdata->cap_button_map->map, + bdata->cap_button_map->nbuttons); + if (retval < 0) { + bdata->cap_button_map->nbuttons = 0; + bdata->cap_button_map->map = NULL; + } + } else { + bdata->cap_button_map->nbuttons = 0; + bdata->cap_button_map->map = NULL; + } + + prop = of_find_property(np, "synaptics,vir-button-codes", NULL); + if (prop && prop->length) { + bdata->vir_button_map->map = devm_kzalloc(dev, + prop->length, + GFP_KERNEL); + if (!bdata->vir_button_map->map) + return -ENOMEM; + bdata->vir_button_map->nbuttons = prop->length / sizeof(u32); + bdata->vir_button_map->nbuttons /= 5; + retval = of_property_read_u32_array(np, + "synaptics,vir-button-codes", + bdata->vir_button_map->map, + bdata->vir_button_map->nbuttons * 5); + if (retval < 0) { + bdata->vir_button_map->nbuttons = 0; + bdata->vir_button_map->map = NULL; + } + } else { + bdata->vir_button_map->nbuttons = 0; + bdata->vir_button_map->map = NULL; + } + + return 0; +} +#endif + +static int synaptics_rmi4_i2c_alloc_buf(struct synaptics_rmi4_data *rmi4_data, + unsigned int count) +{ + static unsigned int buf_size; + + if (count > buf_size) { + if (buf_size) + kfree(wr_buf); + wr_buf = kzalloc(count, GFP_KERNEL); + if (!wr_buf) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for buffer\n", + __func__); + buf_size = 0; + return -ENOMEM; + } + buf_size = count; + } + + return 0; +} + +static void synaptics_rmi4_i2c_check_addr(struct synaptics_rmi4_data *rmi4_data, + struct i2c_client *i2c) +{ + if (hw_if.board_data->ub_i2c_addr == -1) + return; + + if (hw_if.board_data->i2c_addr == i2c->addr) + hw_if.board_data->i2c_addr = hw_if.board_data->ub_i2c_addr; + else + hw_if.board_data->i2c_addr = i2c->addr; +} + +static int synaptics_rmi4_i2c_set_page(struct synaptics_rmi4_data *rmi4_data, + unsigned short addr) +{ + int retval = 0; + unsigned char retry; + unsigned char buf[PAGE_SELECT_LEN]; + unsigned char page; + struct i2c_client *i2c = to_i2c_client(rmi4_data->pdev->dev.parent); + struct i2c_msg msg[2]; + + msg[0].addr = hw_if.board_data->i2c_addr; + msg[0].flags = 0; + msg[0].len = PAGE_SELECT_LEN; + msg[0].buf = buf; + + page = ((addr >> 8) & MASK_8BIT); + buf[0] = MASK_8BIT; + buf[1] = page; + + if (page != rmi4_data->current_page) { + for (retry = 0; retry < SYN_I2C_RETRY_TIMES; retry++) { + if (i2c_transfer(i2c->adapter, &msg[0], 1) == 1) { + rmi4_data->current_page = page; + retval = PAGE_SELECT_LEN; + break; + } + dev_err(rmi4_data->pdev->dev.parent, + "%s: I2C retry %d\n", + __func__, retry + 1); + msleep(20); + + if (retry == SYN_I2C_RETRY_TIMES / 2) { + synaptics_rmi4_i2c_check_addr(rmi4_data, i2c); + msg[0].addr = hw_if.board_data->i2c_addr; + } + } + } else { + retval = PAGE_SELECT_LEN; + } + + return retval; +} + +static int synaptics_rmi4_i2c_read(struct synaptics_rmi4_data *rmi4_data, + unsigned short addr, unsigned char *data, unsigned int length) +{ + int retval = 0; + unsigned char retry; + unsigned char buf; + unsigned char index = 0; + unsigned char xfer_msgs; + unsigned char remaining_msgs; + unsigned short i2c_addr; + unsigned short data_offset = 0; + unsigned int remaining_length = length; + struct i2c_client *i2c = to_i2c_client(rmi4_data->pdev->dev.parent); + struct i2c_adapter *adap = i2c->adapter; + struct i2c_msg msg[rd_msgs + 1]; + + mutex_lock(&rmi4_data->rmi4_io_ctrl_mutex); + + retval = synaptics_rmi4_i2c_set_page(rmi4_data, addr); + if (retval != PAGE_SELECT_LEN) { + retval = -EIO; + goto exit; + } + + msg[0].addr = hw_if.board_data->i2c_addr; + msg[0].flags = 0; + msg[0].len = 1; + msg[0].buf = &buf; + msg[rd_msgs].addr = hw_if.board_data->i2c_addr; + msg[rd_msgs].flags = I2C_M_RD; + msg[rd_msgs].len = (unsigned short)remaining_length; + msg[rd_msgs].buf = &data[data_offset]; + + buf = addr & MASK_8BIT; + + remaining_msgs = rd_msgs + 1; + + while (remaining_msgs) { + xfer_msgs = remaining_msgs; + for (retry = 0; retry < SYN_I2C_RETRY_TIMES; retry++) { + retval = i2c_transfer(adap, &msg[index], xfer_msgs); + if (retval == xfer_msgs) + break; + + dev_err(rmi4_data->pdev->dev.parent, + "%s: I2C retry %d\n", + __func__, retry + 1); + msleep(20); + + if (retry == SYN_I2C_RETRY_TIMES / 2) { + synaptics_rmi4_i2c_check_addr(rmi4_data, i2c); + i2c_addr = hw_if.board_data->i2c_addr; + msg[0].addr = i2c_addr; + msg[rd_msgs].addr = i2c_addr; + } + } + + if (retry == SYN_I2C_RETRY_TIMES) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: I2C read over retry limit\n", + __func__); + retval = -EIO; + goto exit; + } + + remaining_msgs -= xfer_msgs; + index += xfer_msgs; + } + + retval = length; + +exit: + mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex); + + return retval; +} + +static int synaptics_rmi4_i2c_write(struct synaptics_rmi4_data *rmi4_data, + unsigned short addr, unsigned char *data, unsigned int length) +{ + int retval; + unsigned char retry; + struct i2c_client *i2c = to_i2c_client(rmi4_data->pdev->dev.parent); + struct i2c_msg msg[2]; + + mutex_lock(&rmi4_data->rmi4_io_ctrl_mutex); + + retval = synaptics_rmi4_i2c_alloc_buf(rmi4_data, length + 1); + if (retval < 0) + goto exit; + + retval = synaptics_rmi4_i2c_set_page(rmi4_data, addr); + if (retval != PAGE_SELECT_LEN) { + retval = -EIO; + goto exit; + } + + msg[0].addr = hw_if.board_data->i2c_addr; + msg[0].flags = 0; + msg[0].len = (unsigned short)(length + 1); + msg[0].buf = wr_buf; + + wr_buf[0] = addr & MASK_8BIT; + retval = secure_memcpy(&wr_buf[1], length, &data[0], length, length); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy data\n", + __func__); + goto exit; + } + + for (retry = 0; retry < SYN_I2C_RETRY_TIMES; retry++) { + if (i2c_transfer(i2c->adapter, &msg[0], 1) == 1) { + retval = length; + break; + } + dev_err(rmi4_data->pdev->dev.parent, + "%s: I2C retry %d\n", + __func__, retry + 1); + msleep(20); + + if (retry == SYN_I2C_RETRY_TIMES / 2) { + synaptics_rmi4_i2c_check_addr(rmi4_data, i2c); + msg[0].addr = hw_if.board_data->i2c_addr; + } + } + + if (retry == SYN_I2C_RETRY_TIMES) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: I2C write over retry limit\n", + __func__); + retval = -EIO; + } + +exit: + mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex); + + return retval; +} + +#ifdef CONFIG_DRM +static int check_dt(struct device_node *np) +{ + int i; + int count; + struct device_node *node; + struct drm_panel *panel; + + count = of_count_phandle_with_args(np, "panel", NULL); + if (count <= 0) + return 0; + + for (i = 0; i < count; i++) { + node = of_parse_phandle(np, "panel", i); + panel = of_drm_find_panel(node); + of_node_put(node); + if (!IS_ERR(panel)) { + active_panel = panel; + return 0; + } + } + + return PTR_ERR(panel); +} +#endif + +static int check_default_tp(struct device_node *dt, const char *prop) +{ + const char *active_tp; + const char *compatible; + char *start; + int ret; + + ret = of_property_read_string(dt->parent, prop, &active_tp); + if (ret) { + pr_err(" %s:fail to read %s %d\n", __func__, prop, ret); + return -ENODEV; + } + + ret = of_property_read_string(dt, "compatible", &compatible); + if (ret < 0) { + pr_err(" %s:fail to read %s %d\n", __func__, "compatible", ret); + return -ENODEV; + } + + start = strnstr(active_tp, compatible, strlen(active_tp)); + if (start == NULL) { + pr_err(" %s:no match compatible, %s, %s\n", + __func__, compatible, active_tp); + ret = -ENODEV; + } + + return ret; +} + +static struct synaptics_dsx_bus_access bus_access = { + .type = BUS_I2C, + .read = synaptics_rmi4_i2c_read, + .write = synaptics_rmi4_i2c_write, +}; + +static void synaptics_rmi4_i2c_dev_release(struct device *dev) +{ + kfree(synaptics_dsx_i2c_device); +} + +static int synaptics_rmi4_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *dev_id) +{ + int retval; + struct device_node *dp = client->dev.of_node; + +#ifdef CONFIG_DRM + retval = check_dt(dp); + if (retval == -EPROBE_DEFER) + return retval; + + if (retval) { + if (!check_default_tp(dp, "qcom,i2c-touch-active")) + retval = -EPROBE_DEFER; + else + retval = -ENODEV; + + return retval; + } +#endif + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(&client->dev, + "%s: SMBus byte data commands not supported by host\n", + __func__); + return -EIO; + } + + synaptics_dsx_i2c_device = kzalloc( + sizeof(struct platform_device), + GFP_KERNEL); + if (!synaptics_dsx_i2c_device) { + dev_err(&client->dev, + "%s: Failed to allocate memory for synaptics_dsx_i2c_device\n", + __func__); + return -ENOMEM; + } + +#ifdef CONFIG_OF + if (client->dev.of_node) { + hw_if.board_data = devm_kzalloc(&client->dev, + sizeof(struct synaptics_dsx_board_data), + GFP_KERNEL); + if (!hw_if.board_data) { + dev_err(&client->dev, + "%s: Failed to allocate memory for board data\n", + __func__); + return -ENOMEM; + } + hw_if.board_data->cap_button_map = devm_kzalloc(&client->dev, + sizeof(struct synaptics_dsx_button_map), + GFP_KERNEL); + if (!hw_if.board_data->cap_button_map) { + dev_err(&client->dev, + "%s: Failed to allocate memory for 0D button map\n", + __func__); + return -ENOMEM; + } + hw_if.board_data->vir_button_map = devm_kzalloc(&client->dev, + sizeof(struct synaptics_dsx_button_map), + GFP_KERNEL); + if (!hw_if.board_data->vir_button_map) { + dev_err(&client->dev, + "%s: Failed to allocate memory for virtual button map\n", + __func__); + return -ENOMEM; + } + parse_dt(&client->dev, hw_if.board_data); + } +#else + hw_if.board_data = client->dev.platform_data; +#endif + + hw_if.bus_access = &bus_access; + hw_if.board_data->i2c_addr = client->addr; + + synaptics_dsx_i2c_device->name = PLATFORM_DRIVER_NAME; + synaptics_dsx_i2c_device->id = 0; + synaptics_dsx_i2c_device->num_resources = 0; + synaptics_dsx_i2c_device->dev.parent = &client->dev; + synaptics_dsx_i2c_device->dev.platform_data = &hw_if; + synaptics_dsx_i2c_device->dev.release = synaptics_rmi4_i2c_dev_release; + + retval = platform_device_register(synaptics_dsx_i2c_device); + if (retval) { + dev_err(&client->dev, + "%s: Failed to register platform device\n", + __func__); + return -ENODEV; + } + + return 0; +} + +static int synaptics_rmi4_i2c_remove(struct i2c_client *client) +{ + platform_device_unregister(synaptics_dsx_i2c_device); + + return 0; +} + +static const struct i2c_device_id synaptics_rmi4_id_table[] = { + {I2C_DRIVER_NAME, 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, synaptics_rmi4_id_table); + +#ifdef CONFIG_OF +static const struct of_device_id synaptics_rmi4_of_match_table[] = { + { + .compatible = "synaptics,dsx-i2c", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, synaptics_rmi4_of_match_table); +#else +#define synaptics_rmi4_of_match_table NULL +#endif + +static struct i2c_driver synaptics_rmi4_i2c_driver = { + .driver = { + .name = I2C_DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = synaptics_rmi4_of_match_table, + }, + .probe = synaptics_rmi4_i2c_probe, + .remove = synaptics_rmi4_i2c_remove, + .id_table = synaptics_rmi4_id_table, +}; + +int synaptics_rmi4_bus_init(void) +{ + return i2c_add_driver(&synaptics_rmi4_i2c_driver); +} +EXPORT_SYMBOL(synaptics_rmi4_bus_init); + +void synaptics_rmi4_bus_exit(void) +{ + kfree(wr_buf); + + i2c_del_driver(&synaptics_rmi4_i2c_driver); +} +EXPORT_SYMBOL(synaptics_rmi4_bus_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics DSX I2C Bus Support Module"); +MODULE_LICENSE("GPL v2"); diff --git a/qcom/opensource/touch-drivers/synaptics_dsx/synaptics_dsx_proximity.c b/qcom/opensource/touch-drivers/synaptics_dsx/synaptics_dsx_proximity.c new file mode 100644 index 0000000000..9bc18fa8c2 --- /dev/null +++ b/qcom/opensource/touch-drivers/synaptics_dsx/synaptics_dsx_proximity.c @@ -0,0 +1,673 @@ +/* + * Synaptics DSX touchscreen driver + * + * Copyright (C) 2012-2016 Synaptics Incorporated. All rights reserved. + * + * Copyright (c) 2018-2021 The Linux Foundation. All rights reserved. + * Copyright (C) 2012 Alexandra Chin + * Copyright (C) 2012 Scott Lin + * + * 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 +#include +#include +#include "synaptics_dsx_core.h" + +#define PROX_PHYS_NAME "synaptics_dsx/proximity" + +#define HOVER_Z_MAX (255) + +#define HOVERING_FINGER_EN (1 << 4) + +static ssize_t synaptics_rmi4_hover_finger_en_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t synaptics_rmi4_hover_finger_en_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static struct device_attribute attrs[] = { + __ATTR(hover_finger_en, 0664, + synaptics_rmi4_hover_finger_en_show, + synaptics_rmi4_hover_finger_en_store), +}; + +struct synaptics_rmi4_f12_query_5 { + union { + struct { + unsigned char size_of_query6; + struct { + unsigned char ctrl0_is_present:1; + unsigned char ctrl1_is_present:1; + unsigned char ctrl2_is_present:1; + unsigned char ctrl3_is_present:1; + unsigned char ctrl4_is_present:1; + unsigned char ctrl5_is_present:1; + unsigned char ctrl6_is_present:1; + unsigned char ctrl7_is_present:1; + } __packed; + struct { + unsigned char ctrl8_is_present:1; + unsigned char ctrl9_is_present:1; + unsigned char ctrl10_is_present:1; + unsigned char ctrl11_is_present:1; + unsigned char ctrl12_is_present:1; + unsigned char ctrl13_is_present:1; + unsigned char ctrl14_is_present:1; + unsigned char ctrl15_is_present:1; + } __packed; + struct { + unsigned char ctrl16_is_present:1; + unsigned char ctrl17_is_present:1; + unsigned char ctrl18_is_present:1; + unsigned char ctrl19_is_present:1; + unsigned char ctrl20_is_present:1; + unsigned char ctrl21_is_present:1; + unsigned char ctrl22_is_present:1; + unsigned char ctrl23_is_present:1; + } __packed; + }; + unsigned char data[4]; + }; +}; + +struct synaptics_rmi4_f12_query_8 { + union { + struct { + unsigned char size_of_query9; + struct { + unsigned char data0_is_present:1; + unsigned char data1_is_present:1; + unsigned char data2_is_present:1; + unsigned char data3_is_present:1; + unsigned char data4_is_present:1; + unsigned char data5_is_present:1; + unsigned char data6_is_present:1; + unsigned char data7_is_present:1; + } __packed; + }; + unsigned char data[2]; + }; +}; + +struct prox_finger_data { + union { + struct { + unsigned char object_type_and_status; + unsigned char x_lsb; + unsigned char x_msb; + unsigned char y_lsb; + unsigned char y_msb; + unsigned char z; + } __packed; + unsigned char proximity_data[6]; + }; +}; + +struct synaptics_rmi4_prox_handle { + bool hover_finger_present; + bool hover_finger_en; + unsigned char intr_mask; + unsigned short query_base_addr; + unsigned short control_base_addr; + unsigned short data_base_addr; + unsigned short command_base_addr; + unsigned short hover_finger_en_addr; + unsigned short hover_finger_data_addr; + struct input_dev *prox_dev; + struct prox_finger_data *finger_data; + struct synaptics_rmi4_data *rmi4_data; +}; + +static struct synaptics_rmi4_prox_handle *prox; + +DECLARE_COMPLETION(prox_remove_complete); + +static void prox_hover_finger_lift(void) +{ + input_report_key(prox->prox_dev, BTN_TOUCH, 0); + input_report_key(prox->prox_dev, BTN_TOOL_FINGER, 0); + input_sync(prox->prox_dev); + prox->hover_finger_present = false; +} + +static void prox_hover_finger_report(void) +{ + int retval; + int x; + int y; + int z; + struct prox_finger_data *data; + struct synaptics_rmi4_data *rmi4_data = prox->rmi4_data; + + data = prox->finger_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + prox->hover_finger_data_addr, + data->proximity_data, + sizeof(data->proximity_data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read hovering finger data\n", + __func__); + return; + } + + if (data->object_type_and_status != F12_HOVERING_FINGER_STATUS) { + if (prox->hover_finger_present) + prox_hover_finger_lift(); + + return; + } + + x = (data->x_msb << 8) | (data->x_lsb); + y = (data->y_msb << 8) | (data->y_lsb); + z = HOVER_Z_MAX - data->z; + + input_report_key(prox->prox_dev, BTN_TOUCH, 0); + input_report_key(prox->prox_dev, BTN_TOOL_FINGER, 1); + input_report_abs(prox->prox_dev, ABS_X, x); + input_report_abs(prox->prox_dev, ABS_Y, y); + input_report_abs(prox->prox_dev, ABS_DISTANCE, z); + + input_sync(prox->prox_dev); + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: x = %d y = %d z = %d\n", + __func__, x, y, z); + + prox->hover_finger_present = true; +} + +static int prox_set_hover_finger_en(void) +{ + int retval; + unsigned char object_report_enable; + struct synaptics_rmi4_data *rmi4_data = prox->rmi4_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + prox->hover_finger_en_addr, + &object_report_enable, + sizeof(object_report_enable)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read from object report enable register\n", + __func__); + return retval; + } + + if (prox->hover_finger_en) + object_report_enable |= HOVERING_FINGER_EN; + else + object_report_enable &= ~HOVERING_FINGER_EN; + + retval = synaptics_rmi4_reg_write(rmi4_data, + prox->hover_finger_en_addr, + &object_report_enable, + sizeof(object_report_enable)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write to object report enable register\n", + __func__); + return retval; + } + + return 0; +} + +static void prox_set_params(void) +{ + input_set_abs_params(prox->prox_dev, ABS_X, 0, + prox->rmi4_data->sensor_max_x, 0, 0); + input_set_abs_params(prox->prox_dev, ABS_Y, 0, + prox->rmi4_data->sensor_max_y, 0, 0); + input_set_abs_params(prox->prox_dev, ABS_DISTANCE, 0, + HOVER_Z_MAX, 0, 0); +} + +static int prox_reg_init(void) +{ + int retval; + unsigned char ctrl_23_offset; + unsigned char data_1_offset; + struct synaptics_rmi4_f12_query_5 query_5; + struct synaptics_rmi4_f12_query_8 query_8; + struct synaptics_rmi4_data *rmi4_data = prox->rmi4_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + prox->query_base_addr + 5, + query_5.data, + sizeof(query_5.data)); + if (retval < 0) + return retval; + + ctrl_23_offset = query_5.ctrl0_is_present + + query_5.ctrl1_is_present + + query_5.ctrl2_is_present + + query_5.ctrl3_is_present + + query_5.ctrl4_is_present + + query_5.ctrl5_is_present + + query_5.ctrl6_is_present + + query_5.ctrl7_is_present + + query_5.ctrl8_is_present + + query_5.ctrl9_is_present + + query_5.ctrl10_is_present + + query_5.ctrl11_is_present + + query_5.ctrl12_is_present + + query_5.ctrl13_is_present + + query_5.ctrl14_is_present + + query_5.ctrl15_is_present + + query_5.ctrl16_is_present + + query_5.ctrl17_is_present + + query_5.ctrl18_is_present + + query_5.ctrl19_is_present + + query_5.ctrl20_is_present + + query_5.ctrl21_is_present + + query_5.ctrl22_is_present; + + prox->hover_finger_en_addr = prox->control_base_addr + ctrl_23_offset; + + retval = synaptics_rmi4_reg_read(rmi4_data, + prox->query_base_addr + 8, + query_8.data, + sizeof(query_8.data)); + if (retval < 0) + return retval; + + data_1_offset = query_8.data0_is_present; + prox->hover_finger_data_addr = prox->data_base_addr + data_1_offset; + + return retval; +} + +static int prox_scan_pdt(void) +{ + int retval; + unsigned char ii; + unsigned char page; + unsigned char intr_count = 0; + unsigned char intr_off; + unsigned char intr_src; + unsigned short addr; + struct synaptics_rmi4_fn_desc fd; + struct synaptics_rmi4_data *rmi4_data = prox->rmi4_data; + + for (page = 0; page < PAGES_TO_SERVICE; page++) { + for (addr = PDT_START; addr > PDT_END; addr -= PDT_ENTRY_SIZE) { + addr |= (page << 8); + + retval = synaptics_rmi4_reg_read(rmi4_data, + addr, + (unsigned char *)&fd, + sizeof(fd)); + if (retval < 0) + return retval; + + addr &= ~(MASK_8BIT << 8); + + if (fd.fn_number) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Found F%02x\n", + __func__, fd.fn_number); + switch (fd.fn_number) { + case SYNAPTICS_RMI4_F12: + goto f12_found; + break; + } + } else { + break; + } + + intr_count += fd.intr_src_count; + } + } + + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to find F12\n", + __func__); + return -EINVAL; + +f12_found: + prox->query_base_addr = fd.query_base_addr | (page << 8); + prox->control_base_addr = fd.ctrl_base_addr | (page << 8); + prox->data_base_addr = fd.data_base_addr | (page << 8); + prox->command_base_addr = fd.cmd_base_addr | (page << 8); + + retval = prox_reg_init(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to initialize proximity registers\n", + __func__); + return retval; + } + + prox->intr_mask = 0; + intr_src = fd.intr_src_count; + intr_off = intr_count % 8; + for (ii = intr_off; + ii < (intr_src + intr_off); + ii++) { + prox->intr_mask |= 1 << ii; + } + + rmi4_data->intr_mask[0] |= prox->intr_mask; + + addr = rmi4_data->f01_ctrl_base_addr + 1; + + retval = synaptics_rmi4_reg_write(rmi4_data, + addr, + &(rmi4_data->intr_mask[0]), + sizeof(rmi4_data->intr_mask[0])); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set interrupt enable bit\n", + __func__); + return retval; + } + + return 0; +} + +static ssize_t synaptics_rmi4_hover_finger_en_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (!prox) + return -ENODEV; + + return snprintf(buf, PAGE_SIZE, "%u\n", + prox->hover_finger_en); +} + +static ssize_t synaptics_rmi4_hover_finger_en_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned int input; + struct synaptics_rmi4_data *rmi4_data = prox->rmi4_data; + + if (!prox) + return -ENODEV; + + if (kstrtouint(buf, 16, &input) != 1) + return -EINVAL; + + if (input == 1) + prox->hover_finger_en = true; + else if (input == 0) + prox->hover_finger_en = false; + else + return -EINVAL; + + retval = prox_set_hover_finger_en(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to change hovering finger enable setting\n", + __func__); + return retval; + } + + return count; +} + +int synaptics_rmi4_prox_hover_finger_en(bool enable) +{ + int retval; + + if (!prox) + return -ENODEV; + + prox->hover_finger_en = enable; + + retval = prox_set_hover_finger_en(); + if (retval < 0) + return retval; + + return 0; +} +EXPORT_SYMBOL(synaptics_rmi4_prox_hover_finger_en); + +static void synaptics_rmi4_prox_attn(struct synaptics_rmi4_data *rmi4_data, + unsigned char intr_mask) +{ + if (!prox) + return; + + if (prox->intr_mask & intr_mask) + prox_hover_finger_report(); +} + +static int synaptics_rmi4_prox_init(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + unsigned char attr_count; + + if (prox) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Handle already exists\n", + __func__); + return 0; + } + + prox = kzalloc(sizeof(*prox), GFP_KERNEL); + if (!prox) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for prox\n", + __func__); + retval = -ENOMEM; + goto exit; + } + + prox->finger_data = kzalloc(sizeof(*(prox->finger_data)), GFP_KERNEL); + if (!prox->finger_data) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for finger_data\n", + __func__); + retval = -ENOMEM; + goto exit_free_prox; + } + + prox->rmi4_data = rmi4_data; + + retval = prox_scan_pdt(); + if (retval < 0) + goto exit_free_finger_data; + + prox->hover_finger_en = true; + + retval = prox_set_hover_finger_en(); + if (retval < 0) + return retval; + + prox->prox_dev = input_allocate_device(); + if (prox->prox_dev == NULL) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to allocate proximity device\n", + __func__); + retval = -ENOMEM; + goto exit_free_finger_data; + } + + prox->prox_dev->name = PROXIMITY_DRIVER_NAME; + prox->prox_dev->phys = PROX_PHYS_NAME; + prox->prox_dev->id.product = SYNAPTICS_DSX_DRIVER_PRODUCT; + prox->prox_dev->id.version = SYNAPTICS_DSX_DRIVER_VERSION; + prox->prox_dev->dev.parent = rmi4_data->pdev->dev.parent; + input_set_drvdata(prox->prox_dev, rmi4_data); + + set_bit(EV_KEY, prox->prox_dev->evbit); + set_bit(EV_ABS, prox->prox_dev->evbit); + set_bit(BTN_TOUCH, prox->prox_dev->keybit); + set_bit(BTN_TOOL_FINGER, prox->prox_dev->keybit); +#ifdef INPUT_PROP_DIRECT + set_bit(INPUT_PROP_DIRECT, prox->prox_dev->propbit); +#endif + + prox_set_params(); + + retval = input_register_device(prox->prox_dev); + if (retval) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to register proximity device\n", + __func__); + goto exit_free_input_device; + } + + for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) { + retval = sysfs_create_file(&rmi4_data->input_dev->dev.kobj, + &attrs[attr_count].attr); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create sysfs attributes\n", + __func__); + goto exit_free_sysfs; + } + } + + return 0; + +exit_free_sysfs: + for (attr_count--; attr_count >= 0; attr_count--) { + sysfs_remove_file(&rmi4_data->input_dev->dev.kobj, + &attrs[attr_count].attr); + } + + input_unregister_device(prox->prox_dev); + prox->prox_dev = NULL; + +exit_free_input_device: + if (prox->prox_dev) + input_free_device(prox->prox_dev); + +exit_free_finger_data: + kfree(prox->finger_data); + +exit_free_prox: + kfree(prox); + prox = NULL; + +exit: + return retval; +} + +static void synaptics_rmi4_prox_remove(struct synaptics_rmi4_data *rmi4_data) +{ + unsigned char attr_count; + + if (!prox) + goto exit; + + for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) { + sysfs_remove_file(&rmi4_data->input_dev->dev.kobj, + &attrs[attr_count].attr); + } + + input_unregister_device(prox->prox_dev); + kfree(prox->finger_data); + kfree(prox); + prox = NULL; + +exit: + complete(&prox_remove_complete); +} + +static void synaptics_rmi4_prox_reset(struct synaptics_rmi4_data *rmi4_data) +{ + if (!prox) { + synaptics_rmi4_prox_init(rmi4_data); + return; + } + + prox_hover_finger_lift(); + + prox_scan_pdt(); + + prox_set_hover_finger_en(); +} + +static void synaptics_rmi4_prox_reinit(struct synaptics_rmi4_data *rmi4_data) +{ + if (!prox) + return; + + prox_hover_finger_lift(); + + prox_set_hover_finger_en(); +} + +static void synaptics_rmi4_prox_e_suspend(struct synaptics_rmi4_data *rmi4_data) +{ + if (!prox) + return; + + prox_hover_finger_lift(); +} + +static void synaptics_rmi4_prox_suspend(struct synaptics_rmi4_data *rmi4_data) +{ + if (!prox) + return; + + prox_hover_finger_lift(); +} + +static struct synaptics_rmi4_exp_fn proximity_module = { + .fn_type = RMI_PROXIMITY, + .init = synaptics_rmi4_prox_init, + .remove = synaptics_rmi4_prox_remove, + .reset = synaptics_rmi4_prox_reset, + .reinit = synaptics_rmi4_prox_reinit, + .early_suspend = synaptics_rmi4_prox_e_suspend, + .suspend = synaptics_rmi4_prox_suspend, + .resume = NULL, + .late_resume = NULL, + .attn = synaptics_rmi4_prox_attn, +}; + +static int __init rmi4_proximity_module_init(void) +{ + synaptics_rmi4_new_function(&proximity_module, true); + + return 0; +} + +static void __exit rmi4_proximity_module_exit(void) +{ + synaptics_rmi4_new_function(&proximity_module, false); + + wait_for_completion(&prox_remove_complete); +} + +module_init(rmi4_proximity_module_init); +module_exit(rmi4_proximity_module_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics DSX Proximity Module"); +MODULE_LICENSE("GPL v2"); diff --git a/qcom/opensource/touch-drivers/synaptics_dsx/synaptics_dsx_rmi_dev.c b/qcom/opensource/touch-drivers/synaptics_dsx/synaptics_dsx_rmi_dev.c new file mode 100644 index 0000000000..ca366d0dbc --- /dev/null +++ b/qcom/opensource/touch-drivers/synaptics_dsx/synaptics_dsx_rmi_dev.c @@ -0,0 +1,1075 @@ +/* + * Synaptics DSX touchscreen driver + * + * Copyright (C) 2012-2016 Synaptics Incorporated. All rights reserved. + * + * Copyright (c) 2018-2021 The Linux Foundation. All rights reserved. + * Copyright (C) 2012 Alexandra Chin + * Copyright (C) 2012 Scott Lin + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include "synaptics_dsx_core.h" + +#define CHAR_DEVICE_NAME "rmi" +#define DEVICE_CLASS_NAME "rmidev" +#define SYSFS_FOLDER_NAME "rmidev" +#define DEV_NUMBER 1 +#define REG_ADDR_LIMIT 0xFFFF + +#define RMIDEV_MAJOR_NUM 0 + +static ssize_t rmidev_sysfs_data_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t rmidev_sysfs_data_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t rmidev_sysfs_open_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t rmidev_sysfs_release_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t rmidev_sysfs_attn_state_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t rmidev_sysfs_pid_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t rmidev_sysfs_pid_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t rmidev_sysfs_term_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t rmidev_sysfs_intr_mask_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t rmidev_sysfs_intr_mask_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t rmidev_sysfs_concurrent_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t rmidev_sysfs_concurrent_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +struct rmidev_handle { + dev_t dev_no; + pid_t pid; + unsigned char intr_mask; + unsigned char *tmpbuf; + unsigned int tmpbuf_size; + struct device dev; + struct synaptics_rmi4_data *rmi4_data; + struct kobject *sysfs_dir; + struct kernel_siginfo interrupt_signal; + struct kernel_siginfo terminate_signal; + struct task_struct *task; + void *data; + bool concurrent; +}; + +struct rmidev_data { + int ref_count; + struct cdev main_dev; + struct class *device_class; + struct mutex file_mutex; + struct rmidev_handle *rmi_dev; +}; + +static struct bin_attribute attr_data = { + .attr = { + .name = "data", + .mode = 0664, + }, + .size = 0, + .read = rmidev_sysfs_data_show, + .write = rmidev_sysfs_data_store, +}; + +static struct device_attribute attrs[] = { + __ATTR(open, 0220, + synaptics_rmi4_show_error, + rmidev_sysfs_open_store), + __ATTR(release, 0220, + synaptics_rmi4_show_error, + rmidev_sysfs_release_store), + __ATTR(attn_state, 0444, + rmidev_sysfs_attn_state_show, + synaptics_rmi4_store_error), + __ATTR(pid, 0664, + rmidev_sysfs_pid_show, + rmidev_sysfs_pid_store), + __ATTR(term, 0220, + synaptics_rmi4_show_error, + rmidev_sysfs_term_store), + __ATTR(intr_mask, 0664, + rmidev_sysfs_intr_mask_show, + rmidev_sysfs_intr_mask_store), + __ATTR(concurrent, 0664, + rmidev_sysfs_concurrent_show, + rmidev_sysfs_concurrent_store), +}; + +static int rmidev_major_num = RMIDEV_MAJOR_NUM; + +static struct class *rmidev_device_class; + +static struct rmidev_handle *rmidev; + +DECLARE_COMPLETION(rmidev_remove_complete); + +static irqreturn_t rmidev_sysfs_irq(int irq, void *data) +{ + struct synaptics_rmi4_data *rmi4_data = data; + + sysfs_notify(&rmi4_data->input_dev->dev.kobj, + SYSFS_FOLDER_NAME, "attn_state"); + + return IRQ_HANDLED; +} + +static int rmidev_sysfs_irq_enable(struct synaptics_rmi4_data *rmi4_data, + bool enable) +{ + int retval = 0; + unsigned char intr_status[MAX_INTR_REGISTERS]; + unsigned long irq_flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | + IRQF_ONESHOT; + + mutex_lock(&(rmi4_data->rmi4_irq_enable_mutex)); + + if (enable) { + if (rmi4_data->irq_enabled) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Interrupt already enabled\n", + __func__); + goto exit; + } + + /* Clear interrupts first */ + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_data_base_addr + 1, + intr_status, + rmi4_data->num_of_intr_regs); + if (retval < 0) + goto exit; + + retval = request_threaded_irq(rmi4_data->irq, NULL, + rmidev_sysfs_irq, irq_flags, + PLATFORM_DRIVER_NAME, rmi4_data); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create irq thread\n", + __func__); + goto exit; + } + + rmi4_data->irq_enabled = true; + } else { + if (rmi4_data->irq_enabled) { + disable_irq(rmi4_data->irq); + free_irq(rmi4_data->irq, rmi4_data); + rmi4_data->irq_enabled = false; + } + } + +exit: + mutex_unlock(&(rmi4_data->rmi4_irq_enable_mutex)); + + return retval; +} + +static ssize_t rmidev_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 char intr_status = 0; + unsigned int length = (unsigned int)count; + unsigned short address = (unsigned short)pos; + struct synaptics_rmi4_fn *fhandler; + struct synaptics_rmi4_device_info *rmi; + struct synaptics_rmi4_data *rmi4_data = rmidev->rmi4_data; + + rmi = &(rmi4_data->rmi4_mod_info); + + if (length > (REG_ADDR_LIMIT - address)) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Out of register map limit\n", + __func__); + return -EINVAL; + } + + if (length) { + retval = synaptics_rmi4_reg_read(rmi4_data, + address, + (unsigned char *)buf, + length); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read data\n", + __func__); + return retval; + } + } else { + return -EINVAL; + } + + if (!rmidev->concurrent) + goto exit; + + if (address != rmi4_data->f01_data_base_addr) + goto exit; + + if (length <= 1) + goto exit; + + intr_status = buf[1]; + + if (!list_empty(&rmi->support_fn_list)) { + list_for_each_entry(fhandler, &rmi->support_fn_list, link) { + if (fhandler->num_of_data_sources) { + if (fhandler->intr_mask & intr_status) { + rmi4_data->report_touch(rmi4_data, + fhandler); + } + } + } + } + +exit: + return length; +} + +static ssize_t rmidev_sysfs_data_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + unsigned int length = (unsigned int)count; + unsigned short address = (unsigned short)pos; + struct synaptics_rmi4_data *rmi4_data = rmidev->rmi4_data; + + if (length > (REG_ADDR_LIMIT - address)) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Out of register map limit\n", + __func__); + return -EINVAL; + } + + if (length) { + retval = synaptics_rmi4_reg_write(rmi4_data, + address, + (unsigned char *)buf, + length); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write data\n", + __func__); + return retval; + } + } else { + return -EINVAL; + } + + return length; +} + +static ssize_t rmidev_sysfs_open_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int input; + struct synaptics_rmi4_data *rmi4_data = rmidev->rmi4_data; + + if (kstrtouint(buf, 10, &input) != 1) + return -EINVAL; + + if (input != 1) + return -EINVAL; + + if (rmi4_data->sensor_sleep) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Sensor sleeping\n", + __func__); + return -ENODEV; + } + + rmi4_data->stay_awake = true; + + rmi4_data->irq_enable(rmi4_data, false, false); + rmidev_sysfs_irq_enable(rmi4_data, true); + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Attention interrupt disabled\n", + __func__); + + return count; +} + +static ssize_t rmidev_sysfs_release_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int input; + struct synaptics_rmi4_data *rmi4_data = rmidev->rmi4_data; + + if (kstrtouint(buf, 10, &input) != 1) + return -EINVAL; + + if (input != 1) + return -EINVAL; + + rmidev_sysfs_irq_enable(rmi4_data, false); + + rmi4_data->reset_device(rmi4_data, false); + + rmi4_data->stay_awake = false; + + return count; +} + +static ssize_t rmidev_sysfs_attn_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int attn_state; + struct synaptics_rmi4_data *rmi4_data = rmidev->rmi4_data; + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + attn_state = gpio_get_value(bdata->irq_gpio); + + return snprintf(buf, PAGE_SIZE, "%u\n", attn_state); +} + +static ssize_t rmidev_sysfs_pid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", rmidev->pid); +} + +static ssize_t rmidev_sysfs_pid_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int input; + struct synaptics_rmi4_data *rmi4_data = rmidev->rmi4_data; + + if (kstrtouint(buf, 10, &input) != 1) + return -EINVAL; + + rmidev->pid = input; + + if (rmidev->pid) { + rmidev->task = pid_task(find_vpid(rmidev->pid), PIDTYPE_PID); + if (!rmidev->task) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to locate PID of data logging tool\n", + __func__); + return -EINVAL; + } + } + + return count; +} + +static ssize_t rmidev_sysfs_term_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int input; + + if (kstrtouint(buf, 10, &input) != 1) + return -EINVAL; + + if (input != 1) + return -EINVAL; + + if (rmidev->pid) + send_sig_info(SIGTERM, &rmidev->terminate_signal, rmidev->task); + + return count; +} + +static ssize_t rmidev_sysfs_intr_mask_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "0x%02x\n", rmidev->intr_mask); +} + +static ssize_t rmidev_sysfs_intr_mask_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int input; + + if (kstrtouint(buf, 10, &input) != 1) + return -EINVAL; + + rmidev->intr_mask = (unsigned char)input; + + return count; +} + +static ssize_t rmidev_sysfs_concurrent_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", rmidev->concurrent); +} + +static ssize_t rmidev_sysfs_concurrent_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int input; + + if (kstrtouint(buf, 10, &input) != 1) + return -EINVAL; + + rmidev->concurrent = input > 0 ? true : false; + + return count; +} + +static int rmidev_allocate_buffer(int count) +{ + if (count + 1 > rmidev->tmpbuf_size) { + if (rmidev->tmpbuf_size) + kfree(rmidev->tmpbuf); + rmidev->tmpbuf = kzalloc(count + 1, GFP_KERNEL); + if (!rmidev->tmpbuf) { + dev_err(rmidev->rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for buffer\n", + __func__); + rmidev->tmpbuf_size = 0; + return -ENOMEM; + } + rmidev->tmpbuf_size = count + 1; + } + + return 0; +} + +/* + * rmidev_llseek - set register address to access for RMI device + * + * @filp: pointer to file structure + * @off: + * if whence == SEEK_SET, + * off: 16-bit RMI register address + * if whence == SEEK_CUR, + * off: offset from current position + * if whence == SEEK_END, + * off: offset from end position (0xFFFF) + * @whence: SEEK_SET, SEEK_CUR, or SEEK_END + */ +static loff_t rmidev_llseek(struct file *filp, loff_t off, int whence) +{ + loff_t newpos; + struct rmidev_data *dev_data = filp->private_data; + struct synaptics_rmi4_data *rmi4_data = rmidev->rmi4_data; + + if (IS_ERR(dev_data)) { + pr_err("%s: Pointer of char device data is invalid", __func__); + return -EBADF; + } + + mutex_lock(&(dev_data->file_mutex)); + + switch (whence) { + case SEEK_SET: + newpos = off; + break; + case SEEK_CUR: + newpos = filp->f_pos + off; + break; + case SEEK_END: + newpos = REG_ADDR_LIMIT + off; + break; + default: + newpos = -EINVAL; + goto clean_up; + } + + if (newpos < 0 || newpos > REG_ADDR_LIMIT) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: New position 0x%04x is invalid\n", + __func__, (unsigned int)newpos); + newpos = -EINVAL; + goto clean_up; + } + + filp->f_pos = newpos; + +clean_up: + mutex_unlock(&(dev_data->file_mutex)); + + return newpos; +} + +/* + * rmidev_read: read register data from RMI device + * + * @filp: pointer to file structure + * @buf: pointer to user space buffer + * @count: number of bytes to read + * @f_pos: starting RMI register address + */ +static ssize_t rmidev_read(struct file *filp, char __user *buf, + size_t count, loff_t *f_pos) +{ + ssize_t retval; + unsigned char intr_status = 0; + unsigned short address; + struct rmidev_data *dev_data = filp->private_data; + struct synaptics_rmi4_fn *fhandler; + struct synaptics_rmi4_device_info *rmi; + struct synaptics_rmi4_data *rmi4_data = rmidev->rmi4_data; + + rmi = &(rmi4_data->rmi4_mod_info); + + if (IS_ERR(dev_data)) { + pr_err("%s: Pointer of char device data is invalid", __func__); + return -EBADF; + } + + mutex_lock(&(dev_data->file_mutex)); + + if (*f_pos > REG_ADDR_LIMIT) { + retval = -EFAULT; + goto clean_up; + } + + if (count > (REG_ADDR_LIMIT - *f_pos)) + count = REG_ADDR_LIMIT - *f_pos; + + if (count == 0) { + retval = 0; + goto clean_up; + } + address = (unsigned short)(*f_pos); + + rmidev_allocate_buffer(count); + + retval = synaptics_rmi4_reg_read(rmidev->rmi4_data, + *f_pos, + rmidev->tmpbuf, + count); + if (retval < 0) + goto clean_up; + + if (copy_to_user(buf, rmidev->tmpbuf, count)) + retval = -EFAULT; + else + *f_pos += retval; + + if (!rmidev->concurrent) + goto clean_up; + + if (address != rmi4_data->f01_data_base_addr) + goto clean_up; + + if (count <= 1) + goto clean_up; + + intr_status = rmidev->tmpbuf[1]; + + if (!list_empty(&rmi->support_fn_list)) { + list_for_each_entry(fhandler, &rmi->support_fn_list, link) { + if (fhandler->num_of_data_sources) { + if (fhandler->intr_mask & intr_status) { + rmi4_data->report_touch(rmi4_data, + fhandler); + } + } + } + } + +clean_up: + mutex_unlock(&(dev_data->file_mutex)); + + return retval; +} + +/* + * rmidev_write: write register data to RMI device + * + * @filp: pointer to file structure + * @buf: pointer to user space buffer + * @count: number of bytes to write + * @f_pos: starting RMI register address + */ +static ssize_t rmidev_write(struct file *filp, const char __user *buf, + size_t count, loff_t *f_pos) +{ + ssize_t retval; + struct rmidev_data *dev_data = filp->private_data; + + if (IS_ERR(dev_data)) { + pr_err("%s: Pointer of char device data is invalid", __func__); + return -EBADF; + } + + mutex_lock(&(dev_data->file_mutex)); + + if (*f_pos > REG_ADDR_LIMIT) { + retval = -EFAULT; + goto unlock; + } + + if (count > (REG_ADDR_LIMIT - *f_pos)) + count = REG_ADDR_LIMIT - *f_pos; + + if (count == 0) { + retval = 0; + goto unlock; + } + rmidev_allocate_buffer(count); + + if (copy_from_user(rmidev->tmpbuf, buf, count)) { + retval = -EFAULT; + goto unlock; + } + + retval = synaptics_rmi4_reg_write(rmidev->rmi4_data, + *f_pos, + rmidev->tmpbuf, + count); + if (retval >= 0) + *f_pos += retval; + +unlock: + mutex_unlock(&(dev_data->file_mutex)); + + return retval; +} + +static int rmidev_open(struct inode *inp, struct file *filp) +{ + int retval = 0; + struct synaptics_rmi4_data *rmi4_data = rmidev->rmi4_data; + struct rmidev_data *dev_data = + container_of(inp->i_cdev, struct rmidev_data, main_dev); + + if (!dev_data) + return -EACCES; + + if (rmi4_data->sensor_sleep) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Sensor sleeping\n", + __func__); + return -ENODEV; + } + + rmi4_data->stay_awake = true; + + filp->private_data = dev_data; + + mutex_lock(&(dev_data->file_mutex)); + + rmi4_data->irq_enable(rmi4_data, false, false); + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Attention interrupt disabled\n", + __func__); + + if (dev_data->ref_count < 1) + dev_data->ref_count++; + else + retval = -EACCES; + + mutex_unlock(&(dev_data->file_mutex)); + + return retval; +} + +static int rmidev_release(struct inode *inp, struct file *filp) +{ + struct synaptics_rmi4_data *rmi4_data = rmidev->rmi4_data; + struct rmidev_data *dev_data = + container_of(inp->i_cdev, struct rmidev_data, main_dev); + + if (!dev_data) + return -EACCES; + + mutex_lock(&(dev_data->file_mutex)); + + dev_data->ref_count--; + if (dev_data->ref_count < 0) + dev_data->ref_count = 0; + + rmi4_data->reset_device(rmi4_data, false); + + rmi4_data->stay_awake = false; + + mutex_unlock(&(dev_data->file_mutex)); + + return 0; +} + +static const struct file_operations rmidev_fops = { + .owner = THIS_MODULE, + .llseek = rmidev_llseek, + .read = rmidev_read, + .write = rmidev_write, + .open = rmidev_open, + .release = rmidev_release, +}; + +static void rmidev_device_cleanup(struct rmidev_data *dev_data) +{ + dev_t devno; + struct synaptics_rmi4_data *rmi4_data = rmidev->rmi4_data; + + if (dev_data) { + devno = dev_data->main_dev.dev; + + if (dev_data->device_class) + device_destroy(dev_data->device_class, devno); + + cdev_del(&dev_data->main_dev); + + unregister_chrdev_region(devno, 1); + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: rmidev device removed\n", + __func__); + } +} + +static char *rmi_char_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, "rmi/%s", dev_name(dev)); +} + +static int rmidev_create_device_class(void) +{ + if (rmidev_device_class != NULL) + return 0; + + rmidev_device_class = class_create(THIS_MODULE, DEVICE_CLASS_NAME); + + if (IS_ERR(rmidev_device_class)) { + pr_err("%s: Failed to create /dev/%s\n", + __func__, CHAR_DEVICE_NAME); + return -ENODEV; + } + + rmidev_device_class->devnode = rmi_char_devnode; + + return 0; +} + +static void rmidev_attn(struct synaptics_rmi4_data *rmi4_data, + unsigned char intr_mask) +{ + if (!rmidev) + return; + + if (rmidev->pid && (rmidev->intr_mask & intr_mask)) + send_sig_info(SIGIO, &rmidev->interrupt_signal, rmidev->task); + + return; +} + +static int rmidev_init_device(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + dev_t dev_no; + unsigned char attr_count; + struct rmidev_data *dev_data; + struct device *device_ptr; + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + if (rmidev) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Handle already exists\n", + __func__); + return 0; + } + + rmidev = kzalloc(sizeof(*rmidev), GFP_KERNEL); + if (!rmidev) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for rmidev\n", + __func__); + retval = -ENOMEM; + goto err_rmidev; + } + + rmidev->rmi4_data = rmi4_data; + + memset(&rmidev->interrupt_signal, 0, sizeof(rmidev->interrupt_signal)); + rmidev->interrupt_signal.si_signo = SIGIO; + rmidev->interrupt_signal.si_code = SI_USER; + + memset(&rmidev->terminate_signal, 0, sizeof(rmidev->terminate_signal)); + rmidev->terminate_signal.si_signo = SIGTERM; + rmidev->terminate_signal.si_code = SI_USER; + + retval = rmidev_create_device_class(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create device class\n", + __func__); + goto err_device_class; + } + + if (rmidev_major_num) { + dev_no = MKDEV(rmidev_major_num, DEV_NUMBER); + retval = register_chrdev_region(dev_no, 1, CHAR_DEVICE_NAME); + } else { + retval = alloc_chrdev_region(&dev_no, 0, 1, CHAR_DEVICE_NAME); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to allocate char device region\n", + __func__); + goto err_device_region; + } + + rmidev_major_num = MAJOR(dev_no); + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Major number of rmidev = %d\n", + __func__, rmidev_major_num); + } + + dev_data = kzalloc(sizeof(*dev_data), GFP_KERNEL); + if (!dev_data) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for dev_data\n", + __func__); + retval = -ENOMEM; + goto err_dev_data; + } + + mutex_init(&dev_data->file_mutex); + dev_data->rmi_dev = rmidev; + rmidev->data = dev_data; + + cdev_init(&dev_data->main_dev, &rmidev_fops); + + retval = cdev_add(&dev_data->main_dev, dev_no, 1); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to add rmi char device\n", + __func__); + goto err_char_device; + } + + dev_set_name(&rmidev->dev, "rmidev%d", MINOR(dev_no)); + dev_data->device_class = rmidev_device_class; + + device_ptr = device_create(dev_data->device_class, NULL, dev_no, + NULL, CHAR_DEVICE_NAME"%d", MINOR(dev_no)); + if (IS_ERR(device_ptr)) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create rmi char device\n", + __func__); + retval = -ENODEV; + goto err_char_device; + } + + retval = gpio_export(bdata->irq_gpio, false); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to export attention gpio\n", + __func__); + } else { + retval = gpio_export_link(&(rmi4_data->input_dev->dev), + "attn", bdata->irq_gpio); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s Failed to create gpio symlink\n", + __func__); + } else { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Exported attention gpio %d\n", + __func__, bdata->irq_gpio); + } + } + + rmidev->sysfs_dir = kobject_create_and_add(SYSFS_FOLDER_NAME, + &rmi4_data->input_dev->dev.kobj); + if (!rmidev->sysfs_dir) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create sysfs directory\n", + __func__); + retval = -ENODEV; + goto err_sysfs_dir; + } + + retval = sysfs_create_bin_file(rmidev->sysfs_dir, + &attr_data); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create sysfs bin file\n", + __func__); + goto err_sysfs_bin; + } + + for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) { + retval = sysfs_create_file(rmidev->sysfs_dir, + &attrs[attr_count].attr); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create sysfs attributes\n", + __func__); + retval = -ENODEV; + goto err_sysfs_attrs; + } + } + + return 0; + +err_sysfs_attrs: + for (attr_count--; attr_count >= 0; attr_count--) + sysfs_remove_file(rmidev->sysfs_dir, &attrs[attr_count].attr); + + sysfs_remove_bin_file(rmidev->sysfs_dir, &attr_data); + +err_sysfs_bin: + kobject_put(rmidev->sysfs_dir); + +err_sysfs_dir: + sysfs_remove_link(&(rmi4_data->input_dev->dev.kobj), "attn"); + gpio_unexport(bdata->irq_gpio); + +err_char_device: + rmidev_device_cleanup(dev_data); + kfree(dev_data); + +err_dev_data: + unregister_chrdev_region(dev_no, 1); + +err_device_region: + if (rmidev_device_class != NULL) { + class_destroy(rmidev_device_class); + rmidev_device_class = NULL; + } + +err_device_class: + kfree(rmidev); + rmidev = NULL; + +err_rmidev: + return retval; +} + +static void rmidev_remove_device(struct synaptics_rmi4_data *rmi4_data) +{ + unsigned char attr_count; + struct rmidev_data *dev_data; + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + if (!rmidev) + goto exit; + + rmidev_major_num = RMIDEV_MAJOR_NUM; + + for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) + sysfs_remove_file(rmidev->sysfs_dir, &attrs[attr_count].attr); + + sysfs_remove_bin_file(rmidev->sysfs_dir, &attr_data); + + kobject_put(rmidev->sysfs_dir); + + sysfs_remove_link(&(rmi4_data->input_dev->dev.kobj), "attn"); + gpio_unexport(bdata->irq_gpio); + + dev_data = rmidev->data; + if (dev_data) { + rmidev_device_cleanup(dev_data); + kfree(dev_data); + } + + unregister_chrdev_region(rmidev->dev_no, 1); + + if (rmidev_device_class != NULL) { + class_destroy(rmidev_device_class); + rmidev_device_class = NULL; + } + + kfree(rmidev->tmpbuf); + + kfree(rmidev); + rmidev = NULL; + +exit: + complete(&rmidev_remove_complete); +} + +static struct synaptics_rmi4_exp_fn rmidev_module = { + .fn_type = RMI_DEV, + .init = rmidev_init_device, + .remove = rmidev_remove_device, + .reset = NULL, + .reinit = NULL, + .early_suspend = NULL, + .suspend = NULL, + .resume = NULL, + .late_resume = NULL, + .attn = rmidev_attn, +}; + +static int __init rmidev_module_init(void) +{ + synaptics_rmi4_new_function(&rmidev_module, true); + + return 0; +} + +static void __exit rmidev_module_exit(void) +{ + synaptics_rmi4_new_function(&rmidev_module, false); + + wait_for_completion(&rmidev_remove_complete); +} + +module_init(rmidev_module_init); +module_exit(rmidev_module_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics DSX RMI Dev Module"); +MODULE_LICENSE("GPL v2"); diff --git a/qcom/opensource/touch-drivers/synaptics_dsx/synaptics_dsx_rmi_hid_i2c.c b/qcom/opensource/touch-drivers/synaptics_dsx/synaptics_dsx_rmi_hid_i2c.c new file mode 100644 index 0000000000..135dd81e13 --- /dev/null +++ b/qcom/opensource/touch-drivers/synaptics_dsx/synaptics_dsx_rmi_hid_i2c.c @@ -0,0 +1,989 @@ +/* + * Synaptics DSX touchscreen driver + * + * Copyright (C) 2012-2016 Synaptics Incorporated. All rights reserved. + * + * Copyright (c) 2018-2021 The Linux Foundation. All rights reserved. + * Copyright (C) 2012 Alexandra Chin + * Copyright (C) 2012 Scott Lin + * + * 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 +#include +#include +#include +#include +#include +#include +#include "synaptics_dsx_core.h" + +#define SYN_I2C_RETRY_TIMES 10 + +#define REPORT_ID_GET_BLOB 0x07 +#define REPORT_ID_WRITE 0x09 +#define REPORT_ID_READ_ADDRESS 0x0a +#define REPORT_ID_READ_DATA 0x0b +#define REPORT_ID_SET_RMI_MODE 0x0f + +#define PREFIX_USAGE_PAGE_1BYTE 0x05 +#define PREFIX_USAGE_PAGE_2BYTES 0x06 +#define PREFIX_USAGE 0x09 +#define PREFIX_REPORT_ID 0x85 +#define PREFIX_REPORT_COUNT_1BYTE 0x95 +#define PREFIX_REPORT_COUNT_2BYTES 0x96 + +#define USAGE_GET_BLOB 0xc5 +#define USAGE_WRITE 0x02 +#define USAGE_READ_ADDRESS 0x03 +#define USAGE_READ_DATA 0x04 +#define USAGE_SET_MODE 0x06 + +#define FEATURE_REPORT_TYPE 0x03 + +#define VENDOR_DEFINED_PAGE 0xff00 + +#define BLOB_REPORT_SIZE 256 + +#define RESET_COMMAND 0x01 +#define GET_REPORT_COMMAND 0x02 +#define SET_REPORT_COMMAND 0x03 +#define SET_POWER_COMMAND 0x08 + +#define FINGER_MODE 0x00 +#define RMI_MODE 0x02 + +struct hid_report_info { + unsigned char get_blob_id; + unsigned char write_id; + unsigned char read_addr_id; + unsigned char read_data_id; + unsigned char set_mode_id; + unsigned int blob_size; +}; + +static struct hid_report_info hid_report; + +struct hid_device_descriptor { + unsigned short device_descriptor_length; + unsigned short format_version; + unsigned short report_descriptor_length; + unsigned short report_descriptor_index; + unsigned short input_register_index; + unsigned short input_report_max_length; + unsigned short output_register_index; + unsigned short output_report_max_length; + unsigned short command_register_index; + unsigned short data_register_index; + unsigned short vendor_id; + unsigned short product_id; + unsigned short version_id; + unsigned int reserved; +}; + +static struct hid_device_descriptor hid_dd; + +struct i2c_rw_buffer { + unsigned char *read; + unsigned char *write; + unsigned int read_size; + unsigned int write_size; +}; + +static struct i2c_rw_buffer buffer; + +#ifdef CONFIG_OF +static int parse_dt(struct device *dev, struct synaptics_dsx_board_data *bdata) +{ + int retval; + u32 value; + const char *name; + struct property *prop; + struct device_node *np = dev->of_node; + + bdata->irq_gpio = of_get_named_gpio_flags(np, + "synaptics,irq-gpio", 0, + (enum of_gpio_flags *)&bdata->irq_flags); + + 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); + retval = of_property_read_u32(np, "synaptics,power-on-state", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,power-on-state property\n", + __func__); + return retval; + } + bdata->power_on_state = value; + } else { + bdata->power_gpio = -1; + } + + 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) { + dev_err(dev, "%s: Unable to read synaptics,power-delay-ms property\n", + __func__); + return retval; + } + bdata->power_delay_ms = value; + } else { + bdata->power_delay_ms = 0; + } + + prop = of_find_property(np, "synaptics,reset-gpio", NULL); + if (prop && prop->length) { + bdata->reset_gpio = of_get_named_gpio_flags(np, + "synaptics,reset-gpio", 0, NULL); + retval = of_property_read_u32(np, "synaptics,reset-on-state", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,reset-on-state property\n", + __func__); + return retval; + } + bdata->reset_on_state = value; + retval = of_property_read_u32(np, "synaptics,reset-active-ms", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,reset-active-ms property\n", + __func__); + return retval; + } + bdata->reset_active_ms = value; + } else { + bdata->reset_gpio = -1; + } + + 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) { + dev_err(dev, "%s: Unable to read synaptics,reset-delay-ms property\n", + __func__); + return retval; + } + bdata->reset_delay_ms = value; + } else { + bdata->reset_delay_ms = 0; + } + + prop = of_find_property(np, "synaptics,dev-dscrptr-addr", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,dev-dscrptr-addr", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,dev-dscrptr-addr property\n", + __func__); + return retval; + } + bdata->device_descriptor_addr = (unsigned short)value; + } else { + bdata->device_descriptor_addr = 0; + } + + prop = of_find_property(np, "synaptics,max-y-for-2d", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,max-y-for-2d", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,max-y-for-2d property\n", + __func__); + return retval; + } + bdata->max_y_for_2d = value; + } else { + bdata->max_y_for_2d = -1; + } + + prop = of_find_property(np, "synaptics,swap-axes", NULL); + bdata->swap_axes = prop > 0 ? true : false; + + 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,ub-i2c-addr", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,ub-i2c-addr", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,ub-i2c-addr property\n", + __func__); + return retval; + } + bdata->ub_i2c_addr = (unsigned short)value; + } else { + bdata->ub_i2c_addr = -1; + } + + prop = of_find_property(np, "synaptics,cap-button-codes", NULL); + if (prop && prop->length) { + bdata->cap_button_map->map = devm_kzalloc(dev, + prop->length, + GFP_KERNEL); + if (!bdata->cap_button_map->map) + return -ENOMEM; + bdata->cap_button_map->nbuttons = prop->length / sizeof(u32); + retval = of_property_read_u32_array(np, + "synaptics,cap-button-codes", + bdata->cap_button_map->map, + bdata->cap_button_map->nbuttons); + if (retval < 0) { + bdata->cap_button_map->nbuttons = 0; + bdata->cap_button_map->map = NULL; + } + } else { + bdata->cap_button_map->nbuttons = 0; + bdata->cap_button_map->map = NULL; + } + + prop = of_find_property(np, "synaptics,vir-button-codes", NULL); + if (prop && prop->length) { + bdata->vir_button_map->map = devm_kzalloc(dev, + prop->length, + GFP_KERNEL); + if (!bdata->vir_button_map->map) + return -ENOMEM; + bdata->vir_button_map->nbuttons = prop->length / sizeof(u32); + bdata->vir_button_map->nbuttons /= 5; + retval = of_property_read_u32_array(np, + "synaptics,vir-button-codes", + bdata->vir_button_map->map, + bdata->vir_button_map->nbuttons * 5); + if (retval < 0) { + bdata->vir_button_map->nbuttons = 0; + bdata->vir_button_map->map = NULL; + } + } else { + bdata->vir_button_map->nbuttons = 0; + bdata->vir_button_map->map = NULL; + } + + return 0; +} +#endif + +static int do_i2c_transfer(struct i2c_client *client, struct i2c_msg *msg) +{ + unsigned char retry; + + for (retry = 0; retry < SYN_I2C_RETRY_TIMES; retry++) { + if (i2c_transfer(client->adapter, msg, 1) == 1) + break; + dev_err(&client->dev, + "%s: I2C retry %d\n", + __func__, retry + 1); + msleep(20); + } + + if (retry == SYN_I2C_RETRY_TIMES) { + dev_err(&client->dev, + "%s: I2C transfer over retry limit\n", + __func__); + return -EIO; + } + + return 0; +} + +static int check_buffer(unsigned char **buffer, unsigned int *buffer_size, + unsigned int length) +{ + if (*buffer_size < length) { + if (*buffer_size) + kfree(*buffer); + *buffer = kzalloc(length, GFP_KERNEL); + if (!(*buffer)) + return -ENOMEM; + *buffer_size = length; + } + + return 0; +} + +static int generic_read(struct i2c_client *client, unsigned short length) +{ + int retval; + struct i2c_msg msg[] = { + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = length, + } + }; + + check_buffer(&buffer.read, &buffer.read_size, length); + msg[0].buf = buffer.read; + + retval = do_i2c_transfer(client, msg); + + return retval; +} + +static int generic_write(struct i2c_client *client, unsigned short length) +{ + int retval; + struct i2c_msg msg[] = { + { + .addr = client->addr, + .flags = 0, + .len = length, + .buf = buffer.write, + } + }; + + retval = do_i2c_transfer(client, msg); + + return retval; +} + +static void traverse_report_descriptor(unsigned int *index) +{ + unsigned char size; + unsigned char *buf = buffer.read; + + size = buf[*index] & MASK_2BIT; + switch (size) { + case 0: /* 0 bytes */ + *index += 1; + break; + case 1: /* 1 byte */ + *index += 2; + break; + case 2: /* 2 bytes */ + *index += 3; + break; + case 3: /* 4 bytes */ + *index += 5; + break; + default: + break; + } +} + +static void find_blob_size(unsigned int index) +{ + unsigned int ii = index; + unsigned char *buf = buffer.read; + + while (ii < hid_dd.report_descriptor_length) { + if (buf[ii] == PREFIX_REPORT_COUNT_1BYTE) { + hid_report.blob_size = buf[ii + 1]; + return; + } else if (buf[ii] == PREFIX_REPORT_COUNT_2BYTES) { + hid_report.blob_size = buf[ii + 1] | (buf[ii + 2] << 8); + return; + } + traverse_report_descriptor(&ii); + } +} + +static void find_reports(unsigned int index) +{ + unsigned int ii = index; + unsigned char *buf = buffer.read; + static unsigned int report_id_index; + static unsigned char report_id; + static unsigned short usage_page; + + if (buf[ii] == PREFIX_REPORT_ID) { + report_id = buf[ii + 1]; + report_id_index = ii; + return; + } + + if (buf[ii] == PREFIX_USAGE_PAGE_1BYTE) { + usage_page = buf[ii + 1]; + return; + } else if (buf[ii] == PREFIX_USAGE_PAGE_2BYTES) { + usage_page = buf[ii + 1] | (buf[ii + 2] << 8); + return; + } + + if ((usage_page == VENDOR_DEFINED_PAGE) && (buf[ii] == PREFIX_USAGE)) { + switch (buf[ii + 1]) { + case USAGE_GET_BLOB: + hid_report.get_blob_id = report_id; + find_blob_size(report_id_index); + break; + case USAGE_WRITE: + hid_report.write_id = report_id; + break; + case USAGE_READ_ADDRESS: + hid_report.read_addr_id = report_id; + break; + case USAGE_READ_DATA: + hid_report.read_data_id = report_id; + break; + case USAGE_SET_MODE: + hid_report.set_mode_id = report_id; + break; + default: + break; + } + } +} + +static int parse_report_descriptor(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + unsigned int ii = 0; + unsigned char *buf; + struct i2c_client *i2c = to_i2c_client(rmi4_data->pdev->dev.parent); + + buffer.write[0] = hid_dd.report_descriptor_index & MASK_8BIT; + buffer.write[1] = hid_dd.report_descriptor_index >> 8; + retval = generic_write(i2c, 2); + if (retval < 0) + return retval; + retval = generic_read(i2c, hid_dd.report_descriptor_length); + if (retval < 0) + return retval; + + buf = buffer.read; + + hid_report.get_blob_id = REPORT_ID_GET_BLOB; + hid_report.write_id = REPORT_ID_WRITE; + hid_report.read_addr_id = REPORT_ID_READ_ADDRESS; + hid_report.read_data_id = REPORT_ID_READ_DATA; + hid_report.set_mode_id = REPORT_ID_SET_RMI_MODE; + hid_report.blob_size = BLOB_REPORT_SIZE; + + while (ii < hid_dd.report_descriptor_length) { + find_reports(ii); + traverse_report_descriptor(&ii); + } + + return 0; +} + +static int switch_to_rmi(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + struct i2c_client *i2c = to_i2c_client(rmi4_data->pdev->dev.parent); + + mutex_lock(&rmi4_data->rmi4_io_ctrl_mutex); + + check_buffer(&buffer.write, &buffer.write_size, 11); + + /* set rmi mode */ + buffer.write[0] = hid_dd.command_register_index & MASK_8BIT; + buffer.write[1] = hid_dd.command_register_index >> 8; + buffer.write[2] = (FEATURE_REPORT_TYPE << 4) | hid_report.set_mode_id; + buffer.write[3] = SET_REPORT_COMMAND; + buffer.write[4] = hid_report.set_mode_id; + buffer.write[5] = hid_dd.data_register_index & MASK_8BIT; + buffer.write[6] = hid_dd.data_register_index >> 8; + buffer.write[7] = 0x04; + buffer.write[8] = 0x00; + buffer.write[9] = hid_report.set_mode_id; + buffer.write[10] = RMI_MODE; + + retval = generic_write(i2c, 11); + + mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex); + + return retval; +} + +static int check_report_mode(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + unsigned short report_size; + struct i2c_client *i2c = to_i2c_client(rmi4_data->pdev->dev.parent); + + mutex_lock(&rmi4_data->rmi4_io_ctrl_mutex); + + check_buffer(&buffer.write, &buffer.write_size, 7); + + buffer.write[0] = hid_dd.command_register_index & MASK_8BIT; + buffer.write[1] = hid_dd.command_register_index >> 8; + buffer.write[2] = (FEATURE_REPORT_TYPE << 4) | hid_report.set_mode_id; + buffer.write[3] = GET_REPORT_COMMAND; + buffer.write[4] = hid_report.set_mode_id; + buffer.write[5] = hid_dd.data_register_index & MASK_8BIT; + buffer.write[6] = hid_dd.data_register_index >> 8; + + retval = generic_write(i2c, 7); + if (retval < 0) + goto exit; + + retval = generic_read(i2c, 2); + if (retval < 0) + goto exit; + + report_size = (buffer.read[1] << 8) | buffer.read[0]; + + retval = generic_write(i2c, 7); + if (retval < 0) + goto exit; + + retval = generic_read(i2c, report_size); + if (retval < 0) + goto exit; + + retval = buffer.read[3]; + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Report mode = %d\n", + __func__, retval); + +exit: + mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex); + + return retval; +} + +static int hid_i2c_init(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + struct i2c_client *i2c = to_i2c_client(rmi4_data->pdev->dev.parent); + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + mutex_lock(&rmi4_data->rmi4_io_ctrl_mutex); + + check_buffer(&buffer.write, &buffer.write_size, 6); + + /* read device descriptor */ + buffer.write[0] = bdata->device_descriptor_addr & MASK_8BIT; + buffer.write[1] = bdata->device_descriptor_addr >> 8; + retval = generic_write(i2c, 2); + if (retval < 0) + goto exit; + retval = generic_read(i2c, sizeof(hid_dd)); + if (retval < 0) + goto exit; + retval = secure_memcpy((unsigned char *)&hid_dd, + sizeof(struct hid_device_descriptor), + buffer.read, + buffer.read_size, + sizeof(hid_dd)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy device descriptor data\n", + __func__); + goto exit; + } + + retval = parse_report_descriptor(rmi4_data); + if (retval < 0) + goto exit; + + /* set power */ + buffer.write[0] = hid_dd.command_register_index & MASK_8BIT; + buffer.write[1] = hid_dd.command_register_index >> 8; + buffer.write[2] = 0x00; + buffer.write[3] = SET_POWER_COMMAND; + retval = generic_write(i2c, 4); + if (retval < 0) + goto exit; + + /* reset */ + buffer.write[0] = hid_dd.command_register_index & MASK_8BIT; + buffer.write[1] = hid_dd.command_register_index >> 8; + buffer.write[2] = 0x00; + buffer.write[3] = RESET_COMMAND; + retval = generic_write(i2c, 4); + if (retval < 0) + goto exit; + + while (gpio_get_value(bdata->irq_gpio)) + msleep(20); + + retval = generic_read(i2c, hid_dd.input_report_max_length); + if (retval < 0) + goto exit; + + /* get blob */ + buffer.write[0] = hid_dd.command_register_index & MASK_8BIT; + buffer.write[1] = hid_dd.command_register_index >> 8; + buffer.write[2] = (FEATURE_REPORT_TYPE << 4) | hid_report.get_blob_id; + buffer.write[3] = 0x02; + buffer.write[4] = hid_dd.data_register_index & MASK_8BIT; + buffer.write[5] = hid_dd.data_register_index >> 8; + + retval = generic_write(i2c, 6); + if (retval < 0) + goto exit; + + msleep(20); + + retval = generic_read(i2c, hid_report.blob_size + 3); + if (retval < 0) + goto exit; + +exit: + mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex); + + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to initialize HID/I2C interface\n", + __func__); + return retval; + } + + retval = switch_to_rmi(rmi4_data); + + return retval; +} + +static int synaptics_rmi4_i2c_read(struct synaptics_rmi4_data *rmi4_data, + unsigned short addr, unsigned char *data, unsigned int length) +{ + int retval; + unsigned char retry; + unsigned char recover = 1; + unsigned short report_length; + struct i2c_client *i2c = to_i2c_client(rmi4_data->pdev->dev.parent); + struct i2c_msg msg[] = { + { + .addr = i2c->addr, + .flags = 0, + .len = hid_dd.output_report_max_length + 2, + }, + { + .addr = i2c->addr, + .flags = I2C_M_RD, + .len = (unsigned short)(length + 4), + }, + }; + +recover: + mutex_lock(&rmi4_data->rmi4_io_ctrl_mutex); + + check_buffer(&buffer.write, &buffer.write_size, + hid_dd.output_report_max_length + 2); + msg[0].buf = buffer.write; + buffer.write[0] = hid_dd.output_register_index & MASK_8BIT; + buffer.write[1] = hid_dd.output_register_index >> 8; + buffer.write[2] = hid_dd.output_report_max_length & MASK_8BIT; + buffer.write[3] = hid_dd.output_report_max_length >> 8; + buffer.write[4] = hid_report.read_addr_id; + buffer.write[5] = 0x00; + buffer.write[6] = addr & MASK_8BIT; + buffer.write[7] = addr >> 8; + buffer.write[8] = (unsigned char)length; + buffer.write[9] = (unsigned char)(length >> 8); + + check_buffer(&buffer.read, &buffer.read_size, length + 4); + msg[1].buf = buffer.read; + + retval = do_i2c_transfer(i2c, &msg[0]); + if (retval != 0) + goto exit; + + retry = 0; + do { + retval = do_i2c_transfer(i2c, &msg[1]); + if (retval == 0) + retval = length; + else + goto exit; + + report_length = (buffer.read[1] << 8) | buffer.read[0]; + if (report_length == hid_dd.input_report_max_length) { + retval = secure_memcpy(&data[0], length, + &buffer.read[4], buffer.read_size - 4, + length); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy data\n", + __func__); + } else { + retval = length; + } + goto exit; + } + + msleep(20); + retry++; + } while (retry < SYN_I2C_RETRY_TIMES); + + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to receive read report\n", + __func__); + retval = -EIO; + +exit: + mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex); + + if ((retval != length) && (recover == 1)) { + recover = 0; + if (check_report_mode(rmi4_data) != RMI_MODE) { + retval = hid_i2c_init(rmi4_data); + if (retval == 0) + goto recover; + } + } + + return retval; +} + +static int synaptics_rmi4_i2c_write(struct synaptics_rmi4_data *rmi4_data, + unsigned short addr, unsigned char *data, unsigned int length) +{ + int retval; + unsigned char recover = 1; + unsigned int msg_length; + struct i2c_client *i2c = to_i2c_client(rmi4_data->pdev->dev.parent); + struct i2c_msg msg[] = { + { + .addr = i2c->addr, + .flags = 0, + } + }; + + if ((length + 10) < (hid_dd.output_report_max_length + 2)) + msg_length = hid_dd.output_report_max_length + 2; + else + msg_length = length + 10; + +recover: + mutex_lock(&rmi4_data->rmi4_io_ctrl_mutex); + + check_buffer(&buffer.write, &buffer.write_size, msg_length); + msg[0].len = (unsigned short)msg_length; + msg[0].buf = buffer.write; + buffer.write[0] = hid_dd.output_register_index & MASK_8BIT; + buffer.write[1] = hid_dd.output_register_index >> 8; + buffer.write[2] = hid_dd.output_report_max_length & MASK_8BIT; + buffer.write[3] = hid_dd.output_report_max_length >> 8; + buffer.write[4] = hid_report.write_id; + buffer.write[5] = 0x00; + buffer.write[6] = addr & MASK_8BIT; + buffer.write[7] = addr >> 8; + buffer.write[8] = (unsigned char)length; + buffer.write[9] = (unsigned char)(length >> 8); + retval = secure_memcpy(&buffer.write[10], buffer.write_size - 10, + &data[0], length, length); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy data\n", + __func__); + } else { + retval = do_i2c_transfer(i2c, msg); + if (retval == 0) + retval = length; + } + + mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex); + + if ((retval != length) && (recover == 1)) { + recover = 0; + if (check_report_mode(rmi4_data) != RMI_MODE) { + retval = hid_i2c_init(rmi4_data); + if (retval == 0) + goto recover; + } + } + + return retval; +} + +static struct synaptics_dsx_bus_access bus_access = { + .type = BUS_I2C, + .read = synaptics_rmi4_i2c_read, + .write = synaptics_rmi4_i2c_write, +}; + +static struct synaptics_dsx_hw_interface hw_if; + +static struct platform_device *synaptics_dsx_i2c_device; + +static void synaptics_rmi4_i2c_dev_release(struct device *dev) +{ + kfree(synaptics_dsx_i2c_device); +} + +static int synaptics_rmi4_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *dev_id) +{ + int retval; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(&client->dev, + "%s: SMBus byte data commands not supported by host\n", + __func__); + return -EIO; + } + + synaptics_dsx_i2c_device = kzalloc( + sizeof(struct platform_device), + GFP_KERNEL); + if (!synaptics_dsx_i2c_device) { + dev_err(&client->dev, + "%s: Failed to allocate memory for synaptics_dsx_i2c_device\n", + __func__); + return -ENOMEM; + } + +#ifdef CONFIG_OF + if (client->dev.of_node) { + hw_if.board_data = devm_kzalloc(&client->dev, + sizeof(struct synaptics_dsx_board_data), + GFP_KERNEL); + if (!hw_if.board_data) { + dev_err(&client->dev, + "%s: Failed to allocate memory for board data\n", + __func__); + return -ENOMEM; + } + hw_if.board_data->cap_button_map = devm_kzalloc(&client->dev, + sizeof(struct synaptics_dsx_button_map), + GFP_KERNEL); + if (!hw_if.board_data->cap_button_map) { + dev_err(&client->dev, + "%s: Failed to allocate memory for 0D button map\n", + __func__); + return -ENOMEM; + } + hw_if.board_data->vir_button_map = devm_kzalloc(&client->dev, + sizeof(struct synaptics_dsx_button_map), + GFP_KERNEL); + if (!hw_if.board_data->vir_button_map) { + dev_err(&client->dev, + "%s: Failed to allocate memory for virtual button map\n", + __func__); + return -ENOMEM; + } + parse_dt(&client->dev, hw_if.board_data); + } +#else + hw_if.board_data = client->dev.platform_data; +#endif + + hw_if.bus_access = &bus_access; + hw_if.bl_hw_init = switch_to_rmi; + hw_if.ui_hw_init = hid_i2c_init; + + synaptics_dsx_i2c_device->name = PLATFORM_DRIVER_NAME; + synaptics_dsx_i2c_device->id = 0; + synaptics_dsx_i2c_device->num_resources = 0; + synaptics_dsx_i2c_device->dev.parent = &client->dev; + synaptics_dsx_i2c_device->dev.platform_data = &hw_if; + synaptics_dsx_i2c_device->dev.release = synaptics_rmi4_i2c_dev_release; + + retval = platform_device_register(synaptics_dsx_i2c_device); + if (retval) { + dev_err(&client->dev, + "%s: Failed to register platform device\n", + __func__); + return -ENODEV; + } + + return 0; +} + +static int synaptics_rmi4_i2c_remove(struct i2c_client *client) +{ + if (buffer.read_size) + kfree(buffer.read); + + if (buffer.write_size) + kfree(buffer.write); + + platform_device_unregister(synaptics_dsx_i2c_device); + + return 0; +} + +static const struct i2c_device_id synaptics_rmi4_id_table[] = { + {I2C_DRIVER_NAME, 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, synaptics_rmi4_id_table); + +#ifdef CONFIG_OF +static const struct of_device_id synaptics_rmi4_of_match_table[] = { + { + .compatible = "synaptics,dsx-rmi-hid-i2c", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, synaptics_rmi4_of_match_table); +#else +#define synaptics_rmi4_of_match_table NULL +#endif + +static struct i2c_driver synaptics_rmi4_i2c_driver = { + .driver = { + .name = I2C_DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = synaptics_rmi4_of_match_table, + }, + .probe = synaptics_rmi4_i2c_probe, + .remove = synaptics_rmi4_i2c_remove, + .id_table = synaptics_rmi4_id_table, +}; + +int synaptics_rmi4_bus_init(void) +{ + return i2c_add_driver(&synaptics_rmi4_i2c_driver); +} +EXPORT_SYMBOL(synaptics_rmi4_bus_init); + +void synaptics_rmi4_bus_exit(void) +{ + i2c_del_driver(&synaptics_rmi4_i2c_driver); +} +EXPORT_SYMBOL(synaptics_rmi4_bus_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics DSX I2C Bus Support Module"); +MODULE_LICENSE("GPL v2"); diff --git a/qcom/opensource/touch-drivers/synaptics_dsx/synaptics_dsx_spi.c b/qcom/opensource/touch-drivers/synaptics_dsx/synaptics_dsx_spi.c new file mode 100644 index 0000000000..7e957813dc --- /dev/null +++ b/qcom/opensource/touch-drivers/synaptics_dsx/synaptics_dsx_spi.c @@ -0,0 +1,698 @@ +/* + * Synaptics DSX touchscreen driver + * + * Copyright (C) 2012-2016 Synaptics Incorporated. All rights reserved. + * + * Copyright (c) 2018-2021 The Linux Foundation. All rights reserved. + * Copyright (C) 2012 Alexandra Chin + * Copyright (C) 2012 Scott Lin + * + * 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 +#include +#include +#include +#include +#include "synaptics_dsx_core.h" + +#define SPI_READ 0x80 +#define SPI_WRITE 0x00 + +static unsigned char *buf; + +static struct spi_transfer *xfer; + +#ifdef CONFIG_OF +static int parse_dt(struct device *dev, struct synaptics_dsx_board_data *bdata) +{ + int retval; + u32 value; + const char *name; + struct property *prop; + struct device_node *np = dev->of_node; + + bdata->irq_gpio = of_get_named_gpio_flags(np, + "synaptics,irq-gpio", 0, + (enum of_gpio_flags *)&bdata->irq_flags); + + 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); + retval = of_property_read_u32(np, "synaptics,power-on-state", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,power-on-state property\n", + __func__); + return retval; + } + bdata->power_on_state = value; + } else { + bdata->power_gpio = -1; + } + + 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) { + dev_err(dev, "%s: Unable to read synaptics,power-delay-ms property\n", + __func__); + return retval; + } + bdata->power_delay_ms = value; + } else { + bdata->power_delay_ms = 0; + } + + prop = of_find_property(np, "synaptics,reset-gpio", NULL); + if (prop && prop->length) { + bdata->reset_gpio = of_get_named_gpio_flags(np, + "synaptics,reset-gpio", 0, NULL); + retval = of_property_read_u32(np, "synaptics,reset-on-state", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,reset-on-state property\n", + __func__); + return retval; + } + bdata->reset_on_state = value; + retval = of_property_read_u32(np, "synaptics,reset-active-ms", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,reset-active-ms property\n", + __func__); + return retval; + } + bdata->reset_active_ms = value; + } else { + bdata->reset_gpio = -1; + } + + 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) { + dev_err(dev, "%s: Unable to read synaptics,reset-delay-ms property\n", + __func__); + return retval; + } + bdata->reset_delay_ms = value; + } else { + bdata->reset_delay_ms = 0; + } + + 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) { + dev_err(dev, "%s: Unable to read synaptics,byte-delay-us property\n", + __func__); + return retval; + } + bdata->byte_delay_us = value; + } else { + bdata->byte_delay_us = 0; + } + + prop = of_find_property(np, "synaptics,block-delay-us", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,block-delay-us", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,block-delay-us property\n", + __func__); + return retval; + } + bdata->block_delay_us = value; + } else { + bdata->block_delay_us = 0; + } + + prop = of_find_property(np, "synaptics,address-delay-us", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,address-delay-us", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,address-delay-us property\n", + __func__); + return retval; + } + bdata->addr_delay_us = value; + } else { + bdata->addr_delay_us = 0; + } + + prop = of_find_property(np, "synaptics,max-y-for-2d", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,max-y-for-2d", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,max-y-for-2d property\n", + __func__); + return retval; + } + bdata->max_y_for_2d = value; + } else { + bdata->max_y_for_2d = -1; + } + + prop = of_find_property(np, "synaptics,swap-axes", NULL); + bdata->swap_axes = prop > 0 ? true : false; + + 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,ub-i2c-addr", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,ub-i2c-addr", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,ub-i2c-addr property\n", + __func__); + return retval; + } + bdata->ub_i2c_addr = (unsigned short)value; + } else { + bdata->ub_i2c_addr = -1; + } + + prop = of_find_property(np, "synaptics,cap-button-codes", NULL); + if (prop && prop->length) { + bdata->cap_button_map->map = devm_kzalloc(dev, + prop->length, + GFP_KERNEL); + if (!bdata->cap_button_map->map) + return -ENOMEM; + bdata->cap_button_map->nbuttons = prop->length / sizeof(u32); + retval = of_property_read_u32_array(np, + "synaptics,cap-button-codes", + bdata->cap_button_map->map, + bdata->cap_button_map->nbuttons); + if (retval < 0) { + bdata->cap_button_map->nbuttons = 0; + bdata->cap_button_map->map = NULL; + } + } else { + bdata->cap_button_map->nbuttons = 0; + bdata->cap_button_map->map = NULL; + } + + prop = of_find_property(np, "synaptics,vir-button-codes", NULL); + if (prop && prop->length) { + bdata->vir_button_map->map = devm_kzalloc(dev, + prop->length, + GFP_KERNEL); + if (!bdata->vir_button_map->map) + return -ENOMEM; + bdata->vir_button_map->nbuttons = prop->length / sizeof(u32); + bdata->vir_button_map->nbuttons /= 5; + retval = of_property_read_u32_array(np, + "synaptics,vir-button-codes", + bdata->vir_button_map->map, + bdata->vir_button_map->nbuttons * 5); + if (retval < 0) { + bdata->vir_button_map->nbuttons = 0; + bdata->vir_button_map->map = NULL; + } + } else { + bdata->vir_button_map->nbuttons = 0; + bdata->vir_button_map->map = NULL; + } + + return 0; +} +#endif + +static int synaptics_rmi4_spi_alloc_buf(struct synaptics_rmi4_data *rmi4_data, + unsigned int size, unsigned int count) +{ + static unsigned int buf_size; + static unsigned int xfer_count; + + if (size > buf_size) { + if (buf_size) + kfree(buf); + buf = kmalloc(size, GFP_KERNEL); + if (!buf) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for buf\n", + __func__); + buf_size = 0; + return -ENOMEM; + } + buf_size = size; + } + + if (count > xfer_count) { + if (xfer_count) + kfree(xfer); + xfer = kcalloc(count, sizeof(struct spi_transfer), GFP_KERNEL); + if (!xfer) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for xfer\n", + __func__); + xfer_count = 0; + return -ENOMEM; + } + xfer_count = count; + } else { + memset(xfer, 0, count * sizeof(struct spi_transfer)); + } + + return 0; +} + +static int synaptics_rmi4_spi_set_page(struct synaptics_rmi4_data *rmi4_data, + unsigned short addr) +{ + int retval; + unsigned int index; + unsigned int byte_count = PAGE_SELECT_LEN + 1; + unsigned char page; + struct spi_message msg; + struct spi_device *spi = to_spi_device(rmi4_data->pdev->dev.parent); + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + page = ((addr >> 8) & MASK_8BIT); + if ((page >> 7) == (rmi4_data->current_page >> 7)) + return PAGE_SELECT_LEN; + + spi_message_init(&msg); + + retval = synaptics_rmi4_spi_alloc_buf(rmi4_data, byte_count, + byte_count); + if (retval < 0) + return retval; + + buf[0] = SPI_WRITE; + buf[1] = MASK_8BIT; + buf[2] = page; + + if (bdata->byte_delay_us == 0) { + xfer[0].len = byte_count; + xfer[0].tx_buf = &buf[0]; + if (bdata->block_delay_us) + xfer[0].delay_usecs = bdata->block_delay_us; + spi_message_add_tail(&xfer[0], &msg); + } else { + for (index = 0; index < byte_count; index++) { + xfer[index].len = 1; + xfer[index].tx_buf = &buf[index]; + if (index == 1) + xfer[index].delay_usecs = bdata->addr_delay_us; + else + xfer[index].delay_usecs = bdata->byte_delay_us; + spi_message_add_tail(&xfer[index], &msg); + } + if (bdata->block_delay_us) + xfer[index - 1].delay_usecs = bdata->block_delay_us; + } + + retval = spi_sync(spi, &msg); + if (retval == 0) { + rmi4_data->current_page = page; + retval = PAGE_SELECT_LEN; + } else { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to complete SPI transfer, error = %d\n", + __func__, retval); + } + + return retval; +} + +static int synaptics_rmi4_spi_read(struct synaptics_rmi4_data *rmi4_data, + unsigned short addr, unsigned char *data, unsigned int length) +{ + int retval; + unsigned int index; + unsigned int byte_count = length + ADDRESS_LEN; + unsigned char txbuf[ADDRESS_LEN]; + struct spi_message msg; + struct spi_device *spi = to_spi_device(rmi4_data->pdev->dev.parent); + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + spi_message_init(&msg); + + txbuf[0] = (addr >> 8) | SPI_READ; + txbuf[1] = addr & MASK_8BIT; + + mutex_lock(&rmi4_data->rmi4_io_ctrl_mutex); + + retval = synaptics_rmi4_spi_set_page(rmi4_data, addr); + if (retval != PAGE_SELECT_LEN) { + mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex); + return -EIO; + } + + if (bdata->byte_delay_us == 0) { + retval = synaptics_rmi4_spi_alloc_buf(rmi4_data, length, + 2); + } else { + retval = synaptics_rmi4_spi_alloc_buf(rmi4_data, length, + byte_count); + } + if (retval < 0) { + mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex); + return retval; + } + + if (bdata->byte_delay_us == 0) { + xfer[0].len = ADDRESS_LEN; + xfer[0].tx_buf = &txbuf[0]; + spi_message_add_tail(&xfer[0], &msg); + xfer[1].len = length; + xfer[1].rx_buf = &buf[0]; + if (bdata->block_delay_us) + xfer[1].delay_usecs = bdata->block_delay_us; + spi_message_add_tail(&xfer[1], &msg); + } else { + for (index = 0; index < byte_count; index++) { + xfer[index].len = 1; + if (index < ADDRESS_LEN) + xfer[index].tx_buf = &txbuf[index]; + else + xfer[index].rx_buf = &buf[index - ADDRESS_LEN]; + if (index == 1) + xfer[index].delay_usecs = bdata->addr_delay_us; + else + xfer[index].delay_usecs = bdata->byte_delay_us; + spi_message_add_tail(&xfer[index], &msg); + } + if (bdata->block_delay_us) + xfer[index - 1].delay_usecs = bdata->block_delay_us; + } + + retval = spi_sync(spi, &msg); + if (retval == 0) { + retval = secure_memcpy(data, length, buf, length, length); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy data\n", + __func__); + } else { + retval = length; + } + } else { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to complete SPI transfer, error = %d\n", + __func__, retval); + } + + mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex); + + return retval; +} + +static int synaptics_rmi4_spi_write(struct synaptics_rmi4_data *rmi4_data, + unsigned short addr, unsigned char *data, unsigned int length) +{ + int retval; + unsigned int index; + unsigned int byte_count = length + ADDRESS_LEN; + struct spi_message msg; + struct spi_device *spi = to_spi_device(rmi4_data->pdev->dev.parent); + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + spi_message_init(&msg); + + mutex_lock(&rmi4_data->rmi4_io_ctrl_mutex); + + retval = synaptics_rmi4_spi_set_page(rmi4_data, addr); + if (retval != PAGE_SELECT_LEN) { + mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex); + return -EIO; + } + + if (bdata->byte_delay_us == 0) { + retval = synaptics_rmi4_spi_alloc_buf(rmi4_data, byte_count, + 1); + } else { + retval = synaptics_rmi4_spi_alloc_buf(rmi4_data, byte_count, + byte_count); + } + if (retval < 0) { + mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex); + return retval; + } + + buf[0] = (addr >> 8) & ~SPI_READ; + buf[1] = addr & MASK_8BIT; + retval = secure_memcpy(&buf[ADDRESS_LEN], + byte_count - ADDRESS_LEN, data, length, length); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy data\n", + __func__); + mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex); + return retval; + } + + if (bdata->byte_delay_us == 0) { + xfer[0].len = byte_count; + xfer[0].tx_buf = &buf[0]; + if (bdata->block_delay_us) + xfer[0].delay_usecs = bdata->block_delay_us; + spi_message_add_tail(xfer, &msg); + } else { + for (index = 0; index < byte_count; index++) { + xfer[index].len = 1; + xfer[index].tx_buf = &buf[index]; + if (index == 1) + xfer[index].delay_usecs = bdata->addr_delay_us; + else + xfer[index].delay_usecs = bdata->byte_delay_us; + spi_message_add_tail(&xfer[index], &msg); + } + if (bdata->block_delay_us) + xfer[index - 1].delay_usecs = bdata->block_delay_us; + } + + retval = spi_sync(spi, &msg); + if (retval == 0) { + retval = length; + } else { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to complete SPI transfer, error = %d\n", + __func__, retval); + } + + mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex); + + return retval; +} + +static struct synaptics_dsx_bus_access bus_access = { + .type = BUS_SPI, + .read = synaptics_rmi4_spi_read, + .write = synaptics_rmi4_spi_write, +}; + +static struct synaptics_dsx_hw_interface hw_if; + +static struct platform_device *synaptics_dsx_spi_device; + +static void synaptics_rmi4_spi_dev_release(struct device *dev) +{ + kfree(synaptics_dsx_spi_device); +} + +static int synaptics_rmi4_spi_probe(struct spi_device *spi) +{ + int retval; + + if (spi->master->flags & SPI_MASTER_HALF_DUPLEX) { + dev_err(&spi->dev, + "%s: Full duplex not supported by host\n", + __func__); + return -EIO; + } + + synaptics_dsx_spi_device = kzalloc( + sizeof(struct platform_device), + GFP_KERNEL); + if (!synaptics_dsx_spi_device) { + dev_err(&spi->dev, + "%s: Failed to allocate memory for synaptics_dsx_spi_device\n", + __func__); + return -ENOMEM; + } + +#ifdef CONFIG_OF + if (spi->dev.of_node) { + hw_if.board_data = devm_kzalloc(&spi->dev, + sizeof(struct synaptics_dsx_board_data), + GFP_KERNEL); + if (!hw_if.board_data) { + dev_err(&spi->dev, + "%s: Failed to allocate memory for board data\n", + __func__); + return -ENOMEM; + } + hw_if.board_data->cap_button_map = devm_kzalloc(&spi->dev, + sizeof(struct synaptics_dsx_button_map), + GFP_KERNEL); + if (!hw_if.board_data->cap_button_map) { + dev_err(&spi->dev, + "%s: Failed to allocate memory for 0D button map\n", + __func__); + return -ENOMEM; + } + hw_if.board_data->vir_button_map = devm_kzalloc(&spi->dev, + sizeof(struct synaptics_dsx_button_map), + GFP_KERNEL); + if (!hw_if.board_data->vir_button_map) { + dev_err(&spi->dev, + "%s: Failed to allocate memory for virtual button map\n", + __func__); + return -ENOMEM; + } + parse_dt(&spi->dev, hw_if.board_data); + } +#else + hw_if.board_data = spi->dev.platform_data; +#endif + + hw_if.bus_access = &bus_access; + + spi->bits_per_word = 8; + spi->mode = SPI_MODE_3; + + retval = spi_setup(spi); + if (retval < 0) { + dev_err(&spi->dev, + "%s: Failed to perform SPI setup\n", + __func__); + return retval; + } + + synaptics_dsx_spi_device->name = PLATFORM_DRIVER_NAME; + synaptics_dsx_spi_device->id = 0; + synaptics_dsx_spi_device->num_resources = 0; + synaptics_dsx_spi_device->dev.parent = &spi->dev; + synaptics_dsx_spi_device->dev.platform_data = &hw_if; + synaptics_dsx_spi_device->dev.release = synaptics_rmi4_spi_dev_release; + + retval = platform_device_register(synaptics_dsx_spi_device); + if (retval) { + dev_err(&spi->dev, + "%s: Failed to register platform device\n", + __func__); + return -ENODEV; + } + + return 0; +} + +static int synaptics_rmi4_spi_remove(struct spi_device *spi) +{ + platform_device_unregister(synaptics_dsx_spi_device); + + return 0; +} + +static const struct spi_device_id synaptics_rmi4_id_table[] = { + {SPI_DRIVER_NAME, 0}, + {}, +}; +MODULE_DEVICE_TABLE(spi, synaptics_rmi4_id_table); + +#ifdef CONFIG_OF +static const struct of_device_id synaptics_rmi4_of_match_table[] = { + { + .compatible = "synaptics,dsx-spi", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, synaptics_rmi4_of_match_table); +#else +#define synaptics_rmi4_of_match_table NULL +#endif + +static struct spi_driver synaptics_rmi4_spi_driver = { + .driver = { + .name = SPI_DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = synaptics_rmi4_of_match_table, + }, + .probe = synaptics_rmi4_spi_probe, + .remove = synaptics_rmi4_spi_remove, + .id_table = synaptics_rmi4_id_table, +}; + +int synaptics_rmi4_bus_init(void) +{ + return spi_register_driver(&synaptics_rmi4_spi_driver); +} +EXPORT_SYMBOL(synaptics_rmi4_bus_init); + +void synaptics_rmi4_bus_exit(void) +{ + kfree(buf); + + kfree(xfer); + + spi_unregister_driver(&synaptics_rmi4_spi_driver); +} +EXPORT_SYMBOL(synaptics_rmi4_bus_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics DSX SPI Bus Support Module"); +MODULE_LICENSE("GPL v2"); diff --git a/qcom/opensource/touch-drivers/synaptics_dsx/synaptics_dsx_test_reporting.c b/qcom/opensource/touch-drivers/synaptics_dsx/synaptics_dsx_test_reporting.c new file mode 100644 index 0000000000..b4c4cc72be --- /dev/null +++ b/qcom/opensource/touch-drivers/synaptics_dsx/synaptics_dsx_test_reporting.c @@ -0,0 +1,5324 @@ +/* + * Synaptics DSX touchscreen driver + * + * Copyright (C) 2012-2016 Synaptics Incorporated. All rights reserved. + * + * Copyright (c) 2018-2021 The Linux Foundation. All rights reserved. + * Copyright (C) 2012 Alexandra Chin + * Copyright (C) 2012 Scott Lin + * + * 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 +#include +#include +#include +#include +#include "synaptics_dsx_core.h" + +#define SYSFS_FOLDER_NAME "f54" + +#define GET_REPORT_TIMEOUT_S 3 +#define CALIBRATION_TIMEOUT_S 10 +#define COMMAND_TIMEOUT_100MS 20 + +#define NO_SLEEP_OFF (0 << 2) +#define NO_SLEEP_ON (1 << 2) + +#define STATUS_IDLE 0 +#define STATUS_BUSY 1 +#define STATUS_ERROR 2 + +#define REPORT_INDEX_OFFSET 1 +#define REPORT_DATA_OFFSET 3 + +#define SENSOR_RX_MAPPING_OFFSET 1 +#define SENSOR_TX_MAPPING_OFFSET 2 + +#define COMMAND_GET_REPORT 1 +#define COMMAND_FORCE_CAL 2 +#define COMMAND_FORCE_UPDATE 4 + +#define CONTROL_NO_AUTO_CAL 1 + +#define CONTROL_0_SIZE 1 +#define CONTROL_1_SIZE 1 +#define CONTROL_2_SIZE 2 +#define CONTROL_3_SIZE 1 +#define CONTROL_4_6_SIZE 3 +#define CONTROL_7_SIZE 1 +#define CONTROL_8_9_SIZE 3 +#define CONTROL_10_SIZE 1 +#define CONTROL_11_SIZE 2 +#define CONTROL_12_13_SIZE 2 +#define CONTROL_14_SIZE 1 +#define CONTROL_15_SIZE 1 +#define CONTROL_16_SIZE 1 +#define CONTROL_17_SIZE 1 +#define CONTROL_18_SIZE 1 +#define CONTROL_19_SIZE 1 +#define CONTROL_20_SIZE 1 +#define CONTROL_21_SIZE 2 +#define CONTROL_22_26_SIZE 7 +#define CONTROL_27_SIZE 1 +#define CONTROL_28_SIZE 2 +#define CONTROL_29_SIZE 1 +#define CONTROL_30_SIZE 1 +#define CONTROL_31_SIZE 1 +#define CONTROL_32_35_SIZE 8 +#define CONTROL_36_SIZE 1 +#define CONTROL_37_SIZE 1 +#define CONTROL_38_SIZE 1 +#define CONTROL_39_SIZE 1 +#define CONTROL_40_SIZE 1 +#define CONTROL_41_SIZE 1 +#define CONTROL_42_SIZE 2 +#define CONTROL_43_54_SIZE 13 +#define CONTROL_55_56_SIZE 2 +#define CONTROL_57_SIZE 1 +#define CONTROL_58_SIZE 1 +#define CONTROL_59_SIZE 2 +#define CONTROL_60_62_SIZE 3 +#define CONTROL_63_SIZE 1 +#define CONTROL_64_67_SIZE 4 +#define CONTROL_68_73_SIZE 8 +#define CONTROL_70_73_SIZE 6 +#define CONTROL_74_SIZE 2 +#define CONTROL_75_SIZE 1 +#define CONTROL_76_SIZE 1 +#define CONTROL_77_78_SIZE 2 +#define CONTROL_79_83_SIZE 5 +#define CONTROL_84_85_SIZE 2 +#define CONTROL_86_SIZE 1 +#define CONTROL_87_SIZE 1 +#define CONTROL_88_SIZE 1 +#define CONTROL_89_SIZE 1 +#define CONTROL_90_SIZE 1 +#define CONTROL_91_SIZE 1 +#define CONTROL_92_SIZE 1 +#define CONTROL_93_SIZE 1 +#define CONTROL_94_SIZE 1 +#define CONTROL_95_SIZE 1 +#define CONTROL_96_SIZE 1 +#define CONTROL_97_SIZE 1 +#define CONTROL_98_SIZE 1 +#define CONTROL_99_SIZE 1 +#define CONTROL_100_SIZE 1 +#define CONTROL_101_SIZE 1 +#define CONTROL_102_SIZE 1 +#define CONTROL_103_SIZE 1 +#define CONTROL_104_SIZE 1 +#define CONTROL_105_SIZE 1 +#define CONTROL_106_SIZE 1 +#define CONTROL_107_SIZE 1 +#define CONTROL_108_SIZE 1 +#define CONTROL_109_SIZE 1 +#define CONTROL_110_SIZE 1 +#define CONTROL_111_SIZE 1 +#define CONTROL_112_SIZE 1 +#define CONTROL_113_SIZE 1 +#define CONTROL_114_SIZE 1 +#define CONTROL_115_SIZE 1 +#define CONTROL_116_SIZE 1 +#define CONTROL_117_SIZE 1 +#define CONTROL_118_SIZE 1 +#define CONTROL_119_SIZE 1 +#define CONTROL_120_SIZE 1 +#define CONTROL_121_SIZE 1 +#define CONTROL_122_SIZE 1 +#define CONTROL_123_SIZE 1 +#define CONTROL_124_SIZE 1 +#define CONTROL_125_SIZE 1 +#define CONTROL_126_SIZE 1 +#define CONTROL_127_SIZE 1 +#define CONTROL_128_SIZE 1 +#define CONTROL_129_SIZE 1 +#define CONTROL_130_SIZE 1 +#define CONTROL_131_SIZE 1 +#define CONTROL_132_SIZE 1 +#define CONTROL_133_SIZE 1 +#define CONTROL_134_SIZE 1 +#define CONTROL_135_SIZE 1 +#define CONTROL_136_SIZE 1 +#define CONTROL_137_SIZE 1 +#define CONTROL_138_SIZE 1 +#define CONTROL_139_SIZE 1 +#define CONTROL_140_SIZE 1 +#define CONTROL_141_SIZE 1 +#define CONTROL_142_SIZE 1 +#define CONTROL_143_SIZE 1 +#define CONTROL_144_SIZE 1 +#define CONTROL_145_SIZE 1 +#define CONTROL_146_SIZE 1 +#define CONTROL_147_SIZE 1 +#define CONTROL_148_SIZE 1 +#define CONTROL_149_SIZE 1 +#define CONTROL_150_SIZE 1 +#define CONTROL_151_SIZE 1 +#define CONTROL_152_SIZE 1 +#define CONTROL_153_SIZE 1 +#define CONTROL_154_SIZE 1 +#define CONTROL_155_SIZE 1 +#define CONTROL_156_SIZE 1 +#define CONTROL_157_158_SIZE 2 +#define CONTROL_163_SIZE 1 +#define CONTROL_165_SIZE 1 +#define CONTROL_166_SIZE 1 +#define CONTROL_167_SIZE 1 +#define CONTROL_168_SIZE 1 +#define CONTROL_169_SIZE 1 +#define CONTROL_171_SIZE 1 +#define CONTROL_172_SIZE 1 +#define CONTROL_173_SIZE 1 +#define CONTROL_174_SIZE 1 +#define CONTROL_175_SIZE 1 +#define CONTROL_176_SIZE 1 +#define CONTROL_177_178_SIZE 2 +#define CONTROL_179_SIZE 1 +#define CONTROL_182_SIZE 1 +#define CONTROL_183_SIZE 1 +#define CONTROL_185_SIZE 1 +#define CONTROL_186_SIZE 1 +#define CONTROL_187_SIZE 1 +#define CONTROL_188_SIZE 1 + +#define HIGH_RESISTANCE_DATA_SIZE 6 +#define FULL_RAW_CAP_MIN_MAX_DATA_SIZE 4 +#define TRX_OPEN_SHORT_DATA_SIZE 7 + +#define attrify(propname) (&dev_attr_##propname.attr) + +#define show_prototype(propname)\ +static ssize_t propname##_show(\ + struct device *dev,\ + struct device_attribute *attr,\ + char *buf);\ +\ +static struct device_attribute dev_attr_##propname =\ + __ATTR_RO(propname) + +#define store_prototype(propname)\ +static ssize_t propname##_store(\ + struct device *dev,\ + struct device_attribute *attr,\ + const char *buf, size_t count);\ +\ +static struct device_attribute dev_attr_##propname =\ + __ATTR_WO(propname) + +#define show_store_prototype(propname)\ +static ssize_t propname##_show(\ + struct device *dev,\ + struct device_attribute *attr,\ + char *buf);\ +\ +static ssize_t propname##_store(\ + struct device *dev,\ + struct device_attribute *attr,\ + const char *buf, size_t count);\ +\ +static struct device_attribute dev_attr_##propname =\ + __ATTR_RW(propname) + +#define disable_cbc(ctrl_num)\ +do {\ + retval = synaptics_rmi4_reg_read(rmi4_data,\ + f54->control.ctrl_num->address,\ + f54->control.ctrl_num->data,\ + sizeof(f54->control.ctrl_num->data));\ + if (retval < 0) {\ + dev_err(rmi4_data->pdev->dev.parent,\ + "%s: Failed to disable CBC (" #ctrl_num ")\n",\ + __func__);\ + return retval;\ + } \ + f54->control.ctrl_num->cbc_tx_carrier_selection = 0;\ + retval = synaptics_rmi4_reg_write(rmi4_data,\ + f54->control.ctrl_num->address,\ + f54->control.ctrl_num->data,\ + sizeof(f54->control.ctrl_num->data));\ + if (retval < 0) {\ + dev_err(rmi4_data->pdev->dev.parent,\ + "%s: Failed to disable CBC (" #ctrl_num ")\n",\ + __func__);\ + return retval;\ + } \ +} while (0) + +enum f54_report_types { + F54_8BIT_IMAGE = 1, + F54_16BIT_IMAGE = 2, + F54_RAW_16BIT_IMAGE = 3, + F54_HIGH_RESISTANCE = 4, + F54_TX_TO_TX_SHORTS = 5, + F54_RX_TO_RX_SHORTS_1 = 7, + F54_TRUE_BASELINE = 9, + F54_FULL_RAW_CAP_MIN_MAX = 13, + F54_RX_OPENS_1 = 14, + F54_TX_OPENS = 15, + F54_TX_TO_GND_SHORTS = 16, + F54_RX_TO_RX_SHORTS_2 = 17, + F54_RX_OPENS_2 = 18, + F54_FULL_RAW_CAP = 19, + F54_FULL_RAW_CAP_NO_RX_COUPLING = 20, + F54_SENSOR_SPEED = 22, + F54_ADC_RANGE = 23, + F54_TRX_OPENS = 24, + F54_TRX_TO_GND_SHORTS = 25, + F54_TRX_SHORTS = 26, + F54_ABS_RAW_CAP = 38, + F54_ABS_DELTA_CAP = 40, + F54_ABS_HYBRID_DELTA_CAP = 59, + F54_ABS_HYBRID_RAW_CAP = 63, + F54_AMP_FULL_RAW_CAP = 78, + F54_AMP_RAW_ADC = 83, + F54_FULL_RAW_CAP_TDDI = 92, + INVALID_REPORT_TYPE = -1, +}; + +enum f54_afe_cal { + F54_AFE_CAL, + F54_AFE_IS_CAL, +}; + +struct f54_query { + union { + struct { + /* query 0 */ + unsigned char num_of_rx_electrodes; + + /* query 1 */ + unsigned char num_of_tx_electrodes; + + /* query 2 */ + unsigned char f54_query2_b0__1:2; + unsigned char has_baseline:1; + unsigned char has_image8:1; + unsigned char f54_query2_b4__5:2; + unsigned char has_image16:1; + unsigned char f54_query2_b7:1; + + /* queries 3.0 and 3.1 */ + unsigned short clock_rate; + + /* query 4 */ + unsigned char touch_controller_family; + + /* query 5 */ + unsigned char has_pixel_touch_threshold_adjustment:1; + unsigned char f54_query5_b1__7:7; + + /* query 6 */ + unsigned char has_sensor_assignment:1; + unsigned char has_interference_metric:1; + unsigned char has_sense_frequency_control:1; + unsigned char has_firmware_noise_mitigation:1; + unsigned char has_ctrl11:1; + unsigned char has_two_byte_report_rate:1; + unsigned char has_one_byte_report_rate:1; + unsigned char has_relaxation_control:1; + + /* query 7 */ + unsigned char curve_compensation_mode:2; + unsigned char f54_query7_b2__7:6; + + /* query 8 */ + unsigned char f54_query8_b0:1; + unsigned char has_iir_filter:1; + unsigned char has_cmn_removal:1; + unsigned char has_cmn_maximum:1; + unsigned char has_touch_hysteresis:1; + unsigned char has_edge_compensation:1; + unsigned char has_per_frequency_noise_control:1; + unsigned char has_enhanced_stretch:1; + + /* query 9 */ + unsigned char has_force_fast_relaxation:1; + unsigned char has_multi_metric_state_machine:1; + unsigned char has_signal_clarity:1; + unsigned char has_variance_metric:1; + unsigned char has_0d_relaxation_control:1; + unsigned char has_0d_acquisition_control:1; + unsigned char has_status:1; + unsigned char has_slew_metric:1; + + /* query 10 */ + unsigned char has_h_blank:1; + unsigned char has_v_blank:1; + unsigned char has_long_h_blank:1; + unsigned char has_startup_fast_relaxation:1; + unsigned char has_esd_control:1; + unsigned char has_noise_mitigation2:1; + unsigned char has_noise_state:1; + unsigned char has_energy_ratio_relaxation:1; + + /* query 11 */ + unsigned char has_excessive_noise_reporting:1; + unsigned char has_slew_option:1; + unsigned char has_two_overhead_bursts:1; + unsigned char has_query13:1; + unsigned char has_one_overhead_burst:1; + unsigned char f54_query11_b5:1; + unsigned char has_ctrl88:1; + unsigned char has_query15:1; + + /* query 12 */ + unsigned char number_of_sensing_frequencies:4; + unsigned char f54_query12_b4__7:4; + } __packed; + unsigned char data[14]; + }; +}; + +struct f54_query_13 { + union { + struct { + unsigned char has_ctrl86:1; + unsigned char has_ctrl87:1; + unsigned char has_ctrl87_sub0:1; + unsigned char has_ctrl87_sub1:1; + unsigned char has_ctrl87_sub2:1; + unsigned char has_cidim:1; + unsigned char has_noise_mitigation_enhancement:1; + unsigned char has_rail_im:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_15 { + union { + struct { + unsigned char has_ctrl90:1; + unsigned char has_transmit_strength:1; + unsigned char has_ctrl87_sub3:1; + unsigned char has_query16:1; + unsigned char has_query20:1; + unsigned char has_query21:1; + unsigned char has_query22:1; + unsigned char has_query25:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_16 { + union { + struct { + unsigned char has_query17:1; + unsigned char has_data17:1; + unsigned char has_ctrl92:1; + unsigned char has_ctrl93:1; + unsigned char has_ctrl94_query18:1; + unsigned char has_ctrl95_query19:1; + unsigned char has_ctrl99:1; + unsigned char has_ctrl100:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_21 { + union { + struct { + unsigned char has_abs_rx:1; + unsigned char has_abs_tx:1; + unsigned char has_ctrl91:1; + unsigned char has_ctrl96:1; + unsigned char has_ctrl97:1; + unsigned char has_ctrl98:1; + unsigned char has_data19:1; + unsigned char has_query24_data18:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_22 { + union { + struct { + unsigned char has_packed_image:1; + unsigned char has_ctrl101:1; + unsigned char has_dynamic_sense_display_ratio:1; + unsigned char has_query23:1; + unsigned char has_ctrl103_query26:1; + unsigned char has_ctrl104:1; + unsigned char has_ctrl105:1; + unsigned char has_query28:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_23 { + union { + struct { + unsigned char has_ctrl102:1; + unsigned char has_ctrl102_sub1:1; + unsigned char has_ctrl102_sub2:1; + unsigned char has_ctrl102_sub4:1; + unsigned char has_ctrl102_sub5:1; + unsigned char has_ctrl102_sub9:1; + unsigned char has_ctrl102_sub10:1; + unsigned char has_ctrl102_sub11:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_25 { + union { + struct { + unsigned char has_ctrl106:1; + unsigned char has_ctrl102_sub12:1; + unsigned char has_ctrl107:1; + unsigned char has_ctrl108:1; + unsigned char has_ctrl109:1; + unsigned char has_data20:1; + unsigned char f54_query25_b6:1; + unsigned char has_query27:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_27 { + union { + struct { + unsigned char has_ctrl110:1; + unsigned char has_data21:1; + unsigned char has_ctrl111:1; + unsigned char has_ctrl112:1; + unsigned char has_ctrl113:1; + unsigned char has_data22:1; + unsigned char has_ctrl114:1; + unsigned char has_query29:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_29 { + union { + struct { + unsigned char has_ctrl115:1; + unsigned char has_ground_ring_options:1; + unsigned char has_lost_bursts_tuning:1; + unsigned char has_aux_exvcom2_select:1; + unsigned char has_ctrl116:1; + unsigned char has_data23:1; + unsigned char has_ctrl117:1; + unsigned char has_query30:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_30 { + union { + struct { + unsigned char has_ctrl118:1; + unsigned char has_ctrl119:1; + unsigned char has_ctrl120:1; + unsigned char has_ctrl121:1; + unsigned char has_ctrl122_query31:1; + unsigned char has_ctrl123:1; + unsigned char has_ctrl124:1; + unsigned char has_query32:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_32 { + union { + struct { + unsigned char has_ctrl125:1; + unsigned char has_ctrl126:1; + unsigned char has_ctrl127:1; + unsigned char has_abs_charge_pump_disable:1; + unsigned char has_query33:1; + unsigned char has_data24:1; + unsigned char has_query34:1; + unsigned char has_query35:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_33 { + union { + struct { + unsigned char has_ctrl128:1; + unsigned char has_ctrl129:1; + unsigned char has_ctrl130:1; + unsigned char has_ctrl131:1; + unsigned char has_ctrl132:1; + unsigned char has_ctrl133:1; + unsigned char has_ctrl134:1; + unsigned char has_query36:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_35 { + union { + struct { + unsigned char has_data25:1; + unsigned char has_ctrl135:1; + unsigned char has_ctrl136:1; + unsigned char has_ctrl137:1; + unsigned char has_ctrl138:1; + unsigned char has_ctrl139:1; + unsigned char has_data26:1; + unsigned char has_ctrl140:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_36 { + union { + struct { + unsigned char has_ctrl141:1; + unsigned char has_ctrl142:1; + unsigned char has_query37:1; + unsigned char has_ctrl143:1; + unsigned char has_ctrl144:1; + unsigned char has_ctrl145:1; + unsigned char has_ctrl146:1; + unsigned char has_query38:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_38 { + union { + struct { + unsigned char has_ctrl147:1; + unsigned char has_ctrl148:1; + unsigned char has_ctrl149:1; + unsigned char has_ctrl150:1; + unsigned char has_ctrl151:1; + unsigned char has_ctrl152:1; + unsigned char has_ctrl153:1; + unsigned char has_query39:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_39 { + union { + struct { + unsigned char has_ctrl154:1; + unsigned char has_ctrl155:1; + unsigned char has_ctrl156:1; + unsigned char has_ctrl160:1; + unsigned char has_ctrl157_ctrl158:1; + unsigned char f54_query39_b5__6:2; + unsigned char has_query40:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_40 { + union { + struct { + unsigned char has_ctrl169:1; + unsigned char has_ctrl163_query41:1; + unsigned char f54_query40_b2:1; + unsigned char has_ctrl165_query42:1; + unsigned char has_ctrl166:1; + unsigned char has_ctrl167:1; + unsigned char has_ctrl168:1; + unsigned char has_query43:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_43 { + union { + struct { + unsigned char f54_query43_b0__1:2; + unsigned char has_ctrl171:1; + unsigned char has_ctrl172_query44_query45:1; + unsigned char has_ctrl173:1; + unsigned char has_ctrl174:1; + unsigned char has_ctrl175:1; + unsigned char has_query46:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_46 { + union { + struct { + unsigned char has_ctrl176:1; + unsigned char has_ctrl177_ctrl178:1; + unsigned char has_ctrl179:1; + unsigned char f54_query46_b3:1; + unsigned char has_data27:1; + unsigned char has_data28:1; + unsigned char f54_query46_b6:1; + unsigned char has_query47:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_47 { + union { + struct { + unsigned char f54_query47_b0:1; + unsigned char has_ctrl182:1; + unsigned char has_ctrl183:1; + unsigned char f54_query47_b3:1; + unsigned char has_ctrl185:1; + unsigned char has_ctrl186:1; + unsigned char has_ctrl187:1; + unsigned char has_query49:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_49 { + union { + struct { + unsigned char f54_query49_b0__1:2; + unsigned char has_ctrl188:1; + unsigned char has_data31:1; + unsigned char f54_query49_b4__6:3; + unsigned char has_query50:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_50 { + union { + struct { + unsigned char f54_query50_b0__6:7; + unsigned char has_query51:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_51 { + union { + struct { + unsigned char f54_query51_b0__4:5; + unsigned char has_query53_query54_ctrl198:1; + unsigned char has_ctrl199:1; + unsigned char has_query55:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_55 { + union { + struct { + unsigned char has_query56:1; + unsigned char has_data33_data34:1; + unsigned char has_alt_report_rate:1; + unsigned char has_ctrl200:1; + unsigned char has_ctrl201_ctrl202:1; + unsigned char has_ctrl203:1; + unsigned char has_ctrl204:1; + unsigned char has_query57:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_57 { + union { + struct { + unsigned char has_ctrl205:1; + unsigned char has_ctrl206:1; + unsigned char has_usb_bulk_read:1; + unsigned char has_ctrl207:1; + unsigned char has_ctrl208:1; + unsigned char has_ctrl209:1; + unsigned char has_ctrl210:1; + unsigned char has_query58:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_58 { + union { + struct { + unsigned char has_query59:1; + unsigned char has_query60:1; + unsigned char has_ctrl211:1; + unsigned char has_ctrl212:1; + unsigned char has_hybrid_abs_tx_axis_filtering:1; + unsigned char has_hybrid_abs_tx_interpolation:1; + unsigned char has_ctrl213:1; + unsigned char has_query61:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_61 { + union { + struct { + unsigned char has_ctrl214:1; + unsigned char has_ctrl215_query62_query63:1; + unsigned char f54_query_61_b2:1; + unsigned char has_ctrl216:1; + unsigned char has_ctrl217:1; + unsigned char has_misc_host_ctrl:1; + unsigned char hybrid_abs_buttons:1; + unsigned char has_query64:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_64 { + union { + struct { + unsigned char has_ctrl101_sub1:1; + unsigned char has_ctrl220:1; + unsigned char has_ctrl221:1; + unsigned char has_ctrl222:1; + unsigned char has_ctrl219_sub1:1; + unsigned char has_ctrl103_sub3:1; + unsigned char has_ctrl224_ctrl226_ctrl227:1; + unsigned char has_query65:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_65 { + union { + struct { + unsigned char f54_query_65_b0__1:2; + unsigned char has_ctrl101_sub2:1; + unsigned char f54_query_65_b3__4:2; + unsigned char has_query66_ctrl231:1; + unsigned char has_ctrl232:1; + unsigned char has_query67:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_67 { + union { + struct { + unsigned char has_abs_doze_spatial_filter_en:1; + unsigned char has_abs_doze_avg_filter_enhancement_en:1; + unsigned char has_single_display_pulse:1; + unsigned char f54_query_67_b3__4:2; + unsigned char has_ctrl235_ctrl236:1; + unsigned char f54_query_67_b6:1; + unsigned char has_query68:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_68 { + union { + struct { + unsigned char f54_query_68_b0:1; + unsigned char has_ctrl238:1; + unsigned char has_ctrl238_sub1:1; + unsigned char has_ctrl238_sub2:1; + unsigned char has_ctrl239:1; + unsigned char has_freq_filter_bw_ext:1; + unsigned char is_tddi_hic:1; + unsigned char has_query69:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_69 { + union { + struct { + unsigned char has_ctrl240_sub0:1; + unsigned char has_ctrl240_sub1_sub2:1; + unsigned char has_ctrl240_sub3:1; + unsigned char has_ctrl240_sub4:1; + unsigned char f54_query_69_b4__7:4; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_data_31 { + union { + struct { + unsigned char is_calibration_crc:1; + unsigned char calibration_crc:1; + unsigned char short_test_row_number:5; + } __packed; + struct { + unsigned char data[1]; + unsigned short address; + } __packed; + }; +}; + +struct f54_control_7 { + union { + struct { + unsigned char cbc_cap:3; + unsigned char cbc_polarity:1; + unsigned char cbc_tx_carrier_selection:1; + unsigned char f54_ctrl7_b5__7:3; + } __packed; + struct { + unsigned char data[1]; + unsigned short address; + } __packed; + }; +}; + +struct f54_control_41 { + union { + struct { + unsigned char no_signal_clarity:1; + unsigned char f54_ctrl41_b1__7:7; + } __packed; + struct { + unsigned char data[1]; + unsigned short address; + } __packed; + }; +}; + +struct f54_control_57 { + union { + struct { + unsigned char cbc_cap:3; + unsigned char cbc_polarity:1; + unsigned char cbc_tx_carrier_selection:1; + unsigned char f54_ctrl57_b5__7:3; + } __packed; + struct { + unsigned char data[1]; + unsigned short address; + } __packed; + }; +}; + +struct f54_control_86 { + union { + struct { + unsigned char enable_high_noise_state:1; + unsigned char dynamic_sense_display_ratio:2; + unsigned char f54_ctrl86_b3__7:5; + } __packed; + struct { + unsigned char data[1]; + unsigned short address; + } __packed; + }; +}; + +struct f54_control_88 { + union { + struct { + unsigned char tx_low_reference_polarity:1; + unsigned char tx_high_reference_polarity:1; + unsigned char abs_low_reference_polarity:1; + unsigned char abs_polarity:1; + unsigned char cbc_polarity:1; + unsigned char cbc_tx_carrier_selection:1; + unsigned char charge_pump_enable:1; + unsigned char cbc_abs_auto_servo:1; + } __packed; + struct { + unsigned char data[1]; + unsigned short address; + } __packed; + }; +}; + +struct f54_control_110 { + union { + struct { + unsigned char active_stylus_rx_feedback_cap; + unsigned char active_stylus_rx_feedback_cap_reference; + unsigned char active_stylus_low_reference; + unsigned char active_stylus_high_reference; + unsigned char active_stylus_gain_control; + unsigned char active_stylus_gain_control_reference; + unsigned char active_stylus_timing_mode; + unsigned char active_stylus_discovery_bursts; + unsigned char active_stylus_detection_bursts; + unsigned char active_stylus_discovery_noise_multiplier; + unsigned char active_stylus_detection_envelope_min; + unsigned char active_stylus_detection_envelope_max; + unsigned char active_stylus_lose_count; + } __packed; + struct { + unsigned char data[13]; + unsigned short address; + } __packed; + }; +}; + +struct f54_control_149 { + union { + struct { + unsigned char trans_cbc_global_cap_enable:1; + unsigned char f54_ctrl149_b1__7:7; + } __packed; + struct { + unsigned char data[1]; + unsigned short address; + } __packed; + }; +}; + +struct f54_control_188 { + union { + struct { + unsigned char start_calibration:1; + unsigned char start_is_calibration:1; + unsigned char frequency:2; + unsigned char start_production_test:1; + unsigned char short_test_calibration:1; + unsigned char f54_ctrl188_b7:1; + } __packed; + struct { + unsigned char data[1]; + unsigned short address; + } __packed; + }; +}; + +struct f54_control { + struct f54_control_7 *reg_7; + struct f54_control_41 *reg_41; + struct f54_control_57 *reg_57; + struct f54_control_86 *reg_86; + struct f54_control_88 *reg_88; + struct f54_control_110 *reg_110; + struct f54_control_149 *reg_149; + struct f54_control_188 *reg_188; +}; + +struct synaptics_rmi4_f54_handle { + bool no_auto_cal; + bool skip_preparation; + unsigned char status; + unsigned char intr_mask; + unsigned char intr_reg_num; + unsigned char tx_assigned; + unsigned char rx_assigned; + unsigned char *report_data; + unsigned short query_base_addr; + unsigned short control_base_addr; + unsigned short data_base_addr; + unsigned short command_base_addr; + unsigned short fifoindex; + unsigned int report_size; + unsigned int data_buffer_size; + unsigned int data_pos; + enum f54_report_types report_type; + struct f54_query query; + struct f54_query_13 query_13; + struct f54_query_15 query_15; + struct f54_query_16 query_16; + struct f54_query_21 query_21; + struct f54_query_22 query_22; + struct f54_query_23 query_23; + struct f54_query_25 query_25; + struct f54_query_27 query_27; + struct f54_query_29 query_29; + struct f54_query_30 query_30; + struct f54_query_32 query_32; + struct f54_query_33 query_33; + struct f54_query_35 query_35; + struct f54_query_36 query_36; + struct f54_query_38 query_38; + struct f54_query_39 query_39; + struct f54_query_40 query_40; + struct f54_query_43 query_43; + struct f54_query_46 query_46; + struct f54_query_47 query_47; + struct f54_query_49 query_49; + struct f54_query_50 query_50; + struct f54_query_51 query_51; + struct f54_query_55 query_55; + struct f54_query_57 query_57; + struct f54_query_58 query_58; + struct f54_query_61 query_61; + struct f54_query_64 query_64; + struct f54_query_65 query_65; + struct f54_query_67 query_67; + struct f54_query_68 query_68; + struct f54_query_69 query_69; + struct f54_data_31 data_31; + struct f54_control control; + struct mutex status_mutex; + struct kobject *sysfs_dir; + struct hrtimer watchdog; + struct work_struct timeout_work; + struct work_struct test_report_work; + struct workqueue_struct *test_report_workqueue; + struct synaptics_rmi4_data *rmi4_data; +}; + +struct f55_query { + union { + struct { + /* query 0 */ + unsigned char num_of_rx_electrodes; + + /* query 1 */ + unsigned char num_of_tx_electrodes; + + /* query 2 */ + unsigned char has_sensor_assignment:1; + unsigned char has_edge_compensation:1; + unsigned char curve_compensation_mode:2; + unsigned char has_ctrl6:1; + unsigned char has_alternate_transmitter_assignment:1; + unsigned char has_single_layer_multi_touch:1; + unsigned char has_query5:1; + } __packed; + unsigned char data[3]; + }; +}; + +struct f55_query_3 { + union { + struct { + unsigned char has_ctrl8:1; + unsigned char has_ctrl9:1; + unsigned char has_oncell_pattern_support:1; + unsigned char has_data0:1; + unsigned char has_single_wide_pattern_support:1; + unsigned char has_mirrored_tx_pattern_support:1; + unsigned char has_discrete_pattern_support:1; + unsigned char has_query9:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f55_query_5 { + union { + struct { + unsigned char has_corner_compensation:1; + unsigned char has_ctrl12:1; + unsigned char has_trx_configuration:1; + unsigned char has_ctrl13:1; + unsigned char f55_query5_b4:1; + unsigned char has_ctrl14:1; + unsigned char has_basis_function:1; + unsigned char has_query17:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f55_query_17 { + union { + struct { + unsigned char f55_query17_b0:1; + unsigned char has_ctrl16:1; + unsigned char has_ctrl18_ctrl19:1; + unsigned char has_ctrl17:1; + unsigned char has_ctrl20:1; + unsigned char has_ctrl21:1; + unsigned char has_ctrl22:1; + unsigned char has_query18:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f55_query_18 { + union { + struct { + unsigned char has_ctrl23:1; + unsigned char has_ctrl24:1; + unsigned char has_query19:1; + unsigned char has_ctrl25:1; + unsigned char has_ctrl26:1; + unsigned char has_ctrl27_query20:1; + unsigned char has_ctrl28_query21:1; + unsigned char has_query22:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f55_query_22 { + union { + struct { + unsigned char has_ctrl29:1; + unsigned char has_query23:1; + unsigned char has_guard_disable:1; + unsigned char has_ctrl30:1; + unsigned char has_ctrl31:1; + unsigned char has_ctrl32:1; + unsigned char has_query24_through_query27:1; + unsigned char has_query28:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f55_query_23 { + union { + struct { + unsigned char amp_sensor_enabled:1; + unsigned char image_transposed:1; + unsigned char first_column_at_left_side:1; + unsigned char size_of_column2mux:5; + } __packed; + unsigned char data[1]; + }; +}; + +struct f55_query_28 { + union { + struct { + unsigned char f55_query28_b0__4:5; + unsigned char has_ctrl37:1; + unsigned char has_query29:1; + unsigned char has_query30:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f55_query_30 { + union { + struct { + unsigned char has_ctrl38:1; + unsigned char has_query31_query32:1; + unsigned char has_ctrl39:1; + unsigned char has_ctrl40:1; + unsigned char has_ctrl41:1; + unsigned char has_ctrl42:1; + unsigned char has_ctrl43_ctrl44:1; + unsigned char has_query33:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f55_query_33 { + union { + struct { + unsigned char has_extended_amp_pad:1; + unsigned char has_extended_amp_btn:1; + unsigned char has_ctrl45_ctrl46:1; + unsigned char f55_query33_b3:1; + unsigned char has_ctrl47_sub0_sub1:1; + unsigned char f55_query33_b5__7:3; + } __packed; + unsigned char data[1]; + }; +}; + +struct f55_control_43 { + union { + struct { + unsigned char swap_sensor_side:1; + unsigned char f55_ctrl43_b1__7:7; + unsigned char afe_l_mux_size:4; + unsigned char afe_r_mux_size:4; + } __packed; + unsigned char data[2]; + }; +}; + +struct synaptics_rmi4_f55_handle { + bool amp_sensor; + bool extended_amp; + bool has_force; + unsigned char size_of_column2mux; + unsigned char afe_mux_offset; + unsigned char force_tx_offset; + unsigned char force_rx_offset; + unsigned char *tx_assignment; + unsigned char *rx_assignment; + unsigned char *force_tx_assignment; + unsigned char *force_rx_assignment; + unsigned short query_base_addr; + unsigned short control_base_addr; + unsigned short data_base_addr; + unsigned short command_base_addr; + struct f55_query query; + struct f55_query_3 query_3; + struct f55_query_5 query_5; + struct f55_query_17 query_17; + struct f55_query_18 query_18; + struct f55_query_22 query_22; + struct f55_query_23 query_23; + struct f55_query_28 query_28; + struct f55_query_30 query_30; + struct f55_query_33 query_33; +}; + +struct f21_query_2 { + union { + struct { + unsigned char size_of_query3; + struct { + unsigned char query0_is_present:1; + unsigned char query1_is_present:1; + unsigned char query2_is_present:1; + unsigned char query3_is_present:1; + unsigned char query4_is_present:1; + unsigned char query5_is_present:1; + unsigned char query6_is_present:1; + unsigned char query7_is_present:1; + } __packed; + struct { + unsigned char query8_is_present:1; + unsigned char query9_is_present:1; + unsigned char query10_is_present:1; + unsigned char query11_is_present:1; + unsigned char query12_is_present:1; + unsigned char query13_is_present:1; + unsigned char query14_is_present:1; + unsigned char query15_is_present:1; + } __packed; + }; + unsigned char data[3]; + }; +}; + +struct f21_query_5 { + union { + struct { + unsigned char size_of_query6; + struct { + unsigned char ctrl0_is_present:1; + unsigned char ctrl1_is_present:1; + unsigned char ctrl2_is_present:1; + unsigned char ctrl3_is_present:1; + unsigned char ctrl4_is_present:1; + unsigned char ctrl5_is_present:1; + unsigned char ctrl6_is_present:1; + unsigned char ctrl7_is_present:1; + } __packed; + struct { + unsigned char ctrl8_is_present:1; + unsigned char ctrl9_is_present:1; + unsigned char ctrl10_is_present:1; + unsigned char ctrl11_is_present:1; + unsigned char ctrl12_is_present:1; + unsigned char ctrl13_is_present:1; + unsigned char ctrl14_is_present:1; + unsigned char ctrl15_is_present:1; + } __packed; + struct { + unsigned char ctrl16_is_present:1; + unsigned char ctrl17_is_present:1; + unsigned char ctrl18_is_present:1; + unsigned char ctrl19_is_present:1; + unsigned char ctrl20_is_present:1; + unsigned char ctrl21_is_present:1; + unsigned char ctrl22_is_present:1; + unsigned char ctrl23_is_present:1; + } __packed; + }; + unsigned char data[4]; + }; +}; + +struct f21_query_11 { + union { + struct { + unsigned char has_high_resolution_force:1; + unsigned char has_force_sensing_txrx_mapping:1; + unsigned char f21_query11_00_b2__7:6; + unsigned char f21_query11_00_reserved; + unsigned char max_number_of_force_sensors; + unsigned char max_number_of_force_txs; + unsigned char max_number_of_force_rxs; + unsigned char f21_query11_01_reserved; + } __packed; + unsigned char data[6]; + }; +}; + +struct synaptics_rmi4_f21_handle { + bool has_force; + unsigned char tx_assigned; + unsigned char rx_assigned; + unsigned char max_num_of_tx; + unsigned char max_num_of_rx; + unsigned char max_num_of_txrx; + unsigned char *force_txrx_assignment; + unsigned short query_base_addr; + unsigned short control_base_addr; + unsigned short data_base_addr; + unsigned short command_base_addr; +}; + +show_prototype(num_of_mapped_tx); +show_prototype(num_of_mapped_rx); +show_prototype(tx_mapping); +show_prototype(rx_mapping); +show_prototype(num_of_mapped_force_tx); +show_prototype(num_of_mapped_force_rx); +show_prototype(force_tx_mapping); +show_prototype(force_rx_mapping); +show_prototype(report_size); +show_prototype(status); + +store_prototype(do_preparation); +store_prototype(force_cal); +store_prototype(get_report); +store_prototype(resume_touch); +store_prototype(do_afe_calibration); + +show_store_prototype(report_type); +show_store_prototype(fifoindex); +show_store_prototype(no_auto_cal); +show_store_prototype(read_report); + +static struct attribute *attrs[] = { + attrify(num_of_mapped_tx), + attrify(num_of_mapped_rx), + attrify(tx_mapping), + attrify(rx_mapping), + attrify(num_of_mapped_force_tx), + attrify(num_of_mapped_force_rx), + attrify(force_tx_mapping), + attrify(force_rx_mapping), + attrify(report_size), + attrify(status), + attrify(do_preparation), + attrify(force_cal), + attrify(get_report), + attrify(resume_touch), + attrify(do_afe_calibration), + attrify(report_type), + attrify(fifoindex), + attrify(no_auto_cal), + attrify(read_report), + NULL, +}; + +static struct attribute_group attr_group = { + .attrs = attrs, +}; + +static ssize_t test_sysfs_data_read(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static struct bin_attribute test_report_data = { + .attr = { + .name = "report_data", + .mode = 0444, + }, + .size = 0, + .read = test_sysfs_data_read, +}; + +static struct synaptics_rmi4_f54_handle *f54; +static struct synaptics_rmi4_f55_handle *f55; +static struct synaptics_rmi4_f21_handle *f21; + +DECLARE_COMPLETION(test_remove_complete); + +static bool test_report_type_valid(enum f54_report_types report_type) +{ + switch (report_type) { + case F54_8BIT_IMAGE: + case F54_16BIT_IMAGE: + case F54_RAW_16BIT_IMAGE: + case F54_HIGH_RESISTANCE: + case F54_TX_TO_TX_SHORTS: + case F54_RX_TO_RX_SHORTS_1: + case F54_TRUE_BASELINE: + case F54_FULL_RAW_CAP_MIN_MAX: + case F54_RX_OPENS_1: + case F54_TX_OPENS: + case F54_TX_TO_GND_SHORTS: + case F54_RX_TO_RX_SHORTS_2: + case F54_RX_OPENS_2: + case F54_FULL_RAW_CAP: + case F54_FULL_RAW_CAP_NO_RX_COUPLING: + case F54_SENSOR_SPEED: + case F54_ADC_RANGE: + case F54_TRX_OPENS: + case F54_TRX_TO_GND_SHORTS: + case F54_TRX_SHORTS: + case F54_ABS_RAW_CAP: + case F54_ABS_DELTA_CAP: + case F54_ABS_HYBRID_DELTA_CAP: + case F54_ABS_HYBRID_RAW_CAP: + case F54_AMP_FULL_RAW_CAP: + case F54_AMP_RAW_ADC: + case F54_FULL_RAW_CAP_TDDI: + return true; + default: + f54->report_type = INVALID_REPORT_TYPE; + f54->report_size = 0; + return false; + } +} + +static void test_set_report_size(void) +{ + int retval; + unsigned char tx = f54->tx_assigned; + unsigned char rx = f54->rx_assigned; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + switch (f54->report_type) { + case F54_8BIT_IMAGE: + f54->report_size = tx * rx; + break; + case F54_16BIT_IMAGE: + case F54_RAW_16BIT_IMAGE: + case F54_TRUE_BASELINE: + case F54_FULL_RAW_CAP: + case F54_FULL_RAW_CAP_NO_RX_COUPLING: + case F54_SENSOR_SPEED: + case F54_AMP_FULL_RAW_CAP: + case F54_AMP_RAW_ADC: + case F54_FULL_RAW_CAP_TDDI: + f54->report_size = 2 * tx * rx; + break; + case F54_HIGH_RESISTANCE: + f54->report_size = HIGH_RESISTANCE_DATA_SIZE; + break; + case F54_TX_TO_TX_SHORTS: + case F54_TX_OPENS: + case F54_TX_TO_GND_SHORTS: + f54->report_size = (tx + 7) / 8; + break; + case F54_RX_TO_RX_SHORTS_1: + case F54_RX_OPENS_1: + if (rx < tx) + f54->report_size = 2 * rx * rx; + else + f54->report_size = 2 * tx * rx; + break; + case F54_FULL_RAW_CAP_MIN_MAX: + f54->report_size = FULL_RAW_CAP_MIN_MAX_DATA_SIZE; + break; + case F54_RX_TO_RX_SHORTS_2: + case F54_RX_OPENS_2: + if (rx <= tx) + f54->report_size = 0; + else + f54->report_size = 2 * rx * (rx - tx); + break; + case F54_ADC_RANGE: + if (f54->query.has_signal_clarity) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->control.reg_41->address, + f54->control.reg_41->data, + sizeof(f54->control.reg_41->data)); + if (retval < 0) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Failed to read control reg_41\n", + __func__); + f54->report_size = 0; + break; + } + if (!f54->control.reg_41->no_signal_clarity) { + if (tx % 4) + tx += 4 - (tx % 4); + } + } + f54->report_size = 2 * tx * rx; + break; + case F54_TRX_OPENS: + case F54_TRX_TO_GND_SHORTS: + case F54_TRX_SHORTS: + f54->report_size = TRX_OPEN_SHORT_DATA_SIZE; + break; + case F54_ABS_RAW_CAP: + case F54_ABS_DELTA_CAP: + case F54_ABS_HYBRID_DELTA_CAP: + case F54_ABS_HYBRID_RAW_CAP: + tx += f21->tx_assigned; + rx += f21->rx_assigned; + f54->report_size = 4 * (tx + rx); + break; + default: + f54->report_size = 0; + } +} + +static int test_set_interrupt(bool set) +{ + int retval; + unsigned char ii; + unsigned char zero = 0x00; + unsigned char *intr_mask; + unsigned short f01_ctrl_reg; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + intr_mask = rmi4_data->intr_mask; + f01_ctrl_reg = rmi4_data->f01_ctrl_base_addr + 1 + f54->intr_reg_num; + + if (!set) { + retval = synaptics_rmi4_reg_write(rmi4_data, + f01_ctrl_reg, + &zero, + sizeof(zero)); + if (retval < 0) + return retval; + } + + for (ii = 0; ii < rmi4_data->num_of_intr_regs; ii++) { + if (intr_mask[ii] != 0x00) { + f01_ctrl_reg = rmi4_data->f01_ctrl_base_addr + 1 + ii; + if (set) { + retval = synaptics_rmi4_reg_write(rmi4_data, + f01_ctrl_reg, + &zero, + sizeof(zero)); + if (retval < 0) + return retval; + } else { + retval = synaptics_rmi4_reg_write(rmi4_data, + f01_ctrl_reg, + &(intr_mask[ii]), + sizeof(intr_mask[ii])); + if (retval < 0) + return retval; + } + } + } + + f01_ctrl_reg = rmi4_data->f01_ctrl_base_addr + 1 + f54->intr_reg_num; + + if (set) { + retval = synaptics_rmi4_reg_write(rmi4_data, + f01_ctrl_reg, + &f54->intr_mask, + 1); + if (retval < 0) + return retval; + } + + return 0; +} + +static int test_wait_for_command_completion(void) +{ + int retval; + unsigned char value; + unsigned char timeout_count; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + timeout_count = 0; + do { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->command_base_addr, + &value, + sizeof(value)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read command register\n", + __func__); + return retval; + } + + if (value == 0x00) + break; + + msleep(100); + timeout_count++; + } while (timeout_count < COMMAND_TIMEOUT_100MS); + + if (timeout_count == COMMAND_TIMEOUT_100MS) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Timed out waiting for command completion\n", + __func__); + return -ETIMEDOUT; + } + + return 0; +} + +static int test_do_command(unsigned char command) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + retval = synaptics_rmi4_reg_write(rmi4_data, + f54->command_base_addr, + &command, + sizeof(command)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write command\n", + __func__); + return retval; + } + + retval = test_wait_for_command_completion(); + if (retval < 0) + return retval; + + return 0; +} + +static int test_do_preparation(void) +{ + int retval; + unsigned char value; + unsigned char zero = 0x00; + unsigned char device_ctrl; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_ctrl_base_addr, + &device_ctrl, + sizeof(device_ctrl)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set no sleep\n", + __func__); + return retval; + } + + device_ctrl |= NO_SLEEP_ON; + + retval = synaptics_rmi4_reg_write(rmi4_data, + rmi4_data->f01_ctrl_base_addr, + &device_ctrl, + sizeof(device_ctrl)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set no sleep\n", + __func__); + return retval; + } + + if (f54->skip_preparation) + return 0; + + switch (f54->report_type) { + case F54_16BIT_IMAGE: + case F54_RAW_16BIT_IMAGE: + case F54_SENSOR_SPEED: + case F54_ADC_RANGE: + case F54_ABS_RAW_CAP: + case F54_ABS_DELTA_CAP: + case F54_ABS_HYBRID_DELTA_CAP: + case F54_ABS_HYBRID_RAW_CAP: + case F54_FULL_RAW_CAP_TDDI: + break; + case F54_AMP_RAW_ADC: + if (f54->query_49.has_ctrl188) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->control.reg_188->address, + f54->control.reg_188->data, + sizeof(f54->control.reg_188->data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set start production test\n", + __func__); + return retval; + } + f54->control.reg_188->start_production_test = 1; + retval = synaptics_rmi4_reg_write(rmi4_data, + f54->control.reg_188->address, + f54->control.reg_188->data, + sizeof(f54->control.reg_188->data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set start production test\n", + __func__); + return retval; + } + } + break; + default: + if (f54->query.touch_controller_family == 1) + disable_cbc(reg_7); + else if (f54->query.has_ctrl88) + disable_cbc(reg_88); + + if (f54->query.has_0d_acquisition_control) + disable_cbc(reg_57); + + if ((f54->query.has_query15) && + (f54->query_15.has_query25) && + (f54->query_25.has_query27) && + (f54->query_27.has_query29) && + (f54->query_29.has_query30) && + (f54->query_30.has_query32) && + (f54->query_32.has_query33) && + (f54->query_33.has_query36) && + (f54->query_36.has_query38) && + (f54->query_38.has_ctrl149)) { + retval = synaptics_rmi4_reg_write(rmi4_data, + f54->control.reg_149->address, + &zero, + sizeof(f54->control.reg_149->data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to disable global CBC\n", + __func__); + return retval; + } + } + + if (f54->query.has_signal_clarity) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->control.reg_41->address, + &value, + sizeof(f54->control.reg_41->data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to disable signal clarity\n", + __func__); + return retval; + } + value |= 0x01; + retval = synaptics_rmi4_reg_write(rmi4_data, + f54->control.reg_41->address, + &value, + sizeof(f54->control.reg_41->data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to disable signal clarity\n", + __func__); + return retval; + } + } + + retval = test_do_command(COMMAND_FORCE_UPDATE); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to do force update\n", + __func__); + return retval; + } + + retval = test_do_command(COMMAND_FORCE_CAL); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to do force cal\n", + __func__); + return retval; + } + } + + return 0; +} + +static int test_do_afe_calibration(enum f54_afe_cal mode) +{ + int retval; + unsigned char timeout = CALIBRATION_TIMEOUT_S; + unsigned char timeout_count = 0; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->control.reg_188->address, + f54->control.reg_188->data, + sizeof(f54->control.reg_188->data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to start calibration\n", + __func__); + return retval; + } + + if (mode == F54_AFE_CAL) + f54->control.reg_188->start_calibration = 1; + else if (mode == F54_AFE_IS_CAL) + f54->control.reg_188->start_is_calibration = 1; + + retval = synaptics_rmi4_reg_write(rmi4_data, + f54->control.reg_188->address, + f54->control.reg_188->data, + sizeof(f54->control.reg_188->data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to start calibration\n", + __func__); + return retval; + } + + do { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->control.reg_188->address, + f54->control.reg_188->data, + sizeof(f54->control.reg_188->data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to complete calibration\n", + __func__); + return retval; + } + + if (mode == F54_AFE_CAL) { + if (!f54->control.reg_188->start_calibration) + break; + } else if (mode == F54_AFE_IS_CAL) { + if (!f54->control.reg_188->start_is_calibration) + break; + } + + if (timeout_count == timeout) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Timed out waiting for calibration completion\n", + __func__); + return -EBUSY; + } + + timeout_count++; + msleep(1000); + } while (true); + + /* check CRC */ + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->data_31.address, + f54->data_31.data, + sizeof(f54->data_31.data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read calibration CRC\n", + __func__); + return retval; + } + + if (mode == F54_AFE_CAL) { + if (f54->data_31.calibration_crc == 0) + return 0; + } else if (mode == F54_AFE_IS_CAL) { + if (f54->data_31.is_calibration_crc == 0) + return 0; + } + + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read calibration CRC\n", + __func__); + + return -EINVAL; +} + +static int test_check_for_idle_status(void) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + switch (f54->status) { + case STATUS_IDLE: + retval = 0; + break; + case STATUS_BUSY: + dev_err(rmi4_data->pdev->dev.parent, + "%s: Status busy\n", + __func__); + retval = -EINVAL; + break; + case STATUS_ERROR: + dev_err(rmi4_data->pdev->dev.parent, + "%s: Status error\n", + __func__); + retval = -EINVAL; + break; + default: + dev_err(rmi4_data->pdev->dev.parent, + "%s: Invalid status (%d)\n", + __func__, f54->status); + retval = -EINVAL; + } + + return retval; +} + +static void test_timeout_work(struct work_struct *work) +{ + int retval; + unsigned char command; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + mutex_lock(&f54->status_mutex); + + if (f54->status == STATUS_BUSY) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->command_base_addr, + &command, + sizeof(command)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read command register\n", + __func__); + } else if (command & COMMAND_GET_REPORT) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Report type not supported by FW\n", + __func__); + } else { + queue_work(f54->test_report_workqueue, + &f54->test_report_work); + goto exit; + } + f54->status = STATUS_ERROR; + f54->report_size = 0; + } + +exit: + mutex_unlock(&f54->status_mutex); +} + +static enum hrtimer_restart test_get_report_timeout(struct hrtimer *timer) +{ + schedule_work(&(f54->timeout_work)); + + return HRTIMER_NORESTART; +} + +static ssize_t num_of_mapped_tx_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", f54->tx_assigned); +} + +static ssize_t num_of_mapped_rx_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", f54->rx_assigned); +} + +static ssize_t tx_mapping_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int cnt; + int count = 0; + unsigned char ii; + unsigned char tx_num; + unsigned char tx_electrodes; + + if (!f55) + return -EINVAL; + + tx_electrodes = f55->query.num_of_tx_electrodes; + + for (ii = 0; ii < tx_electrodes; ii++) { + tx_num = f55->tx_assignment[ii]; + if (tx_num == 0xff) + cnt = snprintf(buf, PAGE_SIZE - count, "xx "); + else + cnt = snprintf(buf, PAGE_SIZE - count, "%02u ", tx_num); + buf += cnt; + count += cnt; + } + + snprintf(buf, PAGE_SIZE - count, "\n"); + count++; + + return count; +} + +static ssize_t rx_mapping_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int cnt; + int count = 0; + unsigned char ii; + unsigned char rx_num; + unsigned char rx_electrodes; + + if (!f55) + return -EINVAL; + + rx_electrodes = f55->query.num_of_rx_electrodes; + + for (ii = 0; ii < rx_electrodes; ii++) { + rx_num = f55->rx_assignment[ii]; + if (rx_num == 0xff) + cnt = snprintf(buf, PAGE_SIZE - count, "xx "); + else + cnt = snprintf(buf, PAGE_SIZE - count, "%02u ", rx_num); + buf += cnt; + count += cnt; + } + + snprintf(buf, PAGE_SIZE - count, "\n"); + count++; + + return count; +} + +static ssize_t num_of_mapped_force_tx_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", f21->tx_assigned); +} + +static ssize_t num_of_mapped_force_rx_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", f21->rx_assigned); +} + +static ssize_t force_tx_mapping_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int cnt; + int count = 0; + unsigned char ii; + unsigned char tx_num; + unsigned char tx_electrodes; + + if ((!f55 || !f55->has_force) && (!f21 || !f21->has_force)) + return -EINVAL; + + if (f55->has_force) { + tx_electrodes = f55->query.num_of_tx_electrodes; + + for (ii = 0; ii < tx_electrodes; ii++) { + tx_num = f55->force_tx_assignment[ii]; + if (tx_num == 0xff) { + cnt = snprintf(buf, PAGE_SIZE - count, "xx "); + } else { + cnt = snprintf(buf, PAGE_SIZE - count, "%02u ", + tx_num); + } + buf += cnt; + count += cnt; + } + } else if (f21->has_force) { + tx_electrodes = f21->max_num_of_tx; + + for (ii = 0; ii < tx_electrodes; ii++) { + tx_num = f21->force_txrx_assignment[ii]; + if (tx_num == 0xff) { + cnt = snprintf(buf, PAGE_SIZE - count, "xx "); + } else { + cnt = snprintf(buf, PAGE_SIZE - count, "%02u ", + tx_num); + } + buf += cnt; + count += cnt; + } + } + + snprintf(buf, PAGE_SIZE - count, "\n"); + count++; + + return count; +} + +static ssize_t force_rx_mapping_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int cnt; + int count = 0; + unsigned char ii; + unsigned char offset; + unsigned char rx_num; + unsigned char rx_electrodes; + + if ((!f55 || !f55->has_force) && (!f21 || !f21->has_force)) + return -EINVAL; + + if (f55->has_force) { + rx_electrodes = f55->query.num_of_rx_electrodes; + + for (ii = 0; ii < rx_electrodes; ii++) { + rx_num = f55->force_rx_assignment[ii]; + if (rx_num == 0xff) + cnt = snprintf(buf, PAGE_SIZE - count, "xx "); + else + cnt = snprintf(buf, PAGE_SIZE - count, "%02u ", + rx_num); + buf += cnt; + count += cnt; + } + } else if (f21->has_force) { + offset = f21->max_num_of_tx; + rx_electrodes = f21->max_num_of_rx; + + for (ii = offset; ii < (rx_electrodes + offset); ii++) { + rx_num = f21->force_txrx_assignment[ii]; + if (rx_num == 0xff) + cnt = snprintf(buf, PAGE_SIZE - count, "xx "); + else + cnt = snprintf(buf, PAGE_SIZE - count, "%02u ", + rx_num); + buf += cnt; + count += cnt; + } + } + + snprintf(buf, PAGE_SIZE - count, "\n"); + count++; + + return count; +} + +static ssize_t report_size_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", f54->report_size); +} + +static ssize_t status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + + mutex_lock(&f54->status_mutex); + + retval = snprintf(buf, PAGE_SIZE, "%u\n", f54->status); + + mutex_unlock(&f54->status_mutex); + + return retval; +} + +static ssize_t do_preparation_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned long setting; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + retval = sstrtoul(buf, 10, &setting); + if (retval) + return retval; + + if (setting != 1) + return -EINVAL; + + mutex_lock(&f54->status_mutex); + + retval = test_check_for_idle_status(); + if (retval < 0) + goto exit; + + retval = test_do_preparation(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to do preparation\n", + __func__); + goto exit; + } + + retval = count; + +exit: + mutex_unlock(&f54->status_mutex); + + return retval; +} + +static ssize_t force_cal_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned long setting; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + retval = sstrtoul(buf, 10, &setting); + if (retval) + return retval; + + if (setting != 1) + return -EINVAL; + + mutex_lock(&f54->status_mutex); + + retval = test_check_for_idle_status(); + if (retval < 0) + goto exit; + + retval = test_do_command(COMMAND_FORCE_CAL); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to do force cal\n", + __func__); + goto exit; + } + + retval = count; + +exit: + mutex_unlock(&f54->status_mutex); + + return retval; +} + +static ssize_t get_report_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned char command; + unsigned long setting; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + retval = sstrtoul(buf, 10, &setting); + if (retval) + return retval; + + if (setting != 1) + return -EINVAL; + + mutex_lock(&f54->status_mutex); + + retval = test_check_for_idle_status(); + if (retval < 0) + goto exit; + + if (!test_report_type_valid(f54->report_type)) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Invalid report type\n", + __func__); + retval = -EINVAL; + goto exit; + } + + test_set_interrupt(true); + + command = (unsigned char)COMMAND_GET_REPORT; + + retval = synaptics_rmi4_reg_write(rmi4_data, + f54->command_base_addr, + &command, + sizeof(command)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write get report command\n", + __func__); + goto exit; + } + + f54->status = STATUS_BUSY; + f54->report_size = 0; + f54->data_pos = 0; + + hrtimer_start(&f54->watchdog, + ktime_set(GET_REPORT_TIMEOUT_S, 0), + HRTIMER_MODE_REL); + + retval = count; + +exit: + mutex_unlock(&f54->status_mutex); + + return retval; +} + +static ssize_t resume_touch_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned char device_ctrl; + unsigned long setting; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + retval = sstrtoul(buf, 10, &setting); + if (retval) + return retval; + + if (setting != 1) + return -EINVAL; + + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_ctrl_base_addr, + &device_ctrl, + sizeof(device_ctrl)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to restore no sleep setting\n", + __func__); + return retval; + } + + device_ctrl = device_ctrl & ~NO_SLEEP_ON; + device_ctrl |= rmi4_data->no_sleep_setting; + + retval = synaptics_rmi4_reg_write(rmi4_data, + rmi4_data->f01_ctrl_base_addr, + &device_ctrl, + sizeof(device_ctrl)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to restore no sleep setting\n", + __func__); + return retval; + } + + test_set_interrupt(false); + + if (f54->skip_preparation) + return count; + + switch (f54->report_type) { + case F54_16BIT_IMAGE: + case F54_RAW_16BIT_IMAGE: + case F54_SENSOR_SPEED: + case F54_ADC_RANGE: + case F54_ABS_RAW_CAP: + case F54_ABS_DELTA_CAP: + case F54_ABS_HYBRID_DELTA_CAP: + case F54_ABS_HYBRID_RAW_CAP: + case F54_FULL_RAW_CAP_TDDI: + break; + case F54_AMP_RAW_ADC: + if (f54->query_49.has_ctrl188) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->control.reg_188->address, + f54->control.reg_188->data, + sizeof(f54->control.reg_188->data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set start production test\n", + __func__); + return retval; + } + f54->control.reg_188->start_production_test = 0; + retval = synaptics_rmi4_reg_write(rmi4_data, + f54->control.reg_188->address, + f54->control.reg_188->data, + sizeof(f54->control.reg_188->data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set start production test\n", + __func__); + return retval; + } + } + break; + default: + rmi4_data->reset_device(rmi4_data, false); + } + + return count; +} + +static ssize_t do_afe_calibration_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned long setting; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + retval = sstrtoul(buf, 10, &setting); + if (retval) + return retval; + + if (!f54->query_49.has_ctrl188) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: F54_ANALOG_Ctrl188 not found\n", + __func__); + return -EINVAL; + } + + if (setting == 0 || setting == 1) + retval = test_do_afe_calibration((enum f54_afe_cal)setting); + else + return -EINVAL; + + if (retval) + return retval; + else + return count; +} + +static ssize_t report_type_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", f54->report_type); +} + +static ssize_t report_type_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned char data; + unsigned long setting; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + retval = sstrtoul(buf, 10, &setting); + if (retval) + return retval; + + mutex_lock(&f54->status_mutex); + + retval = test_check_for_idle_status(); + if (retval < 0) + goto exit; + + if (!test_report_type_valid((enum f54_report_types)setting)) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Report type not supported by driver\n", + __func__); + retval = -EINVAL; + goto exit; + } + + f54->report_type = (enum f54_report_types)setting; + data = (unsigned char)setting; + retval = synaptics_rmi4_reg_write(rmi4_data, + f54->data_base_addr, + &data, + sizeof(data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write report type\n", + __func__); + goto exit; + } + + retval = count; + +exit: + mutex_unlock(&f54->status_mutex); + + return retval; +} + +static ssize_t fifoindex_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + unsigned char data[2]; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->data_base_addr + REPORT_INDEX_OFFSET, + data, + sizeof(data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read report index\n", + __func__); + return retval; + } + + batohs(&f54->fifoindex, data); + + return snprintf(buf, PAGE_SIZE, "%u\n", f54->fifoindex); +} + +static ssize_t fifoindex_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned char data[2]; + unsigned long setting; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + retval = sstrtoul(buf, 10, &setting); + if (retval) + return retval; + + f54->fifoindex = setting; + + hstoba(data, (unsigned short)setting); + + retval = synaptics_rmi4_reg_write(rmi4_data, + f54->data_base_addr + REPORT_INDEX_OFFSET, + data, + sizeof(data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write report index\n", + __func__); + return retval; + } + + return count; +} + +static ssize_t no_auto_cal_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", f54->no_auto_cal); +} + +static ssize_t no_auto_cal_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned char data; + unsigned long setting; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + retval = sstrtoul(buf, 10, &setting); + if (retval) + return retval; + + if (setting > 1) + return -EINVAL; + + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->control_base_addr, + &data, + sizeof(data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read no auto cal setting\n", + __func__); + return retval; + } + + if (setting) + data |= CONTROL_NO_AUTO_CAL; + else + data &= ~CONTROL_NO_AUTO_CAL; + + retval = synaptics_rmi4_reg_write(rmi4_data, + f54->control_base_addr, + &data, + sizeof(data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write no auto cal setting\n", + __func__); + return retval; + } + + f54->no_auto_cal = (setting == 1); + + return count; +} + +static ssize_t read_report_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned int ii; + unsigned int jj; + int cnt; + int count = 0; + int tx_num = f54->tx_assigned; + int rx_num = f54->rx_assigned; + char *report_data_8; + short *report_data_16; + int *report_data_32; + unsigned short *report_data_u16; + unsigned int *report_data_u32; + + switch (f54->report_type) { + case F54_8BIT_IMAGE: + report_data_8 = (char *)f54->report_data; + for (ii = 0; ii < f54->report_size; ii++) { + cnt = snprintf(buf, PAGE_SIZE - count, "%03d: %d\n", + ii, *report_data_8); + report_data_8++; + buf += cnt; + count += cnt; + } + break; + case F54_AMP_RAW_ADC: + report_data_u16 = (unsigned short *)f54->report_data; + cnt = snprintf(buf, PAGE_SIZE - count, "tx = %d\nrx = %d\n", + tx_num, rx_num); + buf += cnt; + count += cnt; + + for (ii = 0; ii < tx_num; ii++) { + for (jj = 0; jj < (rx_num - 1); jj++) { + cnt = snprintf(buf, PAGE_SIZE - count, "%-4d ", + *report_data_u16); + report_data_u16++; + buf += cnt; + count += cnt; + } + cnt = snprintf(buf, PAGE_SIZE - count, "%-4d\n", + *report_data_u16); + report_data_u16++; + buf += cnt; + count += cnt; + } + break; + case F54_16BIT_IMAGE: + case F54_RAW_16BIT_IMAGE: + case F54_TRUE_BASELINE: + case F54_FULL_RAW_CAP: + case F54_FULL_RAW_CAP_NO_RX_COUPLING: + case F54_SENSOR_SPEED: + case F54_AMP_FULL_RAW_CAP: + case F54_FULL_RAW_CAP_TDDI: + report_data_16 = (short *)f54->report_data; + cnt = snprintf(buf, PAGE_SIZE - count, "tx = %d\nrx = %d\n", + tx_num, rx_num); + buf += cnt; + count += cnt; + + for (ii = 0; ii < tx_num; ii++) { + for (jj = 0; jj < (rx_num - 1); jj++) { + cnt = snprintf(buf, PAGE_SIZE - count, "%-4d ", + *report_data_16); + report_data_16++; + buf += cnt; + count += cnt; + } + cnt = snprintf(buf, PAGE_SIZE - count, "%-4d\n", + *report_data_16); + report_data_16++; + buf += cnt; + count += cnt; + } + break; + case F54_HIGH_RESISTANCE: + case F54_FULL_RAW_CAP_MIN_MAX: + report_data_16 = (short *)f54->report_data; + for (ii = 0; ii < f54->report_size; ii += 2) { + cnt = snprintf(buf, PAGE_SIZE - count, "%03d: %d\n", + ii / 2, *report_data_16); + report_data_16++; + buf += cnt; + count += cnt; + } + break; + case F54_ABS_RAW_CAP: + case F54_ABS_HYBRID_RAW_CAP: + tx_num += f21->tx_assigned; + rx_num += f21->rx_assigned; + report_data_u32 = (unsigned int *)f54->report_data; + cnt = snprintf(buf, PAGE_SIZE - count, "rx "); + buf += cnt; + count += cnt; + for (ii = 0; ii < rx_num; ii++) { + cnt = snprintf(buf, PAGE_SIZE - count, " %2d", ii); + buf += cnt; + count += cnt; + } + cnt = snprintf(buf, PAGE_SIZE - count, "\n"); + buf += cnt; + count += cnt; + + cnt = snprintf(buf, PAGE_SIZE - count, " "); + buf += cnt; + count += cnt; + for (ii = 0; ii < rx_num; ii++) { + cnt = snprintf(buf, PAGE_SIZE - count, " %5u", + *report_data_u32); + report_data_u32++; + buf += cnt; + count += cnt; + } + cnt = snprintf(buf, PAGE_SIZE - count, "\n"); + buf += cnt; + count += cnt; + + cnt = snprintf(buf, PAGE_SIZE - count, "tx "); + buf += cnt; + count += cnt; + for (ii = 0; ii < tx_num; ii++) { + cnt = snprintf(buf, PAGE_SIZE - count, " %2d", ii); + buf += cnt; + count += cnt; + } + cnt = snprintf(buf, PAGE_SIZE - count, "\n"); + buf += cnt; + count += cnt; + + cnt = snprintf(buf, PAGE_SIZE - count, " "); + buf += cnt; + count += cnt; + for (ii = 0; ii < tx_num; ii++) { + cnt = snprintf(buf, PAGE_SIZE - count, " %5u", + *report_data_u32); + report_data_u32++; + buf += cnt; + count += cnt; + } + cnt = snprintf(buf, PAGE_SIZE - count, "\n"); + buf += cnt; + count += cnt; + break; + case F54_ABS_DELTA_CAP: + case F54_ABS_HYBRID_DELTA_CAP: + tx_num += f21->tx_assigned; + rx_num += f21->rx_assigned; + report_data_32 = (int *)f54->report_data; + cnt = snprintf(buf, PAGE_SIZE - count, "rx "); + buf += cnt; + count += cnt; + for (ii = 0; ii < rx_num; ii++) { + cnt = snprintf(buf, PAGE_SIZE - count, " %2d", ii); + buf += cnt; + count += cnt; + } + cnt = snprintf(buf, PAGE_SIZE - count, "\n"); + buf += cnt; + count += cnt; + + cnt = snprintf(buf, PAGE_SIZE - count, " "); + buf += cnt; + count += cnt; + for (ii = 0; ii < rx_num; ii++) { + cnt = snprintf(buf, PAGE_SIZE - count, " %5d", + *report_data_32); + report_data_32++; + buf += cnt; + count += cnt; + } + cnt = snprintf(buf, PAGE_SIZE - count, "\n"); + buf += cnt; + count += cnt; + + cnt = snprintf(buf, PAGE_SIZE - count, "tx "); + buf += cnt; + count += cnt; + for (ii = 0; ii < tx_num; ii++) { + cnt = snprintf(buf, PAGE_SIZE - count, " %2d", ii); + buf += cnt; + count += cnt; + } + cnt = snprintf(buf, PAGE_SIZE - count, "\n"); + buf += cnt; + count += cnt; + + cnt = snprintf(buf, PAGE_SIZE - count, " "); + buf += cnt; + count += cnt; + for (ii = 0; ii < tx_num; ii++) { + cnt = snprintf(buf, PAGE_SIZE - count, " %5d", + *report_data_32); + report_data_32++; + buf += cnt; + count += cnt; + } + cnt = snprintf(buf, PAGE_SIZE - count, "\n"); + buf += cnt; + count += cnt; + break; + default: + for (ii = 0; ii < f54->report_size; ii++) { + cnt = snprintf(buf, PAGE_SIZE - count, "%03d: 0x%02x\n", + ii, f54->report_data[ii]); + buf += cnt; + count += cnt; + } + } + + snprintf(buf, PAGE_SIZE - count, "\n"); + count++; + + return count; +} + +static ssize_t read_report_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned char timeout = GET_REPORT_TIMEOUT_S * 10; + unsigned char timeout_count; + const char cmd[] = {'1', 0}; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + retval = report_type_store(dev, attr, buf, count); + if (retval < 0) + goto exit; + + retval = do_preparation_store(dev, attr, cmd, 1); + if (retval < 0) + goto exit; + + retval = get_report_store(dev, attr, cmd, 1); + if (retval < 0) + goto exit; + + timeout_count = 0; + do { + if (f54->status != STATUS_BUSY) + break; + msleep(100); + timeout_count++; + } while (timeout_count < timeout); + + if ((f54->status != STATUS_IDLE) || (f54->report_size == 0)) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read report\n", + __func__); + retval = -EINVAL; + goto exit; + } + + retval = resume_touch_store(dev, attr, cmd, 1); + if (retval < 0) + goto exit; + + return count; + +exit: + rmi4_data->reset_device(rmi4_data, false); + + return retval; +} + +static ssize_t test_sysfs_data_read(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + unsigned int read_size; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + mutex_lock(&f54->status_mutex); + + retval = test_check_for_idle_status(); + if (retval < 0) + goto exit; + + if (!f54->report_data) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Report type %d data not available\n", + __func__, f54->report_type); + retval = -EINVAL; + goto exit; + } + + if ((f54->data_pos + count) > f54->report_size) + read_size = f54->report_size - f54->data_pos; + else + read_size = min_t(unsigned int, count, f54->report_size); + + retval = secure_memcpy(buf, count, f54->report_data + f54->data_pos, + f54->data_buffer_size - f54->data_pos, read_size); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy report data\n", + __func__); + goto exit; + } + f54->data_pos += read_size; + retval = read_size; + +exit: + mutex_unlock(&f54->status_mutex); + + return retval; +} + +static void test_report_work(struct work_struct *work) +{ + int retval; + unsigned char report_index[2]; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + mutex_lock(&f54->status_mutex); + + if (f54->status != STATUS_BUSY) { + retval = f54->status; + goto exit; + } + + retval = test_wait_for_command_completion(); + if (retval < 0) { + retval = STATUS_ERROR; + goto exit; + } + + test_set_report_size(); + if (f54->report_size == 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Report data size = 0\n", + __func__); + retval = STATUS_ERROR; + goto exit; + } + + if (f54->data_buffer_size < f54->report_size) { + if (f54->data_buffer_size) + kfree(f54->report_data); + f54->report_data = kzalloc(f54->report_size, GFP_KERNEL); + if (!f54->report_data) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for data buffer\n", + __func__); + f54->data_buffer_size = 0; + retval = STATUS_ERROR; + goto exit; + } + f54->data_buffer_size = f54->report_size; + } + + report_index[0] = 0; + report_index[1] = 0; + + retval = synaptics_rmi4_reg_write(rmi4_data, + f54->data_base_addr + REPORT_INDEX_OFFSET, + report_index, + sizeof(report_index)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write report data index\n", + __func__); + retval = STATUS_ERROR; + goto exit; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->data_base_addr + REPORT_DATA_OFFSET, + f54->report_data, + f54->report_size); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read report data\n", + __func__); + retval = STATUS_ERROR; + goto exit; + } + + retval = STATUS_IDLE; + +exit: + mutex_unlock(&f54->status_mutex); + + if (retval == STATUS_ERROR) + f54->report_size = 0; + + f54->status = retval; +} + +static void test_remove_sysfs(void) +{ + sysfs_remove_group(f54->sysfs_dir, &attr_group); + sysfs_remove_bin_file(f54->sysfs_dir, &test_report_data); + kobject_put(f54->sysfs_dir); +} + +static int test_set_sysfs(void) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + f54->sysfs_dir = kobject_create_and_add(SYSFS_FOLDER_NAME, + &rmi4_data->input_dev->dev.kobj); + if (!f54->sysfs_dir) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create sysfs directory\n", + __func__); + goto exit_directory; + } + + retval = sysfs_create_bin_file(f54->sysfs_dir, &test_report_data); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create sysfs bin file\n", + __func__); + goto exit_bin_file; + } + + retval = sysfs_create_group(f54->sysfs_dir, &attr_group); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create sysfs attributes\n", + __func__); + goto exit_attributes; + } + + return 0; + +exit_attributes: + sysfs_remove_group(f54->sysfs_dir, &attr_group); + sysfs_remove_bin_file(f54->sysfs_dir, &test_report_data); + +exit_bin_file: + kobject_put(f54->sysfs_dir); + +exit_directory: + return -ENODEV; +} + +static void test_free_control_mem(void) +{ + struct f54_control control = f54->control; + + kfree(control.reg_7); + kfree(control.reg_41); + kfree(control.reg_57); + kfree(control.reg_86); + kfree(control.reg_88); + kfree(control.reg_110); + kfree(control.reg_149); + kfree(control.reg_188); +} + +static void test_set_data(void) +{ + unsigned short reg_addr; + + reg_addr = f54->data_base_addr + REPORT_DATA_OFFSET + 1; + + /* data 4 */ + if (f54->query.has_sense_frequency_control) + reg_addr++; + + /* data 5 reserved */ + + /* data 6 */ + if (f54->query.has_interference_metric) + reg_addr += 2; + + /* data 7 */ + if (f54->query.has_one_byte_report_rate | + f54->query.has_two_byte_report_rate) + reg_addr++; + if (f54->query.has_two_byte_report_rate) + reg_addr++; + + /* data 8 */ + if (f54->query.has_variance_metric) + reg_addr += 2; + + /* data 9 */ + if (f54->query.has_multi_metric_state_machine) + reg_addr += 2; + + /* data 10 */ + if (f54->query.has_multi_metric_state_machine | + f54->query.has_noise_state) + reg_addr++; + + /* data 11 */ + if (f54->query.has_status) + reg_addr++; + + /* data 12 */ + if (f54->query.has_slew_metric) + reg_addr += 2; + + /* data 13 */ + if (f54->query.has_multi_metric_state_machine) + reg_addr += 2; + + /* data 14 */ + if (f54->query_13.has_cidim) + reg_addr++; + + /* data 15 */ + if (f54->query_13.has_rail_im) + reg_addr++; + + /* data 16 */ + if (f54->query_13.has_noise_mitigation_enhancement) + reg_addr++; + + /* data 17 */ + if (f54->query_16.has_data17) + reg_addr++; + + /* data 18 */ + if (f54->query_21.has_query24_data18) + reg_addr++; + + /* data 19 */ + if (f54->query_21.has_data19) + reg_addr++; + + /* data_20 */ + if (f54->query_25.has_ctrl109) + reg_addr++; + + /* data 21 */ + if (f54->query_27.has_data21) + reg_addr++; + + /* data 22 */ + if (f54->query_27.has_data22) + reg_addr++; + + /* data 23 */ + if (f54->query_29.has_data23) + reg_addr++; + + /* data 24 */ + if (f54->query_32.has_data24) + reg_addr++; + + /* data 25 */ + if (f54->query_35.has_data25) + reg_addr++; + + /* data 26 */ + if (f54->query_35.has_data26) + reg_addr++; + + /* data 27 */ + if (f54->query_46.has_data27) + reg_addr++; + + /* data 28 */ + if (f54->query_46.has_data28) + reg_addr++; + + /* data 29 30 reserved */ + + /* data 31 */ + if (f54->query_49.has_data31) { + f54->data_31.address = reg_addr; + reg_addr++; + } +} + +static int test_set_controls(void) +{ + int retval; + unsigned char length = 0; + unsigned char num_of_sensing_freqs; + unsigned short reg_addr = f54->control_base_addr; + struct f54_control *control = &f54->control; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + num_of_sensing_freqs = f54->query.number_of_sensing_frequencies; + + /* control 0 */ + reg_addr += CONTROL_0_SIZE; + + /* control 1 */ + if ((f54->query.touch_controller_family == 0) || + (f54->query.touch_controller_family == 1)) + reg_addr += CONTROL_1_SIZE; + + /* control 2 */ + reg_addr += CONTROL_2_SIZE; + + /* control 3 */ + if (f54->query.has_pixel_touch_threshold_adjustment) + reg_addr += CONTROL_3_SIZE; + + /* controls 4 5 6 */ + if ((f54->query.touch_controller_family == 0) || + (f54->query.touch_controller_family == 1)) + reg_addr += CONTROL_4_6_SIZE; + + /* control 7 */ + if (f54->query.touch_controller_family == 1) { + control->reg_7 = kzalloc(sizeof(*(control->reg_7)), + GFP_KERNEL); + if (!control->reg_7) + goto exit_no_mem; + control->reg_7->address = reg_addr; + reg_addr += CONTROL_7_SIZE; + } + + /* controls 8 9 */ + if ((f54->query.touch_controller_family == 0) || + (f54->query.touch_controller_family == 1)) + reg_addr += CONTROL_8_9_SIZE; + + /* control 10 */ + if (f54->query.has_interference_metric) + reg_addr += CONTROL_10_SIZE; + + /* control 11 */ + if (f54->query.has_ctrl11) + reg_addr += CONTROL_11_SIZE; + + /* controls 12 13 */ + if (f54->query.has_relaxation_control) + reg_addr += CONTROL_12_13_SIZE; + + /* controls 14 15 16 */ + if (f54->query.has_sensor_assignment) { + reg_addr += CONTROL_14_SIZE; + reg_addr += CONTROL_15_SIZE * f54->query.num_of_rx_electrodes; + reg_addr += CONTROL_16_SIZE * f54->query.num_of_tx_electrodes; + } + + /* controls 17 18 19 */ + if (f54->query.has_sense_frequency_control) { + reg_addr += CONTROL_17_SIZE * num_of_sensing_freqs; + reg_addr += CONTROL_18_SIZE * num_of_sensing_freqs; + reg_addr += CONTROL_19_SIZE * num_of_sensing_freqs; + } + + /* control 20 */ + reg_addr += CONTROL_20_SIZE; + + /* control 21 */ + if (f54->query.has_sense_frequency_control) + reg_addr += CONTROL_21_SIZE; + + /* controls 22 23 24 25 26 */ + if (f54->query.has_firmware_noise_mitigation) + reg_addr += CONTROL_22_26_SIZE; + + /* control 27 */ + if (f54->query.has_iir_filter) + reg_addr += CONTROL_27_SIZE; + + /* control 28 */ + if (f54->query.has_firmware_noise_mitigation) + reg_addr += CONTROL_28_SIZE; + + /* control 29 */ + if (f54->query.has_cmn_removal) + reg_addr += CONTROL_29_SIZE; + + /* control 30 */ + if (f54->query.has_cmn_maximum) + reg_addr += CONTROL_30_SIZE; + + /* control 31 */ + if (f54->query.has_touch_hysteresis) + reg_addr += CONTROL_31_SIZE; + + /* controls 32 33 34 35 */ + if (f54->query.has_edge_compensation) + reg_addr += CONTROL_32_35_SIZE; + + /* control 36 */ + if ((f54->query.curve_compensation_mode == 1) || + (f54->query.curve_compensation_mode == 2)) { + if (f54->query.curve_compensation_mode == 1) { + length = max(f54->query.num_of_rx_electrodes, + f54->query.num_of_tx_electrodes); + } else if (f54->query.curve_compensation_mode == 2) { + length = f54->query.num_of_rx_electrodes; + } + reg_addr += CONTROL_36_SIZE * length; + } + + /* control 37 */ + if (f54->query.curve_compensation_mode == 2) + reg_addr += CONTROL_37_SIZE * f54->query.num_of_tx_electrodes; + + /* controls 38 39 40 */ + if (f54->query.has_per_frequency_noise_control) { + reg_addr += CONTROL_38_SIZE * num_of_sensing_freqs; + reg_addr += CONTROL_39_SIZE * num_of_sensing_freqs; + reg_addr += CONTROL_40_SIZE * num_of_sensing_freqs; + } + + /* control 41 */ + if (f54->query.has_signal_clarity) { + control->reg_41 = kzalloc(sizeof(*(control->reg_41)), + GFP_KERNEL); + if (!control->reg_41) + goto exit_no_mem; + control->reg_41->address = reg_addr; + reg_addr += CONTROL_41_SIZE; + } + + /* control 42 */ + if (f54->query.has_variance_metric) + reg_addr += CONTROL_42_SIZE; + + /* controls 43 44 45 46 47 48 49 50 51 52 53 54 */ + if (f54->query.has_multi_metric_state_machine) + reg_addr += CONTROL_43_54_SIZE; + + /* controls 55 56 */ + if (f54->query.has_0d_relaxation_control) + reg_addr += CONTROL_55_56_SIZE; + + /* control 57 */ + if (f54->query.has_0d_acquisition_control) { + control->reg_57 = kzalloc(sizeof(*(control->reg_57)), + GFP_KERNEL); + if (!control->reg_57) + goto exit_no_mem; + control->reg_57->address = reg_addr; + reg_addr += CONTROL_57_SIZE; + } + + /* control 58 */ + if (f54->query.has_0d_acquisition_control) + reg_addr += CONTROL_58_SIZE; + + /* control 59 */ + if (f54->query.has_h_blank) + reg_addr += CONTROL_59_SIZE; + + /* controls 60 61 62 */ + if ((f54->query.has_h_blank) || + (f54->query.has_v_blank) || + (f54->query.has_long_h_blank)) + reg_addr += CONTROL_60_62_SIZE; + + /* control 63 */ + if ((f54->query.has_h_blank) || + (f54->query.has_v_blank) || + (f54->query.has_long_h_blank) || + (f54->query.has_slew_metric) || + (f54->query.has_slew_option) || + (f54->query.has_noise_mitigation2)) + reg_addr += CONTROL_63_SIZE; + + /* controls 64 65 66 67 */ + if (f54->query.has_h_blank) + reg_addr += CONTROL_64_67_SIZE * 7; + else if ((f54->query.has_v_blank) || + (f54->query.has_long_h_blank)) + reg_addr += CONTROL_64_67_SIZE; + + /* controls 68 69 70 71 72 73 */ + if ((f54->query.has_h_blank) || + (f54->query.has_v_blank) || + (f54->query.has_long_h_blank)) { + if (f54->query_68.is_tddi_hic) + reg_addr += CONTROL_70_73_SIZE; + else + reg_addr += CONTROL_68_73_SIZE; + } + + /* control 74 */ + if (f54->query.has_slew_metric) + reg_addr += CONTROL_74_SIZE; + + /* control 75 */ + if (f54->query.has_enhanced_stretch) + reg_addr += CONTROL_75_SIZE * num_of_sensing_freqs; + + /* control 76 */ + if (f54->query.has_startup_fast_relaxation) + reg_addr += CONTROL_76_SIZE; + + /* controls 77 78 */ + if (f54->query.has_esd_control) + reg_addr += CONTROL_77_78_SIZE; + + /* controls 79 80 81 82 83 */ + if (f54->query.has_noise_mitigation2) + reg_addr += CONTROL_79_83_SIZE; + + /* controls 84 85 */ + if (f54->query.has_energy_ratio_relaxation) + reg_addr += CONTROL_84_85_SIZE; + + /* control 86 */ + if (f54->query_13.has_ctrl86) { + control->reg_86 = kzalloc(sizeof(*(control->reg_86)), + GFP_KERNEL); + if (!control->reg_86) + goto exit_no_mem; + control->reg_86->address = reg_addr; + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->control.reg_86->address, + f54->control.reg_86->data, + sizeof(f54->control.reg_86->data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read sense display ratio\n", + __func__); + return retval; + } + reg_addr += CONTROL_86_SIZE; + } + + /* control 87 */ + if (f54->query_13.has_ctrl87) + reg_addr += CONTROL_87_SIZE; + + /* control 88 */ + if (f54->query.has_ctrl88) { + control->reg_88 = kzalloc(sizeof(*(control->reg_88)), + GFP_KERNEL); + if (!control->reg_88) + goto exit_no_mem; + control->reg_88->address = reg_addr; + reg_addr += CONTROL_88_SIZE; + } + + /* control 89 */ + if (f54->query_13.has_cidim || + f54->query_13.has_noise_mitigation_enhancement || + f54->query_13.has_rail_im) + reg_addr += CONTROL_89_SIZE; + + /* control 90 */ + if (f54->query_15.has_ctrl90) + reg_addr += CONTROL_90_SIZE; + + /* control 91 */ + if (f54->query_21.has_ctrl91) + reg_addr += CONTROL_91_SIZE; + + /* control 92 */ + if (f54->query_16.has_ctrl92) + reg_addr += CONTROL_92_SIZE; + + /* control 93 */ + if (f54->query_16.has_ctrl93) + reg_addr += CONTROL_93_SIZE; + + /* control 94 */ + if (f54->query_16.has_ctrl94_query18) + reg_addr += CONTROL_94_SIZE; + + /* control 95 */ + if (f54->query_16.has_ctrl95_query19) + reg_addr += CONTROL_95_SIZE; + + /* control 96 */ + if (f54->query_21.has_ctrl96) + reg_addr += CONTROL_96_SIZE; + + /* control 97 */ + if (f54->query_21.has_ctrl97) + reg_addr += CONTROL_97_SIZE; + + /* control 98 */ + if (f54->query_21.has_ctrl98) + reg_addr += CONTROL_98_SIZE; + + /* control 99 */ + if (f54->query.touch_controller_family == 2) + reg_addr += CONTROL_99_SIZE; + + /* control 100 */ + if (f54->query_16.has_ctrl100) + reg_addr += CONTROL_100_SIZE; + + /* control 101 */ + if (f54->query_22.has_ctrl101) + reg_addr += CONTROL_101_SIZE; + + /* control 102 */ + if (f54->query_23.has_ctrl102) + reg_addr += CONTROL_102_SIZE; + + /* control 103 */ + if (f54->query_22.has_ctrl103_query26) { + f54->skip_preparation = true; + reg_addr += CONTROL_103_SIZE; + } + + /* control 104 */ + if (f54->query_22.has_ctrl104) + reg_addr += CONTROL_104_SIZE; + + /* control 105 */ + if (f54->query_22.has_ctrl105) + reg_addr += CONTROL_105_SIZE; + + /* control 106 */ + if (f54->query_25.has_ctrl106) + reg_addr += CONTROL_106_SIZE; + + /* control 107 */ + if (f54->query_25.has_ctrl107) + reg_addr += CONTROL_107_SIZE; + + /* control 108 */ + if (f54->query_25.has_ctrl108) + reg_addr += CONTROL_108_SIZE; + + /* control 109 */ + if (f54->query_25.has_ctrl109) + reg_addr += CONTROL_109_SIZE; + + /* control 110 */ + if (f54->query_27.has_ctrl110) { + control->reg_110 = kzalloc(sizeof(*(control->reg_110)), + GFP_KERNEL); + if (!control->reg_110) + goto exit_no_mem; + control->reg_110->address = reg_addr; + reg_addr += CONTROL_110_SIZE; + } + + /* control 111 */ + if (f54->query_27.has_ctrl111) + reg_addr += CONTROL_111_SIZE; + + /* control 112 */ + if (f54->query_27.has_ctrl112) + reg_addr += CONTROL_112_SIZE; + + /* control 113 */ + if (f54->query_27.has_ctrl113) + reg_addr += CONTROL_113_SIZE; + + /* control 114 */ + if (f54->query_27.has_ctrl114) + reg_addr += CONTROL_114_SIZE; + + /* control 115 */ + if (f54->query_29.has_ctrl115) + reg_addr += CONTROL_115_SIZE; + + /* control 116 */ + if (f54->query_29.has_ctrl116) + reg_addr += CONTROL_116_SIZE; + + /* control 117 */ + if (f54->query_29.has_ctrl117) + reg_addr += CONTROL_117_SIZE; + + /* control 118 */ + if (f54->query_30.has_ctrl118) + reg_addr += CONTROL_118_SIZE; + + /* control 119 */ + if (f54->query_30.has_ctrl119) + reg_addr += CONTROL_119_SIZE; + + /* control 120 */ + if (f54->query_30.has_ctrl120) + reg_addr += CONTROL_120_SIZE; + + /* control 121 */ + if (f54->query_30.has_ctrl121) + reg_addr += CONTROL_121_SIZE; + + /* control 122 */ + if (f54->query_30.has_ctrl122_query31) + reg_addr += CONTROL_122_SIZE; + + /* control 123 */ + if (f54->query_30.has_ctrl123) + reg_addr += CONTROL_123_SIZE; + + /* control 124 */ + if (f54->query_30.has_ctrl124) + reg_addr += CONTROL_124_SIZE; + + /* control 125 */ + if (f54->query_32.has_ctrl125) + reg_addr += CONTROL_125_SIZE; + + /* control 126 */ + if (f54->query_32.has_ctrl126) + reg_addr += CONTROL_126_SIZE; + + /* control 127 */ + if (f54->query_32.has_ctrl127) + reg_addr += CONTROL_127_SIZE; + + /* control 128 */ + if (f54->query_33.has_ctrl128) + reg_addr += CONTROL_128_SIZE; + + /* control 129 */ + if (f54->query_33.has_ctrl129) + reg_addr += CONTROL_129_SIZE; + + /* control 130 */ + if (f54->query_33.has_ctrl130) + reg_addr += CONTROL_130_SIZE; + + /* control 131 */ + if (f54->query_33.has_ctrl131) + reg_addr += CONTROL_131_SIZE; + + /* control 132 */ + if (f54->query_33.has_ctrl132) + reg_addr += CONTROL_132_SIZE; + + /* control 133 */ + if (f54->query_33.has_ctrl133) + reg_addr += CONTROL_133_SIZE; + + /* control 134 */ + if (f54->query_33.has_ctrl134) + reg_addr += CONTROL_134_SIZE; + + /* control 135 */ + if (f54->query_35.has_ctrl135) + reg_addr += CONTROL_135_SIZE; + + /* control 136 */ + if (f54->query_35.has_ctrl136) + reg_addr += CONTROL_136_SIZE; + + /* control 137 */ + if (f54->query_35.has_ctrl137) + reg_addr += CONTROL_137_SIZE; + + /* control 138 */ + if (f54->query_35.has_ctrl138) + reg_addr += CONTROL_138_SIZE; + + /* control 139 */ + if (f54->query_35.has_ctrl139) + reg_addr += CONTROL_139_SIZE; + + /* control 140 */ + if (f54->query_35.has_ctrl140) + reg_addr += CONTROL_140_SIZE; + + /* control 141 */ + if (f54->query_36.has_ctrl141) + reg_addr += CONTROL_141_SIZE; + + /* control 142 */ + if (f54->query_36.has_ctrl142) + reg_addr += CONTROL_142_SIZE; + + /* control 143 */ + if (f54->query_36.has_ctrl143) + reg_addr += CONTROL_143_SIZE; + + /* control 144 */ + if (f54->query_36.has_ctrl144) + reg_addr += CONTROL_144_SIZE; + + /* control 145 */ + if (f54->query_36.has_ctrl145) + reg_addr += CONTROL_145_SIZE; + + /* control 146 */ + if (f54->query_36.has_ctrl146) + reg_addr += CONTROL_146_SIZE; + + /* control 147 */ + if (f54->query_38.has_ctrl147) + reg_addr += CONTROL_147_SIZE; + + /* control 148 */ + if (f54->query_38.has_ctrl148) + reg_addr += CONTROL_148_SIZE; + + /* control 149 */ + if (f54->query_38.has_ctrl149) { + control->reg_149 = kzalloc(sizeof(*(control->reg_149)), + GFP_KERNEL); + if (!control->reg_149) + goto exit_no_mem; + control->reg_149->address = reg_addr; + reg_addr += CONTROL_149_SIZE; + } + + /* control 150 */ + if (f54->query_38.has_ctrl150) + reg_addr += CONTROL_150_SIZE; + + /* control 151 */ + if (f54->query_38.has_ctrl151) + reg_addr += CONTROL_151_SIZE; + + /* control 152 */ + if (f54->query_38.has_ctrl152) + reg_addr += CONTROL_152_SIZE; + + /* control 153 */ + if (f54->query_38.has_ctrl153) + reg_addr += CONTROL_153_SIZE; + + /* control 154 */ + if (f54->query_39.has_ctrl154) + reg_addr += CONTROL_154_SIZE; + + /* control 155 */ + if (f54->query_39.has_ctrl155) + reg_addr += CONTROL_155_SIZE; + + /* control 156 */ + if (f54->query_39.has_ctrl156) + reg_addr += CONTROL_156_SIZE; + + /* controls 157 158 */ + if (f54->query_39.has_ctrl157_ctrl158) + reg_addr += CONTROL_157_158_SIZE; + + /* controls 159 to 162 reserved */ + + /* control 163 */ + if (f54->query_40.has_ctrl163_query41) + reg_addr += CONTROL_163_SIZE; + + /* control 164 reserved */ + + /* control 165 */ + if (f54->query_40.has_ctrl165_query42) + reg_addr += CONTROL_165_SIZE; + + /* control 166 */ + if (f54->query_40.has_ctrl166) + reg_addr += CONTROL_166_SIZE; + + /* control 167 */ + if (f54->query_40.has_ctrl167) + reg_addr += CONTROL_167_SIZE; + + /* control 168 */ + if (f54->query_40.has_ctrl168) + reg_addr += CONTROL_168_SIZE; + + /* control 169 */ + if (f54->query_40.has_ctrl169) + reg_addr += CONTROL_169_SIZE; + + /* control 170 reserved */ + + /* control 171 */ + if (f54->query_43.has_ctrl171) + reg_addr += CONTROL_171_SIZE; + + /* control 172 */ + if (f54->query_43.has_ctrl172_query44_query45) + reg_addr += CONTROL_172_SIZE; + + /* control 173 */ + if (f54->query_43.has_ctrl173) + reg_addr += CONTROL_173_SIZE; + + /* control 174 */ + if (f54->query_43.has_ctrl174) + reg_addr += CONTROL_174_SIZE; + + /* control 175 */ + if (f54->query_43.has_ctrl175) + reg_addr += CONTROL_175_SIZE; + + /* control 176 */ + if (f54->query_46.has_ctrl176) + reg_addr += CONTROL_176_SIZE; + + /* controls 177 178 */ + if (f54->query_46.has_ctrl177_ctrl178) + reg_addr += CONTROL_177_178_SIZE; + + /* control 179 */ + if (f54->query_46.has_ctrl179) + reg_addr += CONTROL_179_SIZE; + + /* controls 180 to 181 reserved */ + + /* control 182 */ + if (f54->query_47.has_ctrl182) + reg_addr += CONTROL_182_SIZE; + + /* control 183 */ + if (f54->query_47.has_ctrl183) + reg_addr += CONTROL_183_SIZE; + + /* control 184 reserved */ + + /* control 185 */ + if (f54->query_47.has_ctrl185) + reg_addr += CONTROL_185_SIZE; + + /* control 186 */ + if (f54->query_47.has_ctrl186) + reg_addr += CONTROL_186_SIZE; + + /* control 187 */ + if (f54->query_47.has_ctrl187) + reg_addr += CONTROL_187_SIZE; + + /* control 188 */ + if (f54->query_49.has_ctrl188) { + control->reg_188 = kzalloc(sizeof(*(control->reg_188)), + GFP_KERNEL); + if (!control->reg_188) + goto exit_no_mem; + control->reg_188->address = reg_addr; + reg_addr += CONTROL_188_SIZE; + } + + return 0; + +exit_no_mem: + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for control registers\n", + __func__); + return -ENOMEM; +} + +static int test_set_queries(void) +{ + int retval; + unsigned char offset; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr, + f54->query.data, + sizeof(f54->query.data)); + if (retval < 0) + return retval; + + offset = sizeof(f54->query.data); + + /* query 12 */ + if (f54->query.has_sense_frequency_control == 0) + offset -= 1; + + /* query 13 */ + if (f54->query.has_query13) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_13.data, + sizeof(f54->query_13.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 14 */ + if (f54->query_13.has_ctrl87) + offset += 1; + + /* query 15 */ + if (f54->query.has_query15) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_15.data, + sizeof(f54->query_15.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 16 */ + if (f54->query_15.has_query16) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_16.data, + sizeof(f54->query_16.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 17 */ + if (f54->query_16.has_query17) + offset += 1; + + /* query 18 */ + if (f54->query_16.has_ctrl94_query18) + offset += 1; + + /* query 19 */ + if (f54->query_16.has_ctrl95_query19) + offset += 1; + + /* query 20 */ + if (f54->query_15.has_query20) + offset += 1; + + /* query 21 */ + if (f54->query_15.has_query21) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_21.data, + sizeof(f54->query_21.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 22 */ + if (f54->query_15.has_query22) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_22.data, + sizeof(f54->query_22.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 23 */ + if (f54->query_22.has_query23) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_23.data, + sizeof(f54->query_23.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 24 */ + if (f54->query_21.has_query24_data18) + offset += 1; + + /* query 25 */ + if (f54->query_15.has_query25) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_25.data, + sizeof(f54->query_25.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 26 */ + if (f54->query_22.has_ctrl103_query26) + offset += 1; + + /* query 27 */ + if (f54->query_25.has_query27) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_27.data, + sizeof(f54->query_27.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 28 */ + if (f54->query_22.has_query28) + offset += 1; + + /* query 29 */ + if (f54->query_27.has_query29) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_29.data, + sizeof(f54->query_29.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 30 */ + if (f54->query_29.has_query30) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_30.data, + sizeof(f54->query_30.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 31 */ + if (f54->query_30.has_ctrl122_query31) + offset += 1; + + /* query 32 */ + if (f54->query_30.has_query32) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_32.data, + sizeof(f54->query_32.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 33 */ + if (f54->query_32.has_query33) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_33.data, + sizeof(f54->query_33.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 34 */ + if (f54->query_32.has_query34) + offset += 1; + + /* query 35 */ + if (f54->query_32.has_query35) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_35.data, + sizeof(f54->query_35.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 36 */ + if (f54->query_33.has_query36) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_36.data, + sizeof(f54->query_36.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 37 */ + if (f54->query_36.has_query37) + offset += 1; + + /* query 38 */ + if (f54->query_36.has_query38) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_38.data, + sizeof(f54->query_38.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 39 */ + if (f54->query_38.has_query39) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_39.data, + sizeof(f54->query_39.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 40 */ + if (f54->query_39.has_query40) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_40.data, + sizeof(f54->query_40.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 41 */ + if (f54->query_40.has_ctrl163_query41) + offset += 1; + + /* query 42 */ + if (f54->query_40.has_ctrl165_query42) + offset += 1; + + /* query 43 */ + if (f54->query_40.has_query43) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_43.data, + sizeof(f54->query_43.data)); + if (retval < 0) + return retval; + offset += 1; + } + + if (f54->query_43.has_ctrl172_query44_query45) + offset += 2; + + /* query 46 */ + if (f54->query_43.has_query46) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_46.data, + sizeof(f54->query_46.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 47 */ + if (f54->query_46.has_query47) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_47.data, + sizeof(f54->query_47.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 48 reserved */ + + /* query 49 */ + if (f54->query_47.has_query49) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_49.data, + sizeof(f54->query_49.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 50 */ + if (f54->query_49.has_query50) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_50.data, + sizeof(f54->query_50.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 51 */ + if (f54->query_50.has_query51) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_51.data, + sizeof(f54->query_51.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 53 54 */ + if (f54->query_51.has_query53_query54_ctrl198) + offset += 2; + + /* query 55 */ + if (f54->query_51.has_query55) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_55.data, + sizeof(f54->query_55.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 56 */ + if (f54->query_55.has_query56) + offset += 1; + + /* query 57 */ + if (f54->query_55.has_query57) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_57.data, + sizeof(f54->query_57.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 58 */ + if (f54->query_57.has_query58) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_58.data, + sizeof(f54->query_58.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 59 */ + if (f54->query_58.has_query59) + offset += 1; + + /* query 60 */ + if (f54->query_58.has_query60) + offset += 1; + + /* query 61 */ + if (f54->query_58.has_query61) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_61.data, + sizeof(f54->query_61.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 62 63 */ + if (f54->query_61.has_ctrl215_query62_query63) + offset += 2; + + /* query 64 */ + if (f54->query_61.has_query64) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_64.data, + sizeof(f54->query_64.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 65 */ + if (f54->query_64.has_query65) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_65.data, + sizeof(f54->query_65.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 66 */ + if (f54->query_65.has_query66_ctrl231) + offset += 1; + + /* query 67 */ + if (f54->query_65.has_query67) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_67.data, + sizeof(f54->query_67.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 68 */ + if (f54->query_67.has_query68) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_68.data, + sizeof(f54->query_68.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 68 */ + if (f54->query_68.has_query69) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_69.data, + sizeof(f54->query_69.data)); + if (retval < 0) + return retval; + offset += 1; + } + + return 0; +} + +static void test_f54_set_regs(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn_desc *fd, + unsigned int intr_count, + unsigned char page) +{ + unsigned char ii; + unsigned char intr_offset; + + f54->query_base_addr = fd->query_base_addr | (page << 8); + f54->control_base_addr = fd->ctrl_base_addr | (page << 8); + f54->data_base_addr = fd->data_base_addr | (page << 8); + f54->command_base_addr = fd->cmd_base_addr | (page << 8); + + f54->intr_reg_num = (intr_count + 7) / 8; + if (f54->intr_reg_num != 0) + f54->intr_reg_num -= 1; + + f54->intr_mask = 0; + intr_offset = intr_count % 8; + for (ii = intr_offset; + ii < (fd->intr_src_count + intr_offset); + ii++) { + f54->intr_mask |= 1 << ii; + } +} + +static int test_f55_set_controls(void) +{ + unsigned char offset = 0; + + /* controls 0 1 2 */ + if (f55->query.has_sensor_assignment) + offset += 3; + + /* control 3 */ + if (f55->query.has_edge_compensation) + offset++; + + /* control 4 */ + if (f55->query.curve_compensation_mode == 0x1 || + f55->query.curve_compensation_mode == 0x2) + offset++; + + /* control 5 */ + if (f55->query.curve_compensation_mode == 0x2) + offset++; + + /* control 6 */ + if (f55->query.has_ctrl6) + offset++; + + /* control 7 */ + if (f55->query.has_alternate_transmitter_assignment) + offset++; + + /* control 8 */ + if (f55->query_3.has_ctrl8) + offset++; + + /* control 9 */ + if (f55->query_3.has_ctrl9) + offset++; + + /* control 10 */ + if (f55->query_5.has_corner_compensation) + offset++; + + /* control 11 */ + if (f55->query.curve_compensation_mode == 0x3) + offset++; + + /* control 12 */ + if (f55->query_5.has_ctrl12) + offset++; + + /* control 13 */ + if (f55->query_5.has_ctrl13) + offset++; + + /* control 14 */ + if (f55->query_5.has_ctrl14) + offset++; + + /* control 15 */ + if (f55->query_5.has_basis_function) + offset++; + + /* control 16 */ + if (f55->query_17.has_ctrl16) + offset++; + + /* control 17 */ + if (f55->query_17.has_ctrl17) + offset++; + + /* controls 18 19 */ + if (f55->query_17.has_ctrl18_ctrl19) + offset += 2; + + /* control 20 */ + if (f55->query_17.has_ctrl20) + offset++; + + /* control 21 */ + if (f55->query_17.has_ctrl21) + offset++; + + /* control 22 */ + if (f55->query_17.has_ctrl22) + offset++; + + /* control 23 */ + if (f55->query_18.has_ctrl23) + offset++; + + /* control 24 */ + if (f55->query_18.has_ctrl24) + offset++; + + /* control 25 */ + if (f55->query_18.has_ctrl25) + offset++; + + /* control 26 */ + if (f55->query_18.has_ctrl26) + offset++; + + /* control 27 */ + if (f55->query_18.has_ctrl27_query20) + offset++; + + /* control 28 */ + if (f55->query_18.has_ctrl28_query21) + offset++; + + /* control 29 */ + if (f55->query_22.has_ctrl29) + offset++; + + /* control 30 */ + if (f55->query_22.has_ctrl30) + offset++; + + /* control 31 */ + if (f55->query_22.has_ctrl31) + offset++; + + /* control 32 */ + if (f55->query_22.has_ctrl32) + offset++; + + /* controls 33 34 35 36 reserved */ + + /* control 37 */ + if (f55->query_28.has_ctrl37) + offset++; + + /* control 38 */ + if (f55->query_30.has_ctrl38) + offset++; + + /* control 39 */ + if (f55->query_30.has_ctrl39) + offset++; + + /* control 40 */ + if (f55->query_30.has_ctrl40) + offset++; + + /* control 41 */ + if (f55->query_30.has_ctrl41) + offset++; + + /* control 42 */ + if (f55->query_30.has_ctrl42) + offset++; + + /* controls 43 44 */ + if (f55->query_30.has_ctrl43_ctrl44) { + f55->afe_mux_offset = offset; + offset += 2; + } + + /* controls 45 46 */ + if (f55->query_33.has_ctrl45_ctrl46) { + f55->has_force = true; + f55->force_tx_offset = offset; + f55->force_rx_offset = offset + 1; + offset += 2; + } + + return 0; +} + +static int test_f55_set_queries(void) +{ + int retval; + unsigned char offset; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + f55->query_base_addr, + f55->query.data, + sizeof(f55->query.data)); + if (retval < 0) + return retval; + + offset = sizeof(f55->query.data); + + /* query 3 */ + if (f55->query.has_single_layer_multi_touch) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f55->query_base_addr + offset, + f55->query_3.data, + sizeof(f55->query_3.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 4 */ + if (f55->query_3.has_ctrl9) + offset += 1; + + /* query 5 */ + if (f55->query.has_query5) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f55->query_base_addr + offset, + f55->query_5.data, + sizeof(f55->query_5.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* queries 6 7 */ + if (f55->query.curve_compensation_mode == 0x3) + offset += 2; + + /* query 8 */ + if (f55->query_3.has_ctrl8) + offset += 1; + + /* query 9 */ + if (f55->query_3.has_query9) + offset += 1; + + /* queries 10 11 12 13 14 15 16 */ + if (f55->query_5.has_basis_function) + offset += 7; + + /* query 17 */ + if (f55->query_5.has_query17) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f55->query_base_addr + offset, + f55->query_17.data, + sizeof(f55->query_17.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 18 */ + if (f55->query_17.has_query18) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f55->query_base_addr + offset, + f55->query_18.data, + sizeof(f55->query_18.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 19 */ + if (f55->query_18.has_query19) + offset += 1; + + /* query 20 */ + if (f55->query_18.has_ctrl27_query20) + offset += 1; + + /* query 21 */ + if (f55->query_18.has_ctrl28_query21) + offset += 1; + + /* query 22 */ + if (f55->query_18.has_query22) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f55->query_base_addr + offset, + f55->query_22.data, + sizeof(f55->query_22.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 23 */ + if (f55->query_22.has_query23) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f55->query_base_addr + offset, + f55->query_23.data, + sizeof(f55->query_23.data)); + if (retval < 0) + return retval; + offset += 1; + + f55->amp_sensor = f55->query_23.amp_sensor_enabled; + f55->size_of_column2mux = f55->query_23.size_of_column2mux; + } + + /* queries 24 25 26 27 reserved */ + + /* query 28 */ + if (f55->query_22.has_query28) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f55->query_base_addr + offset, + f55->query_28.data, + sizeof(f55->query_28.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 29 */ + if (f55->query_28.has_query29) + offset += 1; + + /* query 30 */ + if (f55->query_28.has_query30) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f55->query_base_addr + offset, + f55->query_30.data, + sizeof(f55->query_30.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* queries 31 32 */ + if (f55->query_30.has_query31_query32) + offset += 2; + + /* query 33 */ + if (f55->query_30.has_query33) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f55->query_base_addr + offset, + f55->query_33.data, + sizeof(f55->query_33.data)); + if (retval < 0) + return retval; + offset += 1; + + f55->extended_amp = f55->query_33.has_extended_amp_pad; + } + + return 0; +} + +static void test_f55_init(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + unsigned char ii; + unsigned char rx_electrodes; + unsigned char tx_electrodes; + struct f55_control_43 ctrl_43; + + retval = test_f55_set_queries(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read F55 query registers\n", + __func__); + return; + } + + if (!f55->query.has_sensor_assignment) + return; + + retval = test_f55_set_controls(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set up F55 control registers\n", + __func__); + return; + } + + tx_electrodes = f55->query.num_of_tx_electrodes; + rx_electrodes = f55->query.num_of_rx_electrodes; + + f55->tx_assignment = kzalloc(tx_electrodes, GFP_KERNEL); + f55->rx_assignment = kzalloc(rx_electrodes, GFP_KERNEL); + + retval = synaptics_rmi4_reg_read(rmi4_data, + f55->control_base_addr + SENSOR_TX_MAPPING_OFFSET, + f55->tx_assignment, + tx_electrodes); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read F55 tx assignment\n", + __func__); + return; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + f55->control_base_addr + SENSOR_RX_MAPPING_OFFSET, + f55->rx_assignment, + rx_electrodes); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read F55 rx assignment\n", + __func__); + return; + } + + f54->tx_assigned = 0; + for (ii = 0; ii < tx_electrodes; ii++) { + if (f55->tx_assignment[ii] != 0xff) + f54->tx_assigned++; + } + + f54->rx_assigned = 0; + for (ii = 0; ii < rx_electrodes; ii++) { + if (f55->rx_assignment[ii] != 0xff) + f54->rx_assigned++; + } + + if (f55->amp_sensor) { + f54->tx_assigned = f55->size_of_column2mux; + f54->rx_assigned /= 2; + } + + if (f55->extended_amp) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f55->control_base_addr + f55->afe_mux_offset, + ctrl_43.data, + sizeof(ctrl_43.data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read F55 AFE mux sizes\n", + __func__); + return; + } + + f54->tx_assigned = ctrl_43.afe_l_mux_size + + ctrl_43.afe_r_mux_size; + } + + /* force mapping */ + if (f55->has_force) { + f55->force_tx_assignment = kzalloc(tx_electrodes, GFP_KERNEL); + f55->force_rx_assignment = kzalloc(rx_electrodes, GFP_KERNEL); + + retval = synaptics_rmi4_reg_read(rmi4_data, + f55->control_base_addr + f55->force_tx_offset, + f55->force_tx_assignment, + tx_electrodes); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read F55 force tx assignment\n", + __func__); + return; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + f55->control_base_addr + f55->force_rx_offset, + f55->force_rx_assignment, + rx_electrodes); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read F55 force rx assignment\n", + __func__); + return; + } + + for (ii = 0; ii < tx_electrodes; ii++) { + if (f55->force_tx_assignment[ii] != 0xff) + f54->tx_assigned++; + } + + for (ii = 0; ii < rx_electrodes; ii++) { + if (f55->force_rx_assignment[ii] != 0xff) + f54->rx_assigned++; + } + } +} + +static void test_f55_set_regs(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn_desc *fd, + unsigned char page) +{ + f55 = kzalloc(sizeof(*f55), GFP_KERNEL); + if (!f55) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for F55\n", + __func__); + return; + } + + f55->query_base_addr = fd->query_base_addr | (page << 8); + f55->control_base_addr = fd->ctrl_base_addr | (page << 8); + f55->data_base_addr = fd->data_base_addr | (page << 8); + f55->command_base_addr = fd->cmd_base_addr | (page << 8); +} + +static void test_f21_init(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + unsigned char ii; + unsigned char size_of_query2; + unsigned char size_of_query5; + unsigned char query_11_offset; + unsigned char ctrl_4_offset; + struct f21_query_2 *query_2 = NULL; + struct f21_query_5 *query_5 = NULL; + struct f21_query_11 *query_11 = NULL; + + query_2 = kzalloc(sizeof(*query_2), GFP_KERNEL); + if (!query_2) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for query_2\n", + __func__); + goto exit; + } + + query_5 = kzalloc(sizeof(*query_5), GFP_KERNEL); + if (!query_5) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for query_5\n", + __func__); + goto exit; + } + + query_11 = kzalloc(sizeof(*query_11), GFP_KERNEL); + if (!query_11) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for query_11\n", + __func__); + goto exit; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + f21->query_base_addr + 1, + &size_of_query2, + sizeof(size_of_query2)); + if (retval < 0) + goto exit; + + if (size_of_query2 > sizeof(query_2->data)) + size_of_query2 = sizeof(query_2->data); + + retval = synaptics_rmi4_reg_read(rmi4_data, + f21->query_base_addr + 2, + query_2->data, + size_of_query2); + if (retval < 0) + goto exit; + + if (!query_2->query11_is_present) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: No F21 force capabilities\n", + __func__); + goto exit; + } + + query_11_offset = query_2->query0_is_present + + query_2->query1_is_present + + query_2->query2_is_present + + query_2->query3_is_present + + query_2->query4_is_present + + query_2->query5_is_present + + query_2->query6_is_present + + query_2->query7_is_present + + query_2->query8_is_present + + query_2->query9_is_present + + query_2->query10_is_present; + + retval = synaptics_rmi4_reg_read(rmi4_data, + f21->query_base_addr + 11, + query_11->data, + sizeof(query_11->data)); + if (retval < 0) + goto exit; + + if (!query_11->has_force_sensing_txrx_mapping) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: No F21 force mapping\n", + __func__); + goto exit; + } + + f21->max_num_of_tx = query_11->max_number_of_force_txs; + f21->max_num_of_rx = query_11->max_number_of_force_rxs; + f21->max_num_of_txrx = f21->max_num_of_tx + f21->max_num_of_rx; + + f21->force_txrx_assignment = kzalloc(f21->max_num_of_txrx, GFP_KERNEL); + + retval = synaptics_rmi4_reg_read(rmi4_data, + f21->query_base_addr + 4, + &size_of_query5, + sizeof(size_of_query5)); + if (retval < 0) + goto exit; + + if (size_of_query5 > sizeof(query_5->data)) + size_of_query5 = sizeof(query_5->data); + + retval = synaptics_rmi4_reg_read(rmi4_data, + f21->query_base_addr + 5, + query_5->data, + size_of_query5); + if (retval < 0) + goto exit; + + ctrl_4_offset = query_5->ctrl0_is_present + + query_5->ctrl1_is_present + + query_5->ctrl2_is_present + + query_5->ctrl3_is_present; + + retval = synaptics_rmi4_reg_read(rmi4_data, + f21->control_base_addr + ctrl_4_offset, + f21->force_txrx_assignment, + f21->max_num_of_txrx); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read F21 force txrx assignment\n", + __func__); + goto exit; + } + + f21->has_force = true; + + for (ii = 0; ii < f21->max_num_of_tx; ii++) { + if (f21->force_txrx_assignment[ii] != 0xff) + f21->tx_assigned++; + } + + for (ii = f21->max_num_of_tx; ii < f21->max_num_of_txrx; ii++) { + if (f21->force_txrx_assignment[ii] != 0xff) + f21->rx_assigned++; + } + +exit: + kfree(query_2); + kfree(query_5); + kfree(query_11); +} + +static void test_f21_set_regs(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn_desc *fd, + unsigned char page) +{ + f21 = kzalloc(sizeof(*f21), GFP_KERNEL); + if (!f21) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for F21\n", + __func__); + return; + } + + f21->query_base_addr = fd->query_base_addr | (page << 8); + f21->control_base_addr = fd->ctrl_base_addr | (page << 8); + f21->data_base_addr = fd->data_base_addr | (page << 8); + f21->command_base_addr = fd->cmd_base_addr | (page << 8); +} + +static int test_scan_pdt(void) +{ + int retval; + unsigned char intr_count = 0; + unsigned char page; + unsigned short addr; + bool f54found = false; + bool f55found = false; + struct synaptics_rmi4_fn_desc rmi_fd; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + for (page = 0; page < PAGES_TO_SERVICE; page++) { + for (addr = PDT_START; addr > PDT_END; addr -= PDT_ENTRY_SIZE) { + addr |= (page << 8); + + retval = synaptics_rmi4_reg_read(rmi4_data, + addr, + (unsigned char *)&rmi_fd, + sizeof(rmi_fd)); + if (retval < 0) + return retval; + + addr &= ~(MASK_8BIT << 8); + + if (!rmi_fd.fn_number) + break; + + switch (rmi_fd.fn_number) { + case SYNAPTICS_RMI4_F54: + test_f54_set_regs(rmi4_data, + &rmi_fd, intr_count, page); + f54found = true; + break; + case SYNAPTICS_RMI4_F55: + test_f55_set_regs(rmi4_data, + &rmi_fd, page); + f55found = true; + break; + case SYNAPTICS_RMI4_F21: + test_f21_set_regs(rmi4_data, + &rmi_fd, page); + break; + default: + break; + } + + if (f54found && f55found) + goto pdt_done; + + intr_count += rmi_fd.intr_src_count; + } + } + + if (!f54found) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to find F54\n", + __func__); + return -EINVAL; + } + +pdt_done: + return 0; +} + +static void synaptics_rmi4_test_attn(struct synaptics_rmi4_data *rmi4_data, + unsigned char intr_mask) +{ + if (!f54) + return; + + if (f54->intr_mask & intr_mask) + queue_work(f54->test_report_workqueue, &f54->test_report_work); + + return; +} + +static int synaptics_rmi4_test_init(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + + if (f54) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Handle already exists\n", + __func__); + return 0; + } + + f54 = kzalloc(sizeof(*f54), GFP_KERNEL); + if (!f54) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for F54\n", + __func__); + retval = -ENOMEM; + goto exit; + } + + f54->rmi4_data = rmi4_data; + + f55 = NULL; + + f21 = NULL; + + retval = test_scan_pdt(); + if (retval < 0) + goto exit_free_mem; + + retval = test_set_queries(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read F54 query registers\n", + __func__); + goto exit_free_mem; + } + + f54->tx_assigned = f54->query.num_of_tx_electrodes; + f54->rx_assigned = f54->query.num_of_rx_electrodes; + + retval = test_set_controls(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set up F54 control registers\n", + __func__); + goto exit_free_control; + } + + test_set_data(); + + if (f55) + test_f55_init(rmi4_data); + + if (f21) + test_f21_init(rmi4_data); + + if (rmi4_data->external_afe_buttons) + f54->tx_assigned++; + + retval = test_set_sysfs(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create sysfs entries\n", + __func__); + goto exit_sysfs; + } + + f54->test_report_workqueue = + create_singlethread_workqueue("test_report_workqueue"); + INIT_WORK(&f54->test_report_work, test_report_work); + + hrtimer_init(&f54->watchdog, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + f54->watchdog.function = test_get_report_timeout; + INIT_WORK(&f54->timeout_work, test_timeout_work); + + mutex_init(&f54->status_mutex); + f54->status = STATUS_IDLE; + + return 0; + +exit_sysfs: + if (f21) + kfree(f21->force_txrx_assignment); + + if (f55) { + kfree(f55->tx_assignment); + kfree(f55->rx_assignment); + kfree(f55->force_tx_assignment); + kfree(f55->force_rx_assignment); + } + +exit_free_control: + test_free_control_mem(); + +exit_free_mem: + kfree(f21); + f21 = NULL; + kfree(f55); + f55 = NULL; + kfree(f54); + f54 = NULL; + +exit: + return retval; +} + +static void synaptics_rmi4_test_remove(struct synaptics_rmi4_data *rmi4_data) +{ + if (!f54) + goto exit; + + hrtimer_cancel(&f54->watchdog); + + cancel_work_sync(&f54->test_report_work); + flush_workqueue(f54->test_report_workqueue); + destroy_workqueue(f54->test_report_workqueue); + + test_remove_sysfs(); + + if (f21) + kfree(f21->force_txrx_assignment); + + if (f55) { + kfree(f55->tx_assignment); + kfree(f55->rx_assignment); + kfree(f55->force_tx_assignment); + kfree(f55->force_rx_assignment); + } + + test_free_control_mem(); + + if (f54->data_buffer_size) + kfree(f54->report_data); + + kfree(f21); + f21 = NULL; + + kfree(f55); + f55 = NULL; + + kfree(f54); + f54 = NULL; + +exit: + complete(&test_remove_complete); +} + +static void synaptics_rmi4_test_reset(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + + if (!f54) { + synaptics_rmi4_test_init(rmi4_data); + return; + } + + if (f21) + kfree(f21->force_txrx_assignment); + + if (f55) { + kfree(f55->tx_assignment); + kfree(f55->rx_assignment); + kfree(f55->force_tx_assignment); + kfree(f55->force_rx_assignment); + } + + test_free_control_mem(); + + kfree(f55); + f55 = NULL; + + kfree(f21); + f21 = NULL; + + retval = test_scan_pdt(); + if (retval < 0) + goto exit_free_mem; + + retval = test_set_queries(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read F54 query registers\n", + __func__); + goto exit_free_mem; + } + + f54->tx_assigned = f54->query.num_of_tx_electrodes; + f54->rx_assigned = f54->query.num_of_rx_electrodes; + + retval = test_set_controls(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set up F54 control registers\n", + __func__); + goto exit_free_control; + } + + test_set_data(); + + if (f55) + test_f55_init(rmi4_data); + + if (f21) + test_f21_init(rmi4_data); + + if (rmi4_data->external_afe_buttons) + f54->tx_assigned++; + + f54->status = STATUS_IDLE; + + return; + +exit_free_control: + test_free_control_mem(); + +exit_free_mem: + hrtimer_cancel(&f54->watchdog); + + cancel_work_sync(&f54->test_report_work); + flush_workqueue(f54->test_report_workqueue); + destroy_workqueue(f54->test_report_workqueue); + + test_remove_sysfs(); + + if (f54->data_buffer_size) + kfree(f54->report_data); + + kfree(f21); + f21 = NULL; + + kfree(f55); + f55 = NULL; + + kfree(f54); + f54 = NULL; + + return; +} + +static struct synaptics_rmi4_exp_fn test_module = { + .fn_type = RMI_TEST_REPORTING, + .init = synaptics_rmi4_test_init, + .remove = synaptics_rmi4_test_remove, + .reset = synaptics_rmi4_test_reset, + .reinit = NULL, + .early_suspend = NULL, + .suspend = NULL, + .resume = NULL, + .late_resume = NULL, + .attn = synaptics_rmi4_test_attn, +}; + +static int __init rmi4_test_module_init(void) +{ + synaptics_rmi4_new_function(&test_module, true); + + return 0; +} + +static void __exit rmi4_test_module_exit(void) +{ + synaptics_rmi4_new_function(&test_module, false); + + wait_for_completion(&test_remove_complete); +} + +module_init(rmi4_test_module_init); +module_exit(rmi4_test_module_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics DSX Test Reporting Module"); +MODULE_LICENSE("GPL v2"); diff --git a/qcom/opensource/touch-drivers/synaptics_dsx/synaptics_dsx_video.c b/qcom/opensource/touch-drivers/synaptics_dsx/synaptics_dsx_video.c new file mode 100644 index 0000000000..5067d85ca1 --- /dev/null +++ b/qcom/opensource/touch-drivers/synaptics_dsx/synaptics_dsx_video.c @@ -0,0 +1,403 @@ +/* + * Synaptics DSX touchscreen driver + * + * Copyright (C) 2012-2016 Synaptics Incorporated. All rights reserved. + * + * Copyright (c) 2018-2021 The Linux Foundation. All rights reserved. + * Copyright (C) 2012 Alexandra Chin + * Copyright (C) 2012 Scott Lin + * + * 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 +#include +#include "synaptics_dsx_core.h" + +#define SYSFS_FOLDER_NAME "video" + +static ssize_t video_sysfs_dcs_write_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t video_sysfs_param_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static int video_send_dcs_command(unsigned char command_opcode); + +struct f38_command { + union { + struct { + unsigned char command_opcode; + unsigned char register_access:1; + unsigned char gamma_page:1; + unsigned char f38_control1_b2__7:6; + unsigned char parameter_field_1; + unsigned char parameter_field_2; + unsigned char parameter_field_3; + unsigned char parameter_field_4; + unsigned char send_to_dcs:1; + unsigned char f38_command6_b1__7:7; + } __packed; + unsigned char data[7]; + }; +}; + +struct synaptics_rmi4_video_handle { + unsigned char param; + unsigned short query_base_addr; + unsigned short control_base_addr; + unsigned short data_base_addr; + unsigned short command_base_addr; + struct synaptics_rmi4_data *rmi4_data; + struct kobject *sysfs_dir; +}; + +#ifdef RMI_DCS_SUSPEND_RESUME +struct dcs_command { + unsigned char command; + unsigned int wait_time; +}; + +static struct dcs_command suspend_sequence[] = { + { + .command = 0x28, + .wait_time = 200, + }, + { + .command = 0x10, + .wait_time = 200, + }, +}; + +static struct dcs_command resume_sequence[] = { + { + .command = 0x11, + .wait_time = 200, + }, + { + .command = 0x29, + .wait_time = 200, + }, +}; +#endif + +static struct device_attribute attrs[] = { + __ATTR(dcs_write, 0220, + synaptics_rmi4_show_error, + video_sysfs_dcs_write_store), + __ATTR(param, 0220, + synaptics_rmi4_show_error, + video_sysfs_param_store), +}; + +static struct synaptics_rmi4_video_handle *video; + +DECLARE_COMPLETION(video_remove_complete); + +static ssize_t video_sysfs_dcs_write_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned int input; + + if (kstrtouint(buf, 16, &input) != 1) + return -EINVAL; + + retval = video_send_dcs_command((unsigned char)input); + if (retval < 0) + return retval; + + return count; +} + +static ssize_t video_sysfs_param_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int input; + + if (kstrtouint(buf, 16, &input) != 1) + return -EINVAL; + + video->param = (unsigned char)input; + + return count; +} + +static int video_send_dcs_command(unsigned char command_opcode) +{ + int retval; + struct f38_command command; + struct synaptics_rmi4_data *rmi4_data = video->rmi4_data; + + memset(&command, 0x00, sizeof(command)); + + command.command_opcode = command_opcode; + command.parameter_field_1 = video->param; + command.send_to_dcs = 1; + + video->param = 0; + + retval = synaptics_rmi4_reg_write(rmi4_data, + video->command_base_addr, + command.data, + sizeof(command.data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to send DCS command\n", + __func__); + return retval; + } + + return 0; +} + +static int video_scan_pdt(void) +{ + int retval; + unsigned char page; + unsigned short addr; + bool f38_found = false; + struct synaptics_rmi4_fn_desc rmi_fd; + struct synaptics_rmi4_data *rmi4_data = video->rmi4_data; + + for (page = 0; page < PAGES_TO_SERVICE; page++) { + for (addr = PDT_START; addr > PDT_END; addr -= PDT_ENTRY_SIZE) { + addr |= (page << 8); + + retval = synaptics_rmi4_reg_read(rmi4_data, + addr, + (unsigned char *)&rmi_fd, + sizeof(rmi_fd)); + if (retval < 0) + return retval; + + addr &= ~(MASK_8BIT << 8); + + if (!rmi_fd.fn_number) + break; + + if (rmi_fd.fn_number == SYNAPTICS_RMI4_F38) { + f38_found = true; + goto f38_found; + } + } + } + + if (!f38_found) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to find F38\n", + __func__); + return -EINVAL; + } + +f38_found: + video->query_base_addr = rmi_fd.query_base_addr | (page << 8); + video->control_base_addr = rmi_fd.ctrl_base_addr | (page << 8); + video->data_base_addr = rmi_fd.data_base_addr | (page << 8); + video->command_base_addr = rmi_fd.cmd_base_addr | (page << 8); + + return 0; +} + +static int synaptics_rmi4_video_init(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + unsigned char attr_count; + + if (video) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Handle already exists\n", + __func__); + return 0; + } + + video = kzalloc(sizeof(*video), GFP_KERNEL); + if (!video) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for video\n", + __func__); + retval = -ENOMEM; + goto exit; + } + + video->rmi4_data = rmi4_data; + + retval = video_scan_pdt(); + if (retval < 0) { + retval = 0; + goto exit_scan_pdt; + } + + video->sysfs_dir = kobject_create_and_add(SYSFS_FOLDER_NAME, + &rmi4_data->input_dev->dev.kobj); + if (!video->sysfs_dir) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create sysfs directory\n", + __func__); + retval = -ENODEV; + goto exit_sysfs_dir; + } + + for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) { + retval = sysfs_create_file(video->sysfs_dir, + &attrs[attr_count].attr); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create sysfs attributes\n", + __func__); + retval = -ENODEV; + goto exit_sysfs_attrs; + } + } + + return 0; + +exit_sysfs_attrs: + for (attr_count--; attr_count >= 0; attr_count--) + sysfs_remove_file(video->sysfs_dir, &attrs[attr_count].attr); + + kobject_put(video->sysfs_dir); + +exit_sysfs_dir: +exit_scan_pdt: + kfree(video); + video = NULL; + +exit: + return retval; +} + +static void synaptics_rmi4_video_remove(struct synaptics_rmi4_data *rmi4_data) +{ + unsigned char attr_count; + + if (!video) + goto exit; + + for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) + sysfs_remove_file(video->sysfs_dir, &attrs[attr_count].attr); + + kobject_put(video->sysfs_dir); + + kfree(video); + video = NULL; + +exit: + complete(&video_remove_complete); +} + +static void synaptics_rmi4_video_reset(struct synaptics_rmi4_data *rmi4_data) +{ + if (!video) + synaptics_rmi4_video_init(rmi4_data); +} + +#ifdef RMI_DCS_SUSPEND_RESUME +static void synaptics_rmi4_video_suspend(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + unsigned char ii; + unsigned char command; + unsigned char num_of_cmds; + + if (!video) + return; + + num_of_cmds = ARRAY_SIZE(suspend_sequence); + + for (ii = 0; ii < num_of_cmds; ii++) { + command = suspend_sequence[ii].command; + retval = video_send_dcs_command(command); + if (retval < 0) + return; + msleep(suspend_sequence[ii].wait_time); + } +} + +static void synaptics_rmi4_video_resume(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + unsigned char ii; + unsigned char command; + unsigned char num_of_cmds; + + if (!video) + return; + + num_of_cmds = ARRAY_SIZE(resume_sequence); + + for (ii = 0; ii < num_of_cmds; ii++) { + command = resume_sequence[ii].command; + retval = video_send_dcs_command(command); + if (retval < 0) + return; + msleep(resume_sequence[ii].wait_time); + } +} +#endif + +static struct synaptics_rmi4_exp_fn video_module = { + .fn_type = RMI_VIDEO, + .init = synaptics_rmi4_video_init, + .remove = synaptics_rmi4_video_remove, + .reset = synaptics_rmi4_video_reset, + .reinit = NULL, + .early_suspend = NULL, +#ifdef RMI_DCS_SUSPEND_RESUME + .suspend = synaptics_rmi4_video_suspend, + .resume = synaptics_rmi4_video_resume, +#else + .suspend = NULL, + .resume = NULL, +#endif + .late_resume = NULL, + .attn = NULL, +}; + +static int __init rmi4_video_module_init(void) +{ + synaptics_rmi4_new_function(&video_module, true); + + return 0; +} + +static void __exit rmi4_video_module_exit(void) +{ + synaptics_rmi4_new_function(&video_module, false); + + wait_for_completion(&video_remove_complete); +} + +module_init(rmi4_video_module_init); +module_exit(rmi4_video_module_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics DSX Video Module"); +MODULE_LICENSE("GPL v2"); diff --git a/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm.h b/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm.h new file mode 100644 index 0000000000..1d650b451f --- /dev/null +++ b/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm.h @@ -0,0 +1,65 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2019 Scott Lin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#ifndef _SYNAPTICS_TCM_H_ +#define _SYNAPTICS_TCM_H_ + +#define I2C_MODULE_NAME "synaptics_tcm_i2c" +#define SPI_MODULE_NAME "synaptics_tcm_spi" + +struct syna_tcm_board_data { + bool x_flip; + bool y_flip; + bool swap_axes; + int irq_gpio; + int irq_on_state; + int power_gpio; + int power_on_state; + int reset_gpio; + int reset_on_state; + unsigned int spi_mode; + unsigned int power_delay_ms; + unsigned int reset_delay_ms; + unsigned int reset_active_ms; + unsigned int byte_delay_us; + unsigned int block_delay_us; + unsigned int ubl_i2c_addr; + unsigned int ubl_max_freq; + unsigned int ubl_byte_delay_us; + unsigned long irq_flags; + const char *pwr_reg_name; + const char *bus_reg_name; + const char *fw_name; + bool extend_report; +}; + +#endif diff --git a/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm_core.c b/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm_core.c new file mode 100644 index 0000000000..1f4a058ac6 --- /dev/null +++ b/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm_core.c @@ -0,0 +1,3830 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2019 Scott Lin + * + * 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" + +/* #define RESET_ON_RESUME */ + +/* #define RESUME_EARLY_UNBLANK */ + +#define RESET_ON_RESUME_DELAY_MS 50 + +#define PREDICTIVE_READING + +#define MIN_READ_LENGTH 9 + +#define KEEP_DRIVER_ON_ERROR + +/* #define FORCE_RUN_APPLICATION_FIRMWARE */ + +#define SYNA_VTG_MIN_UV 2800000 + +#define SYNA_VTG_MAX_UV 3300000 + +#define SYNA_LOAD_MAX_UA 30000 + +#define SYNA_VDD_VTG_MIN_UV 1800000 + +#define SYNA_VDD_VTG_MAX_UV 2000000 + +#define NOTIFIER_PRIORITY 2 + +#define RESPONSE_TIMEOUT_MS 3000 + +#define APP_STATUS_POLL_TIMEOUT_MS 1000 + +#define APP_STATUS_POLL_MS 100 + +#define ENABLE_IRQ_DELAY_MS 20 + +#define FALL_BACK_ON_POLLING + +#define POLLING_DELAY_MS 5 + +#define RUN_WATCHDOG true + +#define WATCHDOG_TRIGGER_COUNT 2 + +#define WATCHDOG_DELAY_MS 50000 + +#define MODE_SWITCH_DELAY_MS 100 + +#define READ_RETRY_US_MIN 5000 + +#define READ_RETRY_US_MAX 10000 + +#define WRITE_DELAY_US_MIN 500 + +#define WRITE_DELAY_US_MAX 1000 + +#define HOST_DOWNLOAD_WAIT_MS 100 + +#define HOST_DOWNLOAD_TIMEOUT_MS 1000 + +/* #define CREATE_SYSFS */ + +#define DYNAMIC_CONFIG_SYSFS_DIR_NAME "dynamic_config" + +#define dynamic_config_sysfs(c_name, id) \ +static ssize_t syna_tcm_sysfs_##c_name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + int retval; \ + unsigned short value; \ + struct device *p_dev; \ + struct kobject *p_kobj; \ + struct syna_tcm_hcd *tcm_hcd; \ +\ + p_kobj = sysfs_dir->parent; \ + p_dev = container_of(p_kobj, struct device, kobj); \ + tcm_hcd = dev_get_drvdata(p_dev); \ +\ + mutex_lock(&tcm_hcd->extif_mutex); \ +\ + retval = tcm_hcd->get_dynamic_config(tcm_hcd, id, &value); \ + if (retval < 0) { \ + LOGE(tcm_hcd->pdev->dev.parent, \ + "Failed to get dynamic config\n"); \ + goto exit; \ + } \ +\ + retval = snprintf(buf, PAGE_SIZE, "%u\n", value); \ +\ +exit: \ + mutex_unlock(&tcm_hcd->extif_mutex); \ +\ + return retval; \ +} \ +\ +static ssize_t syna_tcm_sysfs_##c_name##_store(struct device *dev, \ + struct device_attribute *attr, const char *buf, size_t count) \ +{ \ + int retval; \ + unsigned int input; \ + struct device *p_dev; \ + struct kobject *p_kobj; \ + struct syna_tcm_hcd *tcm_hcd; \ +\ + p_kobj = sysfs_dir->parent; \ + p_dev = container_of(p_kobj, struct device, kobj); \ + tcm_hcd = dev_get_drvdata(p_dev); \ +\ + if (kstrtouint(buf, 10, &input)) \ + return -EINVAL; \ +\ + mutex_lock(&tcm_hcd->extif_mutex); \ +\ + retval = tcm_hcd->set_dynamic_config(tcm_hcd, id, input); \ + if (retval < 0) { \ + LOGE(tcm_hcd->pdev->dev.parent, \ + "Failed to set dynamic config\n"); \ + goto exit; \ + } \ +\ + retval = count; \ +\ +exit: \ + mutex_unlock(&tcm_hcd->extif_mutex); \ +\ + return retval; \ +} + +DECLARE_COMPLETION(response_complete); + +static struct kobject *sysfs_dir; + +static struct syna_tcm_module_pool mod_pool; + +SHOW_PROTOTYPE(syna_tcm, info); +STORE_PROTOTYPE(syna_tcm, irq_en); +STORE_PROTOTYPE(syna_tcm, reset); +STORE_PROTOTYPE(syna_tcm, watchdog); +SHOW_STORE_PROTOTYPE(syna_tcm, no_doze); +SHOW_STORE_PROTOTYPE(syna_tcm, disable_noise_mitigation); +SHOW_STORE_PROTOTYPE(syna_tcm, inhibit_frequency_shift); +SHOW_STORE_PROTOTYPE(syna_tcm, requested_frequency); +SHOW_STORE_PROTOTYPE(syna_tcm, disable_hsync); +SHOW_STORE_PROTOTYPE(syna_tcm, rezero_on_exit_deep_sleep); +SHOW_STORE_PROTOTYPE(syna_tcm, charger_connected); +SHOW_STORE_PROTOTYPE(syna_tcm, no_baseline_relaxation); +SHOW_STORE_PROTOTYPE(syna_tcm, in_wakeup_gesture_mode); +SHOW_STORE_PROTOTYPE(syna_tcm, stimulus_fingers); +SHOW_STORE_PROTOTYPE(syna_tcm, grip_suppression_enabled); +SHOW_STORE_PROTOTYPE(syna_tcm, enable_thick_glove); +SHOW_STORE_PROTOTYPE(syna_tcm, enable_glove); + +static struct device_attribute *attrs[] = { + ATTRIFY(info), + ATTRIFY(irq_en), + ATTRIFY(reset), + ATTRIFY(watchdog), +}; + +static struct device_attribute *dynamic_config_attrs[] = { + ATTRIFY(no_doze), + ATTRIFY(disable_noise_mitigation), + ATTRIFY(inhibit_frequency_shift), + ATTRIFY(requested_frequency), + ATTRIFY(disable_hsync), + ATTRIFY(rezero_on_exit_deep_sleep), + ATTRIFY(charger_connected), + ATTRIFY(no_baseline_relaxation), + ATTRIFY(in_wakeup_gesture_mode), + ATTRIFY(stimulus_fingers), + ATTRIFY(grip_suppression_enabled), + ATTRIFY(enable_thick_glove), + ATTRIFY(enable_glove), +}; + +extern int touch_module_init(void); +extern void touch_module_exit(void); + +static ssize_t syna_tcm_sysfs_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + unsigned int count; + struct device *p_dev; + struct kobject *p_kobj; + struct syna_tcm_hcd *tcm_hcd; + + p_kobj = sysfs_dir->parent; + p_dev = container_of(p_kobj, struct device, kobj); + tcm_hcd = dev_get_drvdata(p_dev); + + mutex_lock(&tcm_hcd->extif_mutex); + + retval = tcm_hcd->identify(tcm_hcd, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do identification\n"); + goto exit; + } + + count = 0; + + retval = snprintf(buf, PAGE_SIZE - count, + "TouchComm version: %d\n", + tcm_hcd->id_info.version); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + if (SYNAPTICS_TCM_ID_SUBVERSION == 0) { + retval = snprintf(buf, PAGE_SIZE - count, + "Driver version: %d.%d\n", + (unsigned char)(SYNAPTICS_TCM_ID_VERSION >> 8), + (unsigned char)SYNAPTICS_TCM_ID_VERSION); + } else { + retval = snprintf(buf, PAGE_SIZE - count, + "Driver version: %d.%d.%d\n", + (unsigned char)(SYNAPTICS_TCM_ID_VERSION >> 8), + (unsigned char)SYNAPTICS_TCM_ID_VERSION, + SYNAPTICS_TCM_ID_SUBVERSION); + } + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + switch (tcm_hcd->id_info.mode) { + case MODE_APPLICATION: + retval = snprintf(buf, PAGE_SIZE - count, + "Firmware mode: Application\n"); + if (retval < 0) + goto exit; + break; + case MODE_HOST_DOWNLOAD: + retval = snprintf(buf, PAGE_SIZE - count, + "Firmware mode: Host Download\n"); + if (retval < 0) + goto exit; + break; + case MODE_BOOTLOADER: + retval = snprintf(buf, PAGE_SIZE - count, + "Firmware mode: Bootloader\n"); + if (retval < 0) + goto exit; + break; + case MODE_TDDI_BOOTLOADER: + retval = snprintf(buf, PAGE_SIZE - count, + "Firmware mode: TDDI Bootloader\n"); + if (retval < 0) + goto exit; + break; + default: + retval = snprintf(buf, PAGE_SIZE - count, + "Firmware mode: Unknown (%d)\n", + tcm_hcd->id_info.mode); + if (retval < 0) + goto exit; + break; + } + buf += retval; + count += retval; + + retval = snprintf(buf, PAGE_SIZE - count, + "Part number: "); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = secure_memcpy(buf, + PAGE_SIZE - count, + tcm_hcd->id_info.part_number, + sizeof(tcm_hcd->id_info.part_number), + sizeof(tcm_hcd->id_info.part_number)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy part number string\n"); + goto exit; + } + buf += sizeof(tcm_hcd->id_info.part_number); + count += sizeof(tcm_hcd->id_info.part_number); + + retval = snprintf(buf, PAGE_SIZE - count, + "\n"); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = snprintf(buf, PAGE_SIZE - count, + "Packrat number: %d\n", + tcm_hcd->packrat_number); + if (retval < 0) + goto exit; + + count += retval; + + retval = count; + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t syna_tcm_sysfs_irq_en_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned int input; + struct device *p_dev; + struct kobject *p_kobj; + struct syna_tcm_hcd *tcm_hcd; + + p_kobj = sysfs_dir->parent; + p_dev = container_of(p_kobj, struct device, kobj); + tcm_hcd = dev_get_drvdata(p_dev); + + if (kstrtouint(buf, 10, &input)) + return -EINVAL; + + mutex_lock(&tcm_hcd->extif_mutex); + + if (input == 0) { + retval = tcm_hcd->enable_irq(tcm_hcd, false, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to disable interrupt\n"); + goto exit; + } + } else if (input == 1) { + retval = tcm_hcd->enable_irq(tcm_hcd, true, NULL); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enable interrupt\n"); + goto exit; + } + } else { + retval = -EINVAL; + goto exit; + } + + retval = count; + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t syna_tcm_sysfs_reset_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + bool hw_reset; + unsigned int input; + struct device *p_dev; + struct kobject *p_kobj; + struct syna_tcm_hcd *tcm_hcd; + + p_kobj = sysfs_dir->parent; + p_dev = container_of(p_kobj, struct device, kobj); + tcm_hcd = dev_get_drvdata(p_dev); + + if (kstrtouint(buf, 10, &input)) + return -EINVAL; + + if (input == 1) + hw_reset = false; + else if (input == 2) + hw_reset = true; + else + return -EINVAL; + + mutex_lock(&tcm_hcd->extif_mutex); + + retval = tcm_hcd->reset(tcm_hcd, hw_reset, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + goto exit; + } + + retval = count; + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t syna_tcm_sysfs_watchdog_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int input; + struct device *p_dev; + struct kobject *p_kobj; + struct syna_tcm_hcd *tcm_hcd; + + p_kobj = sysfs_dir->parent; + p_dev = container_of(p_kobj, struct device, kobj); + tcm_hcd = dev_get_drvdata(p_dev); + + if (kstrtouint(buf, 10, &input)) + return -EINVAL; + + if (input != 0 && input != 1) + return -EINVAL; + + mutex_lock(&tcm_hcd->extif_mutex); + + tcm_hcd->watchdog.run = input; + tcm_hcd->update_watchdog(tcm_hcd, input); + + mutex_unlock(&tcm_hcd->extif_mutex); + + return count; +} + +dynamic_config_sysfs(no_doze, DC_NO_DOZE) + +dynamic_config_sysfs(disable_noise_mitigation, DC_DISABLE_NOISE_MITIGATION) + +dynamic_config_sysfs(inhibit_frequency_shift, DC_INHIBIT_FREQUENCY_SHIFT) + +dynamic_config_sysfs(requested_frequency, DC_REQUESTED_FREQUENCY) + +dynamic_config_sysfs(disable_hsync, DC_DISABLE_HSYNC) + +dynamic_config_sysfs(rezero_on_exit_deep_sleep, DC_REZERO_ON_EXIT_DEEP_SLEEP) + +dynamic_config_sysfs(charger_connected, DC_CHARGER_CONNECTED) + +dynamic_config_sysfs(no_baseline_relaxation, DC_NO_BASELINE_RELAXATION) + +dynamic_config_sysfs(in_wakeup_gesture_mode, DC_IN_WAKEUP_GESTURE_MODE) + +dynamic_config_sysfs(stimulus_fingers, DC_STIMULUS_FINGERS) + +dynamic_config_sysfs(grip_suppression_enabled, DC_GRIP_SUPPRESSION_ENABLED) + +dynamic_config_sysfs(enable_thick_glove, DC_ENABLE_THICK_GLOVE) + +dynamic_config_sysfs(enable_glove, DC_ENABLE_GLOVE) + +int syna_tcm_add_module(struct syna_tcm_module_cb *mod_cb, bool insert) +{ + struct syna_tcm_module_handler *mod_handler; + + if (!mod_pool.initialized) { + mutex_init(&mod_pool.mutex); + INIT_LIST_HEAD(&mod_pool.list); + mod_pool.initialized = true; + } + + mutex_lock(&mod_pool.mutex); + + if (insert) { + mod_handler = kzalloc(sizeof(*mod_handler), GFP_KERNEL); + if (!mod_handler) { + mutex_unlock(&mod_pool.mutex); + return -ENOMEM; + } + mod_handler->mod_cb = mod_cb; + mod_handler->insert = true; + mod_handler->detach = false; + list_add_tail(&mod_handler->link, &mod_pool.list); + } else if (!list_empty(&mod_pool.list)) { + list_for_each_entry(mod_handler, &mod_pool.list, link) { + if (mod_handler->mod_cb->type == mod_cb->type) { + mod_handler->insert = false; + mod_handler->detach = true; + goto exit; + } + } + } + +exit: + mutex_unlock(&mod_pool.mutex); + + if (mod_pool.queue_work) + queue_work(mod_pool.workqueue, &mod_pool.work); + + return 0; +} +EXPORT_SYMBOL(syna_tcm_add_module); + +static void syna_tcm_module_work(struct work_struct *work) +{ + struct syna_tcm_module_handler *mod_handler; + struct syna_tcm_module_handler *tmp_handler; + struct syna_tcm_hcd *tcm_hcd = mod_pool.tcm_hcd; + + mutex_lock(&mod_pool.mutex); + mod_pool.reconstructing = true; + + if (!list_empty(&mod_pool.list)) { + list_for_each_entry_safe(mod_handler, + tmp_handler, + &mod_pool.list, + link) { + if (mod_handler->insert) { + if (mod_handler->mod_cb->init) + mod_handler->mod_cb->init(tcm_hcd); + mod_handler->insert = false; + } + if (mod_handler->detach) { + if (mod_handler->mod_cb->remove) + mod_handler->mod_cb->remove(tcm_hcd); + list_del(&mod_handler->link); + kfree(mod_handler); + } + } + } + + mod_pool.reconstructing = false; + mutex_unlock(&mod_pool.mutex); +} + +/** + * syna_tcm_report_notifier() - notify occurrence of report received from device + * + * @data: handle of core module + * + * The occurrence of the report generated by the device is forwarded to the + * asynchronous inbox of each registered application module. + */ +static int syna_tcm_report_notifier(void *data) +{ + struct sched_param param = { .sched_priority = NOTIFIER_PRIORITY }; + struct syna_tcm_module_handler *mod_handler; + struct syna_tcm_hcd *tcm_hcd = data; + + sched_setscheduler_nocheck(current, SCHED_RR, ¶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); + mod_pool.reconstructing = true; + + if (!list_empty(&mod_pool.list)) { + list_for_each_entry(mod_handler, &mod_pool.list, link) { + if (!mod_handler->insert && + !mod_handler->detach && + (mod_handler->mod_cb->asyncbox)) + mod_handler->mod_cb->asyncbox(tcm_hcd); + } + } + + mod_pool.reconstructing = false; + mutex_unlock(&mod_pool.mutex); + + set_current_state(TASK_INTERRUPTIBLE); + }; + + return 0; +} + +/** + * syna_tcm_dispatch_report() - dispatch report received from device + * + * @tcm_hcd: handle of core module + * + * The report generated by the device is forwarded to the synchronous inbox of + * each registered application module for further processing. In addition, the + * report notifier thread is woken up for asynchronous notification of the + * report occurrence. + */ +static void syna_tcm_dispatch_report(struct syna_tcm_hcd *tcm_hcd) +{ + struct syna_tcm_module_handler *mod_handler; + + LOCK_BUFFER(tcm_hcd->in); + LOCK_BUFFER(tcm_hcd->report.buffer); + + tcm_hcd->report.buffer.buf = &tcm_hcd->in.buf[MESSAGE_HEADER_SIZE]; + + tcm_hcd->report.buffer.buf_size = tcm_hcd->in.buf_size; + tcm_hcd->report.buffer.buf_size -= MESSAGE_HEADER_SIZE; + + tcm_hcd->report.buffer.data_length = tcm_hcd->payload_length; + + tcm_hcd->report.id = tcm_hcd->status_report_code; + + if (!mutex_trylock(&mod_pool.mutex)) { + LOGI(tcm_hcd->pdev->dev.parent, "unable to acquire mod_pool.mutex\n"); + UNLOCK_BUFFER(tcm_hcd->report.buffer); + UNLOCK_BUFFER(tcm_hcd->in); + return; + } + + if (!list_empty(&mod_pool.list)) { + list_for_each_entry(mod_handler, &mod_pool.list, link) { + if (!mod_handler->insert && + !mod_handler->detach && + (mod_handler->mod_cb->syncbox)) + mod_handler->mod_cb->syncbox(tcm_hcd); + } + } + + tcm_hcd->async_report_id = tcm_hcd->status_report_code; + + mutex_unlock(&mod_pool.mutex); + + UNLOCK_BUFFER(tcm_hcd->report.buffer); + UNLOCK_BUFFER(tcm_hcd->in); + + wake_up_process(tcm_hcd->notifier_thread); +} + +/** + * syna_tcm_dispatch_response() - dispatch response received from device + * + * @tcm_hcd: handle of core module + * + * The response to a command is forwarded to the sender of the command. + */ +static void syna_tcm_dispatch_response(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + + if (atomic_read(&tcm_hcd->command_status) != CMD_BUSY) + return; + + tcm_hcd->response_code = tcm_hcd->status_report_code; + + if (tcm_hcd->payload_length == 0) { + atomic_set(&tcm_hcd->command_status, CMD_IDLE); + goto exit; + } + + LOCK_BUFFER(tcm_hcd->resp); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &tcm_hcd->resp, + tcm_hcd->payload_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for tcm_hcd->resp.buf\n"); + UNLOCK_BUFFER(tcm_hcd->resp); + atomic_set(&tcm_hcd->command_status, CMD_ERROR); + goto exit; + } + + LOCK_BUFFER(tcm_hcd->in); + + retval = secure_memcpy(tcm_hcd->resp.buf, + tcm_hcd->resp.buf_size, + &tcm_hcd->in.buf[MESSAGE_HEADER_SIZE], + tcm_hcd->in.buf_size - MESSAGE_HEADER_SIZE, + tcm_hcd->payload_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy payload\n"); + UNLOCK_BUFFER(tcm_hcd->in); + UNLOCK_BUFFER(tcm_hcd->resp); + atomic_set(&tcm_hcd->command_status, CMD_ERROR); + goto exit; + } + + tcm_hcd->resp.data_length = tcm_hcd->payload_length; + + UNLOCK_BUFFER(tcm_hcd->in); + UNLOCK_BUFFER(tcm_hcd->resp); + + atomic_set(&tcm_hcd->command_status, CMD_IDLE); + +exit: + complete(&response_complete); +} + +/** + * syna_tcm_dispatch_message() - dispatch message received from device + * + * @tcm_hcd: handle of core module + * + * The information received in the message read in from the device is dispatched + * to the appropriate destination based on whether the information represents a + * report or a response to a command. + */ +static void syna_tcm_dispatch_message(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + unsigned char *build_id; + unsigned int payload_length; + unsigned int max_write_size; + + if (tcm_hcd->status_report_code == REPORT_IDENTIFY) { + payload_length = tcm_hcd->payload_length; + + LOCK_BUFFER(tcm_hcd->in); + + retval = secure_memcpy((unsigned char *)&tcm_hcd->id_info, + sizeof(tcm_hcd->id_info), + &tcm_hcd->in.buf[MESSAGE_HEADER_SIZE], + tcm_hcd->in.buf_size - MESSAGE_HEADER_SIZE, + MIN(sizeof(tcm_hcd->id_info), payload_length)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy identification info\n"); + UNLOCK_BUFFER(tcm_hcd->in); + return; + } + + UNLOCK_BUFFER(tcm_hcd->in); + + build_id = tcm_hcd->id_info.build_id; + tcm_hcd->packrat_number = le4_to_uint(build_id); + + max_write_size = le2_to_uint(tcm_hcd->id_info.max_write_size); + tcm_hcd->wr_chunk_size = MIN(max_write_size, WR_CHUNK_SIZE); + if (tcm_hcd->wr_chunk_size == 0) + tcm_hcd->wr_chunk_size = max_write_size; + + LOGD(tcm_hcd->pdev->dev.parent, + "Received identify report (firmware mode = 0x%02x)\n", + tcm_hcd->id_info.mode); + + if (atomic_read(&tcm_hcd->command_status) == CMD_BUSY) { + switch (tcm_hcd->command) { + case CMD_RESET: + case CMD_RUN_BOOTLOADER_FIRMWARE: + case CMD_RUN_APPLICATION_FIRMWARE: + case CMD_ENTER_PRODUCTION_TEST_MODE: + tcm_hcd->response_code = STATUS_OK; + atomic_set(&tcm_hcd->command_status, CMD_IDLE); + complete(&response_complete); + break; + default: + LOGN(tcm_hcd->pdev->dev.parent, + "Device has been reset\n"); + atomic_set(&tcm_hcd->command_status, CMD_ERROR); + complete(&response_complete); + break; + } + } + + if (tcm_hcd->id_info.mode == MODE_HOST_DOWNLOAD) { + tcm_hcd->host_download_mode = true; + return; + } + +#ifdef FORCE_RUN_APPLICATION_FIRMWARE + if (tcm_hcd->id_info.mode != MODE_APPLICATION && + !mutex_is_locked(&tcm_hcd->reset_mutex)) { + if (atomic_read(&tcm_hcd->helper.task) == HELP_NONE) { + atomic_set(&tcm_hcd->helper.task, + HELP_RUN_APPLICATION_FIRMWARE); + queue_work(tcm_hcd->helper.workqueue, + &tcm_hcd->helper.work); + return; + } + } +#endif + } + + if (tcm_hcd->status_report_code >= REPORT_IDENTIFY) { + if ((mod_pool.reconstructing) + && (tcm_hcd->status_report_code == REPORT_TOUCH)) + return; + syna_tcm_dispatch_report(tcm_hcd); + + } else + syna_tcm_dispatch_response(tcm_hcd); + +} + +/** + * syna_tcm_continued_read() - retrieve entire payload from device + * + * @tcm_hcd: handle of core module + * + * Read transactions are carried out until the entire payload is retrieved from + * the device and stored in the handle of the core module. + */ +static int syna_tcm_continued_read(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + unsigned char marker; + unsigned char code; + unsigned int idx; + unsigned int offset; + unsigned int chunks; + unsigned int chunk_space; + unsigned int xfer_length; + unsigned int total_length; + unsigned int remaining_length; + + total_length = MESSAGE_HEADER_SIZE + tcm_hcd->payload_length + 1; + + remaining_length = total_length - tcm_hcd->read_length; + + LOCK_BUFFER(tcm_hcd->in); + + retval = syna_tcm_realloc_mem(tcm_hcd, + &tcm_hcd->in, + total_length + 1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to reallocate memory for tcm_hcd->in.buf\n"); + UNLOCK_BUFFER(tcm_hcd->in); + return retval; + } + + /** + * available chunk space for payload = total chunk size minus header + * marker byte and header code byte + */ + if (tcm_hcd->rd_chunk_size == 0) + chunk_space = remaining_length; + else + chunk_space = tcm_hcd->rd_chunk_size - 2; + + chunks = ceil_div(remaining_length, chunk_space); + + chunks = chunks == 0 ? 1 : chunks; + + offset = tcm_hcd->read_length; + + LOCK_BUFFER(tcm_hcd->temp); + + for (idx = 0; idx < chunks; idx++) { + if (remaining_length > chunk_space) + xfer_length = chunk_space; + else + xfer_length = remaining_length; + + if (xfer_length == 1) { + tcm_hcd->in.buf[offset] = MESSAGE_PADDING; + offset += xfer_length; + remaining_length -= xfer_length; + continue; + } + + retval = syna_tcm_alloc_mem(tcm_hcd, + &tcm_hcd->temp, + xfer_length + 2); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for temp.buf\n"); + UNLOCK_BUFFER(tcm_hcd->temp); + UNLOCK_BUFFER(tcm_hcd->in); + return retval; + } + + retval = syna_tcm_read(tcm_hcd, + tcm_hcd->temp.buf, + xfer_length + 2); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read from device\n"); + UNLOCK_BUFFER(tcm_hcd->temp); + UNLOCK_BUFFER(tcm_hcd->in); + return retval; + } + + marker = tcm_hcd->temp.buf[0]; + code = tcm_hcd->temp.buf[1]; + + if (marker != MESSAGE_MARKER) { + LOGE(tcm_hcd->pdev->dev.parent, + "Incorrect header marker (0x%02x)\n", + marker); + UNLOCK_BUFFER(tcm_hcd->temp); + UNLOCK_BUFFER(tcm_hcd->in); + return -EIO; + } + + if (code != STATUS_CONTINUED_READ) { + LOGE(tcm_hcd->pdev->dev.parent, + "Incorrect header code (0x%02x)\n", + code); + UNLOCK_BUFFER(tcm_hcd->temp); + UNLOCK_BUFFER(tcm_hcd->in); + return -EIO; + } + + retval = secure_memcpy(&tcm_hcd->in.buf[offset], + tcm_hcd->in.buf_size - offset, + &tcm_hcd->temp.buf[2], + tcm_hcd->temp.buf_size - 2, + xfer_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy payload\n"); + UNLOCK_BUFFER(tcm_hcd->temp); + UNLOCK_BUFFER(tcm_hcd->in); + return retval; + } + + offset += xfer_length; + + remaining_length -= xfer_length; + } + + UNLOCK_BUFFER(tcm_hcd->temp); + UNLOCK_BUFFER(tcm_hcd->in); + + return 0; +} + +/** + * syna_tcm_raw_read() - retrieve specific number of data bytes from device + * + * @tcm_hcd: handle of core module + * @in_buf: buffer for storing data retrieved from device + * @length: number of bytes to retrieve from device + * + * Read transactions are carried out until the specific number of data bytes + * are retrieved from the device and stored in in_buf. + */ +static int syna_tcm_raw_read(struct syna_tcm_hcd *tcm_hcd, + unsigned char *in_buf, unsigned int length) +{ + int retval; + unsigned char code; + unsigned int idx; + unsigned int offset; + unsigned int chunks; + unsigned int chunk_space; + unsigned int xfer_length; + unsigned int remaining_length; + + if (length < 2) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid length information\n"); + return -EINVAL; + } + + /* minus header marker byte and header code byte */ + remaining_length = length - 2; + + /** + * available chunk space for data = total chunk size minus header + * marker byte and header code byte + */ + if (tcm_hcd->rd_chunk_size == 0) + chunk_space = remaining_length; + else + chunk_space = tcm_hcd->rd_chunk_size - 2; + + chunks = ceil_div(remaining_length, chunk_space); + + chunks = chunks == 0 ? 1 : chunks; + + offset = 0; + + LOCK_BUFFER(tcm_hcd->temp); + + for (idx = 0; idx < chunks; idx++) { + if (remaining_length > chunk_space) + xfer_length = chunk_space; + else + xfer_length = remaining_length; + + if (xfer_length == 1) { + in_buf[offset] = MESSAGE_PADDING; + offset += xfer_length; + remaining_length -= xfer_length; + continue; + } + + retval = syna_tcm_alloc_mem(tcm_hcd, + &tcm_hcd->temp, + xfer_length + 2); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for temp.buf\n"); + UNLOCK_BUFFER(tcm_hcd->temp); + return retval; + } + + retval = syna_tcm_read(tcm_hcd, + tcm_hcd->temp.buf, + xfer_length + 2); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read from device\n"); + UNLOCK_BUFFER(tcm_hcd->temp); + return retval; + } + + code = tcm_hcd->temp.buf[1]; + + if (idx == 0) { + retval = secure_memcpy(&in_buf[0], + length, + &tcm_hcd->temp.buf[0], + tcm_hcd->temp.buf_size, + xfer_length + 2); + } else { + if (code != STATUS_CONTINUED_READ) { + LOGE(tcm_hcd->pdev->dev.parent, + "Incorrect header code (0x%02x)\n", + code); + UNLOCK_BUFFER(tcm_hcd->temp); + return -EIO; + } + + retval = secure_memcpy(&in_buf[offset], + length - offset, + &tcm_hcd->temp.buf[2], + tcm_hcd->temp.buf_size - 2, + xfer_length); + } + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy data\n"); + UNLOCK_BUFFER(tcm_hcd->temp); + return retval; + } + + if (idx == 0) + offset += (xfer_length + 2); + else + offset += xfer_length; + + remaining_length -= xfer_length; + } + + UNLOCK_BUFFER(tcm_hcd->temp); + + return 0; +} + +/** + * syna_tcm_raw_write() - write command/data to device without receiving + * response + * + * @tcm_hcd: handle of core module + * @command: command to send to device + * @data: data to send to device + * @length: length of data in bytes + * + * A command and its data, if any, are sent to the device. + */ +static int syna_tcm_raw_write(struct syna_tcm_hcd *tcm_hcd, + unsigned char command, unsigned char *data, unsigned int length) +{ + int retval; + unsigned int idx; + unsigned int chunks; + unsigned int chunk_space; + unsigned int xfer_length; + unsigned int remaining_length; + + remaining_length = length; + + /** + * available chunk space for data = total chunk size minus command + * byte + */ + if (tcm_hcd->wr_chunk_size == 0) + chunk_space = remaining_length; + else + chunk_space = tcm_hcd->wr_chunk_size - 1; + + chunks = ceil_div(remaining_length, chunk_space); + + chunks = chunks == 0 ? 1 : chunks; + + LOCK_BUFFER(tcm_hcd->out); + + for (idx = 0; idx < chunks; idx++) { + if (remaining_length > chunk_space) + xfer_length = chunk_space; + else + xfer_length = remaining_length; + + retval = syna_tcm_alloc_mem(tcm_hcd, + &tcm_hcd->out, + xfer_length + 1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for out.buf\n"); + UNLOCK_BUFFER(tcm_hcd->out); + return retval; + } + + if (idx == 0) + tcm_hcd->out.buf[0] = command; + else + tcm_hcd->out.buf[0] = CMD_CONTINUE_WRITE; + + if (xfer_length) { + retval = secure_memcpy(&tcm_hcd->out.buf[1], + tcm_hcd->out.buf_size - 1, + &data[idx * chunk_space], + remaining_length, + xfer_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy data\n"); + UNLOCK_BUFFER(tcm_hcd->out); + return retval; + } + } + + retval = syna_tcm_write(tcm_hcd, + tcm_hcd->out.buf, + xfer_length + 1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write to device\n"); + UNLOCK_BUFFER(tcm_hcd->out); + return retval; + } + + remaining_length -= xfer_length; + } + + UNLOCK_BUFFER(tcm_hcd->out); + + return 0; +} + +/** + * syna_tcm_read_message() - read message from device + * + * @tcm_hcd: handle of core module + * @in_buf: buffer for storing data in raw read mode + * @length: length of data in bytes in raw read mode + * + * If in_buf is not NULL, raw read mode is used and syna_tcm_raw_read() is + * called. Otherwise, a message including its entire payload is retrieved from + * the device and dispatched to the appropriate destination. + */ +static int syna_tcm_read_message(struct syna_tcm_hcd *tcm_hcd, + unsigned char *in_buf, unsigned int length) +{ + int retval; + bool retry; + unsigned int total_length; + struct syna_tcm_message_header *header; + + mutex_lock(&tcm_hcd->rw_ctrl_mutex); + + if (in_buf != NULL) { + retval = syna_tcm_raw_read(tcm_hcd, in_buf, length); + goto exit; + } + + retry = true; + +retry: + LOCK_BUFFER(tcm_hcd->in); + + retval = syna_tcm_read(tcm_hcd, + tcm_hcd->in.buf, + tcm_hcd->read_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read from device\n"); + UNLOCK_BUFFER(tcm_hcd->in); + if (retry) { + usleep_range(READ_RETRY_US_MIN, READ_RETRY_US_MAX); + retry = false; + goto retry; + } + goto exit; + } + + header = (struct syna_tcm_message_header *)tcm_hcd->in.buf; + + if (header->marker != MESSAGE_MARKER) { + LOGE(tcm_hcd->pdev->dev.parent, + "Incorrect header marker (0x%02x)\n", + header->marker); + UNLOCK_BUFFER(tcm_hcd->in); + retval = -ENXIO; + if (retry) { + usleep_range(READ_RETRY_US_MIN, READ_RETRY_US_MAX); + retry = false; + goto retry; + } + goto exit; + } + + tcm_hcd->status_report_code = header->code; + + tcm_hcd->payload_length = le2_to_uint(header->length); + + LOGD(tcm_hcd->pdev->dev.parent, + "Header code = 0x%02x\n", + tcm_hcd->status_report_code); + + LOGD(tcm_hcd->pdev->dev.parent, + "Payload length = %d\n", + tcm_hcd->payload_length); + + if (tcm_hcd->status_report_code <= STATUS_ERROR || + tcm_hcd->status_report_code == STATUS_INVALID) { + switch (tcm_hcd->status_report_code) { + case STATUS_OK: + break; + case STATUS_CONTINUED_READ: + LOGD(tcm_hcd->pdev->dev.parent, + "Out-of-sync continued read\n"); + fallthrough; + case STATUS_IDLE: + case STATUS_BUSY: + tcm_hcd->payload_length = 0; + UNLOCK_BUFFER(tcm_hcd->in); + retval = 0; + goto exit; + default: + LOGE(tcm_hcd->pdev->dev.parent, + "Incorrect header code (0x%02x)\n", + tcm_hcd->status_report_code); + if (tcm_hcd->status_report_code == STATUS_INVALID) { + if (retry) { + usleep_range(READ_RETRY_US_MIN, + READ_RETRY_US_MAX); + retry = false; + goto retry; + } else { + tcm_hcd->payload_length = 0; + } + } + } + } + + total_length = MESSAGE_HEADER_SIZE + tcm_hcd->payload_length + 1; + +#ifdef PREDICTIVE_READING + if (total_length <= tcm_hcd->read_length) { + goto check_padding; + } else if (total_length - 1 == tcm_hcd->read_length) { + tcm_hcd->in.buf[total_length - 1] = MESSAGE_PADDING; + goto check_padding; + } +#else + if (tcm_hcd->payload_length == 0) { + tcm_hcd->in.buf[total_length - 1] = MESSAGE_PADDING; + goto check_padding; + } +#endif + + UNLOCK_BUFFER(tcm_hcd->in); + + retval = syna_tcm_continued_read(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do continued read\n"); + goto exit; + }; + + LOCK_BUFFER(tcm_hcd->in); + + tcm_hcd->in.buf[0] = MESSAGE_MARKER; + tcm_hcd->in.buf[1] = tcm_hcd->status_report_code; + tcm_hcd->in.buf[2] = (unsigned char)tcm_hcd->payload_length; + tcm_hcd->in.buf[3] = (unsigned char)(tcm_hcd->payload_length >> 8); + +check_padding: + if (tcm_hcd->in.buf[total_length - 1] != MESSAGE_PADDING) { + LOGE(tcm_hcd->pdev->dev.parent, + "Incorrect message padding byte (0x%02x)\n", + tcm_hcd->in.buf[total_length - 1]); + UNLOCK_BUFFER(tcm_hcd->in); + retval = -EIO; + goto exit; + } + + UNLOCK_BUFFER(tcm_hcd->in); + +#ifdef PREDICTIVE_READING + total_length = MAX(total_length, MIN_READ_LENGTH); + tcm_hcd->read_length = MIN(total_length, tcm_hcd->rd_chunk_size); + if (tcm_hcd->rd_chunk_size == 0) + tcm_hcd->read_length = total_length; +#endif + + mutex_unlock(&tcm_hcd->rw_ctrl_mutex); + + syna_tcm_dispatch_message(tcm_hcd); + + retval = 0; + + return retval; + +exit: + if (retval < 0) { + if (atomic_read(&tcm_hcd->command_status) == CMD_BUSY) { + atomic_set(&tcm_hcd->command_status, CMD_ERROR); + complete(&response_complete); + } + } + + mutex_unlock(&tcm_hcd->rw_ctrl_mutex); + + return retval; +} + +/** + * syna_tcm_write_message() - write message to device and receive response + * + * @tcm_hcd: handle of core module + * @command: command to send to device + * @payload: payload of command + * @length: length of payload in bytes + * @resp_buf: buffer for storing command response + * @resp_buf_size: size of response buffer in bytes + * @resp_length: length of command response in bytes + * @response_code: status code returned in command response + * @polling_delay_ms: delay time after sending command before resuming polling + * + * If resp_buf is NULL, raw write mode is used and syna_tcm_raw_write() is + * called. Otherwise, a command and its payload, if any, are sent to the device + * and the response to the command generated by the device is read in. + */ +static int syna_tcm_write_message(struct syna_tcm_hcd *tcm_hcd, + unsigned char command, unsigned char *payload, + unsigned int length, unsigned char **resp_buf, + unsigned int *resp_buf_size, unsigned int *resp_length, + unsigned char *response_code, unsigned int polling_delay_ms) +{ + int retval; + unsigned int idx; + unsigned int chunks; + unsigned int chunk_space; + unsigned int xfer_length; + unsigned int remaining_length; + unsigned int command_status; + + if (response_code != NULL) + *response_code = STATUS_INVALID; + + if (!tcm_hcd->do_polling && current->pid == tcm_hcd->isr_pid) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid execution context\n"); + return -EINVAL; + } + + mutex_lock(&tcm_hcd->command_mutex); + + mutex_lock(&tcm_hcd->rw_ctrl_mutex); + + if (resp_buf == NULL) { + retval = syna_tcm_raw_write(tcm_hcd, command, payload, length); + mutex_unlock(&tcm_hcd->rw_ctrl_mutex); + goto exit; + } + + if (tcm_hcd->do_polling && polling_delay_ms) { + cancel_delayed_work_sync(&tcm_hcd->polling_work); + flush_workqueue(tcm_hcd->polling_workqueue); + } + + atomic_set(&tcm_hcd->command_status, CMD_BUSY); + + reinit_completion(&response_complete); + + tcm_hcd->command = command; + + LOCK_BUFFER(tcm_hcd->resp); + + tcm_hcd->resp.buf = *resp_buf; + tcm_hcd->resp.buf_size = *resp_buf_size; + tcm_hcd->resp.data_length = 0; + + UNLOCK_BUFFER(tcm_hcd->resp); + + /* adding two length bytes as part of payload */ + remaining_length = length + 2; + + /** + * available chunk space for payload = total chunk size minus command + * byte + */ + if (tcm_hcd->wr_chunk_size == 0) + chunk_space = remaining_length; + else + chunk_space = tcm_hcd->wr_chunk_size - 1; + + chunks = ceil_div(remaining_length, chunk_space); + + chunks = chunks == 0 ? 1 : chunks; + + LOGD(tcm_hcd->pdev->dev.parent, + "Command = 0x%02x\n", + command); + + LOCK_BUFFER(tcm_hcd->out); + + for (idx = 0; idx < chunks; idx++) { + if (remaining_length > chunk_space) + xfer_length = chunk_space; + else + xfer_length = remaining_length; + + retval = syna_tcm_alloc_mem(tcm_hcd, + &tcm_hcd->out, + xfer_length + 1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for out.buf\n"); + UNLOCK_BUFFER(tcm_hcd->out); + mutex_unlock(&tcm_hcd->rw_ctrl_mutex); + goto exit; + } + + if (idx == 0) { + tcm_hcd->out.buf[0] = command; + tcm_hcd->out.buf[1] = (unsigned char)length; + tcm_hcd->out.buf[2] = (unsigned char)(length >> 8); + + if (xfer_length > 2) { + retval = secure_memcpy(&tcm_hcd->out.buf[3], + tcm_hcd->out.buf_size - 3, + payload, + remaining_length - 2, + xfer_length - 2); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy payload\n"); + UNLOCK_BUFFER(tcm_hcd->out); + mutex_unlock(&tcm_hcd->rw_ctrl_mutex); + goto exit; + } + } + } else { + tcm_hcd->out.buf[0] = CMD_CONTINUE_WRITE; + + retval = secure_memcpy(&tcm_hcd->out.buf[1], + tcm_hcd->out.buf_size - 1, + &payload[idx * chunk_space - 2], + remaining_length, + xfer_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy payload\n"); + UNLOCK_BUFFER(tcm_hcd->out); + mutex_unlock(&tcm_hcd->rw_ctrl_mutex); + goto exit; + } + } + + retval = syna_tcm_write(tcm_hcd, + tcm_hcd->out.buf, + xfer_length + 1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write to device\n"); + UNLOCK_BUFFER(tcm_hcd->out); + mutex_unlock(&tcm_hcd->rw_ctrl_mutex); + goto exit; + } + + remaining_length -= xfer_length; + + if (chunks > 1) + usleep_range(WRITE_DELAY_US_MIN, WRITE_DELAY_US_MAX); + } + + UNLOCK_BUFFER(tcm_hcd->out); + + mutex_unlock(&tcm_hcd->rw_ctrl_mutex); + + if (tcm_hcd->do_polling && polling_delay_ms) { + queue_delayed_work(tcm_hcd->polling_workqueue, + &tcm_hcd->polling_work, + msecs_to_jiffies(polling_delay_ms)); + } + + retval = wait_for_completion_timeout(&response_complete, + msecs_to_jiffies(RESPONSE_TIMEOUT_MS)); + if (retval == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Timed out waiting for response (command 0x%02x)\n", + tcm_hcd->command); + retval = -EIO; + goto exit; + } + + command_status = atomic_read(&tcm_hcd->command_status); + if (command_status != CMD_IDLE) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get valid response (command 0x%02x)\n", + tcm_hcd->command); + retval = -EIO; + goto exit; + } + + LOCK_BUFFER(tcm_hcd->resp); + + if (tcm_hcd->response_code != STATUS_OK) { + if (tcm_hcd->resp.data_length) { + LOGE(tcm_hcd->pdev->dev.parent, + "Error code = 0x%02x (command 0x%02x)\n", + tcm_hcd->resp.buf[0], tcm_hcd->command); + } + retval = -EIO; + } else { + retval = 0; + } + + *resp_buf = tcm_hcd->resp.buf; + *resp_buf_size = tcm_hcd->resp.buf_size; + *resp_length = tcm_hcd->resp.data_length; + + if (response_code != NULL) + *response_code = tcm_hcd->response_code; + + UNLOCK_BUFFER(tcm_hcd->resp); + +exit: + tcm_hcd->command = CMD_NONE; + + atomic_set(&tcm_hcd->command_status, CMD_IDLE); + + mutex_unlock(&tcm_hcd->command_mutex); + + return retval; +} + +static int syna_tcm_wait_hdl(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + + msleep(HOST_DOWNLOAD_WAIT_MS); + + if (!atomic_read(&tcm_hcd->host_downloading)) + return 0; + + retval = wait_event_interruptible_timeout(tcm_hcd->hdl_wq, + !atomic_read(&tcm_hcd->host_downloading), + msecs_to_jiffies(HOST_DOWNLOAD_TIMEOUT_MS)); + if (retval == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Timed out waiting for completion of host download\n"); + atomic_set(&tcm_hcd->host_downloading, 0); + retval = -EIO; + } else { + retval = 0; + } + + return retval; +} + +static void syna_tcm_check_hdl(struct syna_tcm_hcd *tcm_hcd) +{ + struct syna_tcm_module_handler *mod_handler; + + LOCK_BUFFER(tcm_hcd->report.buffer); + + tcm_hcd->report.buffer.buf = NULL; + tcm_hcd->report.buffer.buf_size = 0; + tcm_hcd->report.buffer.data_length = 0; + tcm_hcd->report.id = REPORT_HDL; + + UNLOCK_BUFFER(tcm_hcd->report.buffer); + + mutex_lock(&mod_pool.mutex); + + if (!list_empty(&mod_pool.list)) { + list_for_each_entry(mod_handler, &mod_pool.list, link) { + if (!mod_handler->insert && + !mod_handler->detach && + (mod_handler->mod_cb->syncbox)) + mod_handler->mod_cb->syncbox(tcm_hcd); + } + } + + mutex_unlock(&mod_pool.mutex); +} + +static void syna_tcm_update_watchdog(struct syna_tcm_hcd *tcm_hcd, bool en) +{ + cancel_delayed_work_sync(&tcm_hcd->watchdog.work); + flush_workqueue(tcm_hcd->watchdog.workqueue); + + if (!tcm_hcd->watchdog.run) { + tcm_hcd->watchdog.count = 0; + return; + } + + if (en) { + queue_delayed_work(tcm_hcd->watchdog.workqueue, + &tcm_hcd->watchdog.work, + msecs_to_jiffies(WATCHDOG_DELAY_MS)); + } else { + tcm_hcd->watchdog.count = 0; + } +} + +static void syna_tcm_watchdog_work(struct work_struct *work) +{ + int retval; + struct delayed_work *delayed_work = + container_of(work, struct delayed_work, work); + struct syna_tcm_watchdog *watchdog = + container_of(delayed_work, struct syna_tcm_watchdog, + work); + struct syna_tcm_hcd *tcm_hcd = + container_of(watchdog, struct syna_tcm_hcd, watchdog); + + if (mutex_is_locked(&tcm_hcd->rw_ctrl_mutex)) + goto exit; + + mutex_lock(&tcm_hcd->rw_ctrl_mutex); + + retval = syna_tcm_read(tcm_hcd, + &tcm_hcd->marker, + 1); + + mutex_unlock(&tcm_hcd->rw_ctrl_mutex); + + if (retval < 0 || tcm_hcd->marker != MESSAGE_MARKER) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read from device\n"); + + tcm_hcd->watchdog.count++; + + if (tcm_hcd->watchdog.count >= WATCHDOG_TRIGGER_COUNT) { + retval = tcm_hcd->reset(tcm_hcd, true, false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + } + tcm_hcd->watchdog.count = 0; + } + } + +exit: + queue_delayed_work(tcm_hcd->watchdog.workqueue, + &tcm_hcd->watchdog.work, + msecs_to_jiffies(WATCHDOG_DELAY_MS)); +} + +static void syna_tcm_polling_work(struct work_struct *work) +{ + int retval; + struct delayed_work *delayed_work = + container_of(work, struct delayed_work, work); + struct syna_tcm_hcd *tcm_hcd = + container_of(delayed_work, struct syna_tcm_hcd, + polling_work); + + if (!tcm_hcd->do_polling) + return; + + retval = tcm_hcd->read_message(tcm_hcd, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read message\n"); + if (retval == -ENXIO && tcm_hcd->hw_if->bus_io->type == BUS_SPI) + syna_tcm_check_hdl(tcm_hcd); + } + + if (!(tcm_hcd->in_suspend && retval < 0)) { + queue_delayed_work(tcm_hcd->polling_workqueue, + &tcm_hcd->polling_work, + msecs_to_jiffies(POLLING_DELAY_MS)); + } +} + +static irqreturn_t syna_tcm_isr(int irq, void *data) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = data; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + if (unlikely(gpio_get_value(bdata->irq_gpio) != bdata->irq_on_state)) + goto exit; + + tcm_hcd->isr_pid = current->pid; + + retval = tcm_hcd->read_message(tcm_hcd, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, "Failed to read message\n"); + if (retval == -ENXIO && + tcm_hcd->hw_if->bus_io->type == BUS_SPI) + syna_tcm_check_hdl(tcm_hcd); + } + +exit: + return IRQ_HANDLED; +} + +static int syna_tcm_enable_irq(struct syna_tcm_hcd *tcm_hcd, bool en, bool ns) +{ + int retval; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + static bool irq_freed = true; + + mutex_lock(&tcm_hcd->irq_en_mutex); + + if (en) { + if (tcm_hcd->irq_enabled) { + LOGD(tcm_hcd->pdev->dev.parent, + "Interrupt already enabled\n"); + retval = 0; + goto exit; + } + + if (bdata->irq_gpio < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid IRQ GPIO\n"); + retval = -EINVAL; + goto queue_polling_work; + } + + if (irq_freed) { + retval = request_threaded_irq(tcm_hcd->irq, NULL, + syna_tcm_isr, bdata->irq_flags, + PLATFORM_DRIVER_NAME, tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create interrupt thread\n"); + } + } else { + enable_irq(tcm_hcd->irq); + retval = 0; + } + +queue_polling_work: + if (retval < 0) { +#ifdef FALL_BACK_ON_POLLING + queue_delayed_work(tcm_hcd->polling_workqueue, + &tcm_hcd->polling_work, + msecs_to_jiffies(POLLING_DELAY_MS)); + tcm_hcd->do_polling = true; + retval = 0; +#endif + } + + if (retval < 0) + goto exit; + else + msleep(ENABLE_IRQ_DELAY_MS); + } else { + if (!tcm_hcd->irq_enabled) { + LOGD(tcm_hcd->pdev->dev.parent, + "Interrupt already disabled\n"); + retval = 0; + goto exit; + } + + if (bdata->irq_gpio >= 0) { + if (ns) { + disable_irq_nosync(tcm_hcd->irq); + } else { + disable_irq(tcm_hcd->irq); + free_irq(tcm_hcd->irq, tcm_hcd); + } + irq_freed = !ns; + } + + if (ns) { + cancel_delayed_work(&tcm_hcd->polling_work); + } else { + cancel_delayed_work_sync(&tcm_hcd->polling_work); + flush_workqueue(tcm_hcd->polling_workqueue); + } + + tcm_hcd->do_polling = false; + } + + retval = 0; + +exit: + if (retval == 0) + tcm_hcd->irq_enabled = en; + + mutex_unlock(&tcm_hcd->irq_en_mutex); + + return retval; +} + +static int syna_tcm_set_gpio(struct syna_tcm_hcd *tcm_hcd, int gpio, + bool config, int dir, int state) +{ + int retval; + char label[16]; + + if (config) { + retval = snprintf(label, 16, "tcm_gpio_%d\n", gpio); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set GPIO label\n"); + return retval; + } + + retval = gpio_request(gpio, label); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to request GPIO %d\n", + gpio); + return retval; + } + + if (dir == 0) + retval = gpio_direction_input(gpio); + else + retval = gpio_direction_output(gpio, state); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set GPIO %d direction\n", + gpio); + return retval; + } + } else { + gpio_free(gpio); + } + + return 0; +} + +static int syna_tcm_config_gpio(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + if (bdata->irq_gpio >= 0) { + retval = syna_tcm_set_gpio(tcm_hcd, bdata->irq_gpio, + true, 0, 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to configure interrupt GPIO\n"); + goto err_set_gpio_irq; + } + } + + if (bdata->power_gpio >= 0) { + retval = syna_tcm_set_gpio(tcm_hcd, bdata->power_gpio, + true, 1, !bdata->power_on_state); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to configure power GPIO\n"); + goto err_set_gpio_power; + } + } + + if (bdata->reset_gpio >= 0) { + retval = syna_tcm_set_gpio(tcm_hcd, bdata->reset_gpio, + true, 1, !bdata->reset_on_state); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to configure reset GPIO\n"); + goto err_set_gpio_reset; + } + } + + if (bdata->power_gpio >= 0) { + gpio_set_value(bdata->power_gpio, bdata->power_on_state); + msleep(bdata->power_delay_ms); + } + + if (bdata->reset_gpio >= 0) { + gpio_set_value(bdata->reset_gpio, bdata->reset_on_state); + msleep(bdata->reset_active_ms); + gpio_set_value(bdata->reset_gpio, !bdata->reset_on_state); + msleep(bdata->reset_delay_ms); + } + + return 0; + +err_set_gpio_reset: + if (bdata->power_gpio >= 0) + syna_tcm_set_gpio(tcm_hcd, bdata->power_gpio, false, 0, 0); + +err_set_gpio_power: + if (bdata->irq_gpio >= 0) + syna_tcm_set_gpio(tcm_hcd, bdata->irq_gpio, false, 0, 0); + +err_set_gpio_irq: + return retval; +} + +static int syna_tcm_enable_regulator(struct syna_tcm_hcd *tcm_hcd, bool en) +{ + int retval; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + if (!en) { + retval = 0; + goto disable_pwr_reg; + } + + if (tcm_hcd->bus_reg) { + retval = regulator_set_voltage(tcm_hcd->bus_reg, + SYNA_VDD_VTG_MIN_UV, SYNA_VDD_VTG_MAX_UV); + if (retval) { + LOGE(tcm_hcd->pdev->dev.parent, + "set bus regulator voltage failed\n"); + goto exit; + } + + retval = regulator_set_load(tcm_hcd->bus_reg, + SYNA_LOAD_MAX_UA); + if (retval) { + LOGE(tcm_hcd->pdev->dev.parent, + "set bus regulator load failed\n"); + goto exit; + } + + retval = regulator_enable(tcm_hcd->bus_reg); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enable bus regulator\n"); + goto exit; + } + } + + if (tcm_hcd->pwr_reg) { + if (regulator_count_voltages(tcm_hcd->pwr_reg) > 0) { + retval = regulator_set_voltage(tcm_hcd->pwr_reg, + SYNA_VTG_MIN_UV, SYNA_VTG_MAX_UV); + if (retval) { + LOGE(tcm_hcd->pdev->dev.parent, + "set power regulator voltage failed\n"); + goto disable_bus_reg; + } + retval = regulator_set_load(tcm_hcd->pwr_reg, + SYNA_LOAD_MAX_UA); + if (retval) { + LOGE(tcm_hcd->pdev->dev.parent, + "set power regulator load failed\n"); + goto disable_bus_reg; + } + } + + retval = regulator_enable(tcm_hcd->pwr_reg); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enable power regulator\n"); + goto disable_bus_reg; + } + msleep(bdata->power_delay_ms); + } + + return 0; + +disable_pwr_reg: + if (tcm_hcd->pwr_reg) { + if (regulator_count_voltages(tcm_hcd->pwr_reg) > 0) { + regulator_set_load(tcm_hcd->pwr_reg, 0); + regulator_set_voltage(tcm_hcd->pwr_reg, 0, + SYNA_VTG_MAX_UV); + } + regulator_disable(tcm_hcd->pwr_reg); + } + +disable_bus_reg: + if (tcm_hcd->bus_reg) { + regulator_set_load(tcm_hcd->bus_reg, 0); + regulator_set_voltage(tcm_hcd->bus_reg, 0, + SYNA_VDD_VTG_MAX_UV); + regulator_disable(tcm_hcd->bus_reg); + } + +exit: + return retval; +} + +static int syna_tcm_get_regulator(struct syna_tcm_hcd *tcm_hcd, bool get) +{ + int retval; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + if (!get) { + retval = 0; + goto regulator_put; + } + + if (bdata->bus_reg_name != NULL && *bdata->bus_reg_name != 0) { + tcm_hcd->bus_reg = regulator_get(tcm_hcd->pdev->dev.parent, + bdata->bus_reg_name); + if (IS_ERR(tcm_hcd->bus_reg)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get bus regulator\n"); + retval = PTR_ERR(tcm_hcd->bus_reg); + goto regulator_put; + } + } + + if (bdata->pwr_reg_name != NULL && *bdata->pwr_reg_name != 0) { + tcm_hcd->pwr_reg = regulator_get(tcm_hcd->pdev->dev.parent, + bdata->pwr_reg_name); + if (IS_ERR(tcm_hcd->pwr_reg)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get power regulator\n"); + retval = PTR_ERR(tcm_hcd->pwr_reg); + goto regulator_put; + } + } + + return 0; + +regulator_put: + if (tcm_hcd->bus_reg) { + regulator_put(tcm_hcd->bus_reg); + tcm_hcd->bus_reg = NULL; + } + + if (tcm_hcd->pwr_reg) { + regulator_put(tcm_hcd->pwr_reg); + tcm_hcd->pwr_reg = NULL; + } + + return retval; +} + +static int syna_tcm_get_app_info(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + unsigned int timeout; + + timeout = APP_STATUS_POLL_TIMEOUT_MS; + + resp_buf = NULL; + resp_buf_size = 0; + +get_app_info: + retval = tcm_hcd->write_message(tcm_hcd, + CMD_GET_APPLICATION_INFO, + NULL, + 0, + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_GET_APPLICATION_INFO)); + goto exit; + } + + retval = secure_memcpy((unsigned char *)&tcm_hcd->app_info, + sizeof(tcm_hcd->app_info), + resp_buf, + resp_buf_size, + MIN(sizeof(tcm_hcd->app_info), resp_length)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy application info\n"); + goto exit; + } + + tcm_hcd->app_status = le2_to_uint(tcm_hcd->app_info.status); + + if (tcm_hcd->app_status == APP_STATUS_BOOTING || + tcm_hcd->app_status == APP_STATUS_UPDATING) { + if (timeout > 0) { + msleep(APP_STATUS_POLL_MS); + timeout -= APP_STATUS_POLL_MS; + goto get_app_info; + } + } + + retval = 0; + +exit: + kfree(resp_buf); + + return retval; +} + +static int syna_tcm_get_boot_info(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + + resp_buf = NULL; + resp_buf_size = 0; + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_GET_BOOT_INFO, + NULL, + 0, + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_GET_BOOT_INFO)); + goto exit; + } + + retval = secure_memcpy((unsigned char *)&tcm_hcd->boot_info, + sizeof(tcm_hcd->boot_info), + resp_buf, + resp_buf_size, + MIN(sizeof(tcm_hcd->boot_info), resp_length)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy boot info\n"); + goto exit; + } + + retval = 0; + +exit: + kfree(resp_buf); + + return retval; +} + +static int syna_tcm_identify(struct syna_tcm_hcd *tcm_hcd, bool id) +{ + int retval; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + unsigned int max_write_size; + + resp_buf = NULL; + resp_buf_size = 0; + + mutex_lock(&tcm_hcd->identify_mutex); + + if (!id) + goto get_info; + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_IDENTIFY, + NULL, + 0, + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_IDENTIFY)); + goto exit; + } + + retval = secure_memcpy((unsigned char *)&tcm_hcd->id_info, + sizeof(tcm_hcd->id_info), + resp_buf, + resp_buf_size, + MIN(sizeof(tcm_hcd->id_info), resp_length)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy identification info\n"); + goto exit; + } + + tcm_hcd->packrat_number = le4_to_uint(tcm_hcd->id_info.build_id); + + max_write_size = le2_to_uint(tcm_hcd->id_info.max_write_size); + tcm_hcd->wr_chunk_size = MIN(max_write_size, WR_CHUNK_SIZE); + if (tcm_hcd->wr_chunk_size == 0) + tcm_hcd->wr_chunk_size = max_write_size; + +get_info: + switch (tcm_hcd->id_info.mode) { + case MODE_APPLICATION: + retval = syna_tcm_get_app_info(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get application info\n"); + goto exit; + } + break; + case MODE_BOOTLOADER: + case MODE_TDDI_BOOTLOADER: + retval = syna_tcm_get_boot_info(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get boot info\n"); + goto exit; + } + break; + default: + break; + } + + retval = 0; + +exit: + mutex_unlock(&tcm_hcd->identify_mutex); + + kfree(resp_buf); + + return retval; +} + +static int syna_tcm_run_production_test_firmware(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + bool retry; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + + retry = true; + + resp_buf = NULL; + resp_buf_size = 0; + +retry: + retval = tcm_hcd->write_message(tcm_hcd, + CMD_ENTER_PRODUCTION_TEST_MODE, + NULL, + 0, + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + MODE_SWITCH_DELAY_MS); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_ENTER_PRODUCTION_TEST_MODE)); + goto exit; + } + + if (tcm_hcd->id_info.mode != MODE_PRODUCTION_TEST) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run production test firmware\n"); + if (retry) { + retry = false; + goto retry; + } + retval = -EINVAL; + goto exit; + } else if (tcm_hcd->app_status != APP_STATUS_OK) { + LOGE(tcm_hcd->pdev->dev.parent, + "Application status = 0x%02x\n", + tcm_hcd->app_status); + } + + retval = 0; + +exit: + kfree(resp_buf); + + return retval; +} + +static int syna_tcm_run_application_firmware(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + bool retry; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + + retry = true; + + resp_buf = NULL; + resp_buf_size = 0; + +retry: + retval = tcm_hcd->write_message(tcm_hcd, + CMD_RUN_APPLICATION_FIRMWARE, + NULL, + 0, + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + MODE_SWITCH_DELAY_MS); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_RUN_APPLICATION_FIRMWARE)); + goto exit; + } + + retval = tcm_hcd->identify(tcm_hcd, false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do identification\n"); + goto exit; + } + + if (tcm_hcd->id_info.mode != MODE_APPLICATION) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run application (status = 0x%02x)\n", + tcm_hcd->boot_info.status); + if (retry) { + retry = false; + goto retry; + } + retval = -EINVAL; + goto exit; + } else if (tcm_hcd->app_status != APP_STATUS_OK) { + LOGE(tcm_hcd->pdev->dev.parent, + "Application status = 0x%02x\n", + tcm_hcd->app_status); + } + + retval = 0; + +exit: + kfree(resp_buf); + + return retval; +} + +static int syna_tcm_run_bootloader_firmware(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + + resp_buf = NULL; + resp_buf_size = 0; + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_RUN_BOOTLOADER_FIRMWARE, + NULL, + 0, + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + MODE_SWITCH_DELAY_MS); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_RUN_BOOTLOADER_FIRMWARE)); + goto exit; + } + + retval = tcm_hcd->identify(tcm_hcd, false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do identification\n"); + goto exit; + } + + if (tcm_hcd->id_info.mode == MODE_APPLICATION) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enter bootloader mode\n"); + retval = -EINVAL; + goto exit; + } + + retval = 0; + +exit: + kfree(resp_buf); + + return retval; +} + +static int syna_tcm_switch_mode(struct syna_tcm_hcd *tcm_hcd, + enum firmware_mode mode) +{ + int retval; + + mutex_lock(&tcm_hcd->reset_mutex); + + tcm_hcd->update_watchdog(tcm_hcd, false); + + switch (mode) { + case FW_MODE_BOOTLOADER: + retval = syna_tcm_run_bootloader_firmware(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to switch to bootloader mode\n"); + goto exit; + } + break; + case FW_MODE_APPLICATION: + retval = syna_tcm_run_application_firmware(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to switch to application mode\n"); + goto exit; + } + break; + case FW_MODE_PRODUCTION_TEST: + retval = syna_tcm_run_production_test_firmware(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to switch to production test mode\n"); + goto exit; + } + break; + default: + LOGE(tcm_hcd->pdev->dev.parent, "Invalid firmware mode\n"); + retval = -EINVAL; + goto exit; + } + + retval = 0; + +exit: + tcm_hcd->update_watchdog(tcm_hcd, true); + + mutex_unlock(&tcm_hcd->reset_mutex); + + return retval; +} + +static int syna_tcm_get_dynamic_config(struct syna_tcm_hcd *tcm_hcd, + enum dynamic_config_id id, unsigned short *value) +{ + int retval; + unsigned char out_buf; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + + resp_buf = NULL; + resp_buf_size = 0; + + out_buf = (unsigned char)id; + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_GET_DYNAMIC_CONFIG, + &out_buf, + sizeof(out_buf), + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_GET_DYNAMIC_CONFIG)); + goto exit; + } + + if (resp_length < 2) { + LOGE(tcm_hcd->pdev->dev.parent, "Invalid data length\n"); + retval = -EINVAL; + goto exit; + } + + *value = (unsigned short)le2_to_uint(resp_buf); + + retval = 0; + +exit: + kfree(resp_buf); + + return retval; +} + +static int syna_tcm_set_dynamic_config(struct syna_tcm_hcd *tcm_hcd, + enum dynamic_config_id id, unsigned short value) +{ + int retval; + unsigned char out_buf[3]; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + + resp_buf = NULL; + resp_buf_size = 0; + + out_buf[0] = (unsigned char)id; + out_buf[1] = (unsigned char)value; + out_buf[2] = (unsigned char)(value >> 8); + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_SET_DYNAMIC_CONFIG, + out_buf, + sizeof(out_buf), + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_SET_DYNAMIC_CONFIG)); + goto exit; + } + + retval = 0; + +exit: + kfree(resp_buf); + + return retval; +} + +static int syna_tcm_get_data_location(struct syna_tcm_hcd *tcm_hcd, + enum flash_area area, unsigned int *addr, unsigned int *length) +{ + int retval; + unsigned char out_buf; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + + switch (area) { + case CUSTOM_LCM: + out_buf = LCM_DATA; + break; + case CUSTOM_OEM: + out_buf = OEM_DATA; + break; + case PPDT: + out_buf = PPDT_DATA; + break; + default: + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid flash area\n"); + return -EINVAL; + } + + resp_buf = NULL; + resp_buf_size = 0; + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_GET_DATA_LOCATION, + &out_buf, + sizeof(out_buf), + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_GET_DATA_LOCATION)); + goto exit; + } + + if (resp_length != 4) { + LOGE(tcm_hcd->pdev->dev.parent, "Invalid data length\n"); + retval = -EINVAL; + goto exit; + } + + *addr = le2_to_uint(&resp_buf[0]); + *length = le2_to_uint(&resp_buf[2]); + + retval = 0; + +exit: + kfree(resp_buf); + + return retval; +} + +static int syna_tcm_sleep(struct syna_tcm_hcd *tcm_hcd, bool en) +{ + int retval; + unsigned char command; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + + command = en ? CMD_ENTER_DEEP_SLEEP : CMD_EXIT_DEEP_SLEEP; + + resp_buf = NULL; + resp_buf_size = 0; + + retval = tcm_hcd->write_message(tcm_hcd, + command, + NULL, + 0, + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + en ? + STR(CMD_ENTER_DEEP_SLEEP) : + STR(CMD_EXIT_DEEP_SLEEP)); + goto exit; + } + + retval = 0; + +exit: + kfree(resp_buf); + + return retval; +} + +static int syna_tcm_reset(struct syna_tcm_hcd *tcm_hcd, bool hw, bool update_wd) +{ + int retval; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + struct syna_tcm_module_handler *mod_handler; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + resp_buf = NULL; + resp_buf_size = 0; + + mutex_lock(&tcm_hcd->reset_mutex); + + if (update_wd) + tcm_hcd->update_watchdog(tcm_hcd, false); + + if (hw) { + if (bdata->reset_gpio < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Hardware reset unavailable\n"); + retval = -EINVAL; + goto exit; + } + gpio_set_value(bdata->reset_gpio, bdata->reset_on_state); + msleep(bdata->reset_active_ms); + gpio_set_value(bdata->reset_gpio, !bdata->reset_on_state); + } else { + retval = tcm_hcd->write_message(tcm_hcd, + CMD_RESET, + NULL, + 0, + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + bdata->reset_delay_ms); + if (retval < 0 && !tcm_hcd->host_download_mode) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_RESET)); + goto exit; + } + } + + if (tcm_hcd->host_download_mode) { + mutex_unlock(&tcm_hcd->reset_mutex); + kfree(resp_buf); + retval = syna_tcm_wait_hdl(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to wait for completion of download\n"); + return retval; + } + if (update_wd) + tcm_hcd->update_watchdog(tcm_hcd, true); + return 0; + } + + msleep(bdata->reset_delay_ms); + + retval = tcm_hcd->identify(tcm_hcd, false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do identification\n"); + goto exit; + } + + if (tcm_hcd->id_info.mode == MODE_APPLICATION) + goto get_features; + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_RUN_APPLICATION_FIRMWARE, + NULL, + 0, + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + MODE_SWITCH_DELAY_MS); + if (retval < 0) { + LOGN(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_RUN_APPLICATION_FIRMWARE)); + } + + retval = tcm_hcd->identify(tcm_hcd, false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do identification\n"); + goto exit; + } + +get_features: + LOGD(tcm_hcd->pdev->dev.parent, + "Firmware mode = 0x%02x\n", + tcm_hcd->id_info.mode); + + if (tcm_hcd->id_info.mode != MODE_APPLICATION) { + LOGN(tcm_hcd->pdev->dev.parent, + "Boot status = 0x%02x\n", + tcm_hcd->boot_info.status); + } else if (tcm_hcd->app_status != APP_STATUS_OK) { + LOGN(tcm_hcd->pdev->dev.parent, + "Application status = 0x%02x\n", + tcm_hcd->app_status); + } + + if (tcm_hcd->id_info.mode != MODE_APPLICATION) + goto dispatch_reset; + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_GET_FEATURES, + NULL, + 0, + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + 0); + if (retval < 0) + LOGN(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_GET_FEATURES)); + else { + retval = secure_memcpy((unsigned char *)&tcm_hcd->features, + sizeof(tcm_hcd->features), + resp_buf, + resp_buf_size, + MIN(sizeof(tcm_hcd->features), resp_length)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy feature description\n"); + } + } + +dispatch_reset: + mutex_lock(&mod_pool.mutex); + mod_pool.reconstructing = true; + + if (!list_empty(&mod_pool.list)) { + list_for_each_entry(mod_handler, &mod_pool.list, link) { + if (!mod_handler->insert && + !mod_handler->detach && + (mod_handler->mod_cb->reset)) + mod_handler->mod_cb->reset(tcm_hcd); + } + } + + mod_pool.reconstructing = false; + mutex_unlock(&mod_pool.mutex); + + retval = 0; + +exit: + if (update_wd) + tcm_hcd->update_watchdog(tcm_hcd, true); + + mutex_unlock(&tcm_hcd->reset_mutex); + + kfree(resp_buf); + + return retval; +} + +static int syna_tcm_rezero(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + unsigned char *resp_buf; + unsigned int resp_buf_size; + unsigned int resp_length; + + resp_buf = NULL; + resp_buf_size = 0; + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_REZERO, + NULL, + 0, + &resp_buf, + &resp_buf_size, + &resp_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_REZERO)); + goto exit; + } + + retval = 0; + +exit: + kfree(resp_buf); + + return retval; +} + +static void syna_tcm_helper_work(struct work_struct *work) +{ + int retval; + unsigned char task; + struct syna_tcm_module_handler *mod_handler; + struct syna_tcm_helper *helper = + container_of(work, struct syna_tcm_helper, work); + struct syna_tcm_hcd *tcm_hcd = + container_of(helper, struct syna_tcm_hcd, helper); + + task = atomic_read(&helper->task); + + switch (task) { + case HELP_RUN_APPLICATION_FIRMWARE: + mutex_lock(&tcm_hcd->reset_mutex); + tcm_hcd->update_watchdog(tcm_hcd, false); + retval = syna_tcm_run_application_firmware(tcm_hcd); + if (retval < 0) + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to switch to application mode\n"); + tcm_hcd->update_watchdog(tcm_hcd, true); + mutex_unlock(&tcm_hcd->reset_mutex); + break; + case HELP_SEND_RESET_NOTIFICATION: + mutex_lock(&tcm_hcd->reset_mutex); + retval = tcm_hcd->identify(tcm_hcd, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do identification\n"); + mutex_unlock(&tcm_hcd->reset_mutex); + break; + } + mutex_lock(&mod_pool.mutex); + if (!list_empty(&mod_pool.list)) { + list_for_each_entry(mod_handler, &mod_pool.list, link) { + if (!mod_handler->insert && + !mod_handler->detach && + (mod_handler->mod_cb->reset)) + mod_handler->mod_cb->reset(tcm_hcd); + } + } + mutex_unlock(&mod_pool.mutex); + mutex_unlock(&tcm_hcd->reset_mutex); + wake_up_interruptible(&tcm_hcd->hdl_wq); + break; + default: + break; + } + + atomic_set(&helper->task, HELP_NONE); +} + +#if defined(CONFIG_PM) || defined(CONFIG_DRM) || defined(CONFIG_FB) + +static int syna_tcm_deferred_probe(struct device *dev); + +static int syna_tcm_resume(struct device *dev) +{ + int retval; + struct syna_tcm_module_handler *mod_handler; + struct syna_tcm_hcd *tcm_hcd = dev_get_drvdata(dev); + + if (!tcm_hcd->init_okay) + syna_tcm_deferred_probe(dev); + + if (!tcm_hcd->in_suspend) + return 0; + else { + if (tcm_hcd->irq_enabled) { + tcm_hcd->watchdog.run = false; + tcm_hcd->update_watchdog(tcm_hcd, false); + tcm_hcd->enable_irq(tcm_hcd, false, false); + } + } + + retval = syna_tcm_enable_regulator(tcm_hcd, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enable regulators\n"); + } + + retval = pinctrl_select_state( + tcm_hcd->ts_pinctrl, + tcm_hcd->pinctrl_state_active); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "%s: Failed to select %s pinstate %d\n", + __func__, PINCTRL_STATE_ACTIVE, retval); + } + + if (tcm_hcd->host_download_mode) { +#ifndef WAKEUP_GESTURE + syna_tcm_check_hdl(tcm_hcd); + retval = syna_tcm_wait_hdl(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to wait for completion of download\n"); + goto exit; + } +#endif + } else { + tcm_hcd->enable_irq(tcm_hcd, true, NULL); +#ifdef RESET_ON_RESUME + msleep(RESET_ON_RESUME_DELAY_MS); + goto do_reset; +#endif + } + + tcm_hcd->update_watchdog(tcm_hcd, true); + + if (tcm_hcd->id_info.mode != MODE_APPLICATION || + tcm_hcd->app_status != APP_STATUS_OK) { + LOGN(tcm_hcd->pdev->dev.parent, + "Application firmware not running\n"); + goto do_reset; + } + + retval = tcm_hcd->sleep(tcm_hcd, false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to exit deep sleep\n"); + goto exit; + } + + retval = syna_tcm_rezero(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to rezero\n"); + goto exit; + } + + goto mod_resume; + +do_reset: + retval = tcm_hcd->reset(tcm_hcd, false, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + goto exit; + } + + if (tcm_hcd->id_info.mode != MODE_APPLICATION || + tcm_hcd->app_status != APP_STATUS_OK) { + LOGN(tcm_hcd->pdev->dev.parent, + "Application firmware not running\n"); + retval = 0; + goto exit; + } + +mod_resume: + mutex_lock(&mod_pool.mutex); + + if (!list_empty(&mod_pool.list)) { + list_for_each_entry(mod_handler, &mod_pool.list, link) { + if (!mod_handler->insert && + !mod_handler->detach && + (mod_handler->mod_cb->resume)) + mod_handler->mod_cb->resume(tcm_hcd); + } + } + + mutex_unlock(&mod_pool.mutex); + + retval = 0; + +exit: + tcm_hcd->in_suspend = false; + + return retval; +} + +static int syna_tcm_suspend(struct device *dev) +{ + struct syna_tcm_module_handler *mod_handler; + struct syna_tcm_hcd *tcm_hcd = dev_get_drvdata(dev); + int retval; + + if (tcm_hcd->in_suspend || !tcm_hcd->init_okay) + return 0; + + if (pinctrl_select_state( + tcm_hcd->ts_pinctrl, + tcm_hcd->pinctrl_state_suspend)) + LOGE(tcm_hcd->pdev->dev.parent, + "%s: Failed to select %s pinstate\n", + __func__, PINCTRL_STATE_RELEASE); + + mutex_lock(&mod_pool.mutex); + + if (!list_empty(&mod_pool.list)) { + list_for_each_entry(mod_handler, &mod_pool.list, link) { + if (!mod_handler->insert && + !mod_handler->detach && + (mod_handler->mod_cb->suspend)) + mod_handler->mod_cb->suspend(tcm_hcd); + } + } + +#ifndef WAKEUP_GESTURE + tcm_hcd->enable_irq(tcm_hcd, false, true); +#endif + + retval = syna_tcm_enable_regulator(tcm_hcd, false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to disable regulators\n"); + } + + mutex_unlock(&mod_pool.mutex); + + tcm_hcd->in_suspend = true; + + return retval; +} +#endif + + + +#ifdef CONFIG_DRM +#if 0 +static int syna_tcm_early_suspend(struct device *dev) +{ +#ifndef WAKEUP_GESTURE + int retval; +#endif + + struct syna_tcm_module_handler *mod_handler; + struct syna_tcm_hcd *tcm_hcd = dev_get_drvdata(dev); + + if (tcm_hcd->in_suspend || !tcm_hcd->init_okay) + return 0; + + if (pinctrl_select_state( + tcm_hcd->ts_pinctrl, + tcm_hcd->pinctrl_state_suspend)) + LOGE(tcm_hcd->pdev->dev.parent, + "%s: Failed to select %s pinstate\n", + __func__, PINCTRL_STATE_RELEASE); + + tcm_hcd->update_watchdog(tcm_hcd, false); + + if (tcm_hcd->id_info.mode != MODE_APPLICATION || + tcm_hcd->app_status != APP_STATUS_OK) { + LOGN(tcm_hcd->pdev->dev.parent, + "Application firmware not running\n"); + return 0; + } + +#ifndef WAKEUP_GESTURE + retval = tcm_hcd->sleep(tcm_hcd, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enter deep sleep\n"); + return retval; + } +#endif + + mutex_lock(&mod_pool.mutex); + + if (!list_empty(&mod_pool.list)) { + list_for_each_entry(mod_handler, &mod_pool.list, link) { + if (!mod_handler->insert && + !mod_handler->detach && + (mod_handler->mod_cb->early_suspend)) + mod_handler->mod_cb->early_suspend(tcm_hcd); + } + } + + mutex_unlock(&mod_pool.mutex); + +#ifndef WAKEUP_GESTURE + tcm_hcd->enable_irq(tcm_hcd, false, true); +#endif + + return 0; +} +#endif + +static void syna_tcm_fb_notifier_cb(enum panel_event_notifier_tag tag, + struct panel_event_notification *notification, + void *pvt_data) +{ + int retval = 0; + struct syna_tcm_hcd *tcm_hcd = (struct syna_tcm_hcd *) pvt_data; + + if (!notification) { + pr_err("Invalid notification\n"); + return; + } + + pr_debug("Notification type:%d, early_trigger:%d", + notification->notif_type, + notification->notif_data.early_trigger); + switch (notification->notif_type) { + case DRM_PANEL_EVENT_UNBLANK: + if (notification->notif_data.early_trigger) + pr_debug("resume notification pre commit\n"); + else + retval = syna_tcm_resume(&tcm_hcd->pdev->dev); + break; + case DRM_PANEL_EVENT_BLANK: + if (notification->notif_data.early_trigger) { + retval = syna_tcm_suspend(&tcm_hcd->pdev->dev); + } else { + pr_debug("suspend notification post commit\n"); + } + break; + case DRM_PANEL_EVENT_BLANK_LP: + pr_debug("received lp event\n"); + break; + case DRM_PANEL_EVENT_FPS_CHANGE: + pr_debug("Received fps change old fps:%d new fps:%d\n", + notification->notif_data.old_fps, + notification->notif_data.new_fps); + break; + default: + pr_debug("notification not serviced :%d\n", + notification->notif_type); + break; + } +#if 0 + if (atomic_read(&tcm_hcd->firmware_flashing) + && transition == DRM_PANEL_BLANK_POWERDOWN) { + retval = wait_event_interruptible_timeout(tcm_hcd->reflash_wq, + !atomic_read(&tcm_hcd->firmware_flashing), + msecs_to_jiffies(RESPONSE_TIMEOUT_MS)); + if (retval == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Timed out waiting for flashing firmware\n"); + atomic_set(&tcm_hcd->firmware_flashing, 0); + return -EIO; + } + } + + if (action == DRM_PANEL_EARLY_EVENT_BLANK && + transition == DRM_PANEL_BLANK_POWERDOWN) + retval = syna_tcm_early_suspend(&tcm_hcd->pdev->dev); + else if (action == DRM_PANEL_EVENT_BLANK) { + if (transition == DRM_PANEL_BLANK_POWERDOWN) { + retval = syna_tcm_suspend(&tcm_hcd->pdev->dev); + tcm_hcd->fb_ready = 0; + } else if (transition == DRM_PANEL_BLANK_UNBLANK) { +#ifndef RESUME_EARLY_UNBLANK + retval = syna_tcm_resume(&tcm_hcd->pdev->dev); + tcm_hcd->fb_ready++; +#endif + } + } else if (action == DRM_PANEL_EARLY_EVENT_BLANK && + transition == DRM_PANEL_BLANK_UNBLANK) { +#ifdef RESUME_EARLY_UNBLANK + retval = syna_tcm_resume(&tcm_hcd->pdev->dev); + tcm_hcd->fb_ready++; +#endif + } +#endif + +} + +#elif CONFIG_FB + +static int syna_tcm_early_suspend(struct device *dev) +{ +#ifndef WAKEUP_GESTURE + int retval; +#endif + + struct syna_tcm_module_handler *mod_handler; + struct syna_tcm_hcd *tcm_hcd = dev_get_drvdata(dev); + + if (tcm_hcd->in_suspend) + return 0; + + tcm_hcd->update_watchdog(tcm_hcd, false); + + if (tcm_hcd->id_info.mode != MODE_APPLICATION || + tcm_hcd->app_status != APP_STATUS_OK) { + LOGN(tcm_hcd->pdev->dev.parent, + "Application firmware not running\n"); + return 0; + } + +#ifndef WAKEUP_GESTURE + retval = tcm_hcd->sleep(tcm_hcd, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enter deep sleep\n"); + return retval; + } +#endif + + mutex_lock(&mod_pool.mutex); + + if (!list_empty(&mod_pool.list)) { + list_for_each_entry(mod_handler, &mod_pool.list, link) { + if (!mod_handler->insert && + !mod_handler->detach && + (mod_handler->mod_cb->early_suspend)) + mod_handler->mod_cb->early_suspend(tcm_hcd); + } + } + + mutex_unlock(&mod_pool.mutex); + + return 0; +} + +static int syna_tcm_fb_notifier_cb(struct notifier_block *nb, + unsigned long action, void *data) +{ + int retval = 0; + int *transition; + struct fb_event *evdata = data; + struct syna_tcm_hcd *tcm_hcd = + container_of(nb, struct syna_tcm_hcd, fb_notifier); + + if (!evdata || !evdata->data || !tcm_hcd) + return 0; + + transition = (int *)evdata->data; + + if (atomic_read(&tcm_hcd->firmware_flashing) + && *transition == FB_BLANK_POWERDOWN) { + retval = wait_event_interruptible_timeout(tcm_hcd->reflash_wq, + !atomic_read(&tcm_hcd->firmware_flashing), + msecs_to_jiffies(RESPONSE_TIMEOUT_MS)); + if (retval == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Timed out waiting for flashing firmware\n"); + atomic_set(&tcm_hcd->firmware_flashing, 0); + return -EIO; + } + } + + if (action == FB_EARLY_EVENT_BLANK && + *transition == FB_BLANK_POWERDOWN) + retval = syna_tcm_early_suspend(&tcm_hcd->pdev->dev); + else if (action == FB_EVENT_BLANK) { + if (*transition == FB_BLANK_POWERDOWN) { + retval = syna_tcm_suspend(&tcm_hcd->pdev->dev); + tcm_hcd->fb_ready = 0; + } else if (*transition == FB_BLANK_UNBLANK) { +#ifndef RESUME_EARLY_UNBLANK + retval = syna_tcm_resume(&tcm_hcd->pdev->dev); + tcm_hcd->fb_ready++; +#endif + } + } else if (action == FB_EARLY_EVENT_BLANK && + *transition == FB_BLANK_UNBLANK) { +#ifdef RESUME_EARLY_UNBLANK + retval = syna_tcm_resume(&tcm_hcd->pdev->dev); + tcm_hcd->fb_ready++; +#endif + } + + return 0; +} +#endif + +static int synaptics_tcm_pinctrl_init(struct syna_tcm_hcd *tcm_hcd) +{ + int retval = 0; + + /* Get pinctrl if target uses pinctrl */ + tcm_hcd->ts_pinctrl = devm_pinctrl_get((tcm_hcd->pdev->dev.parent)); + if (IS_ERR_OR_NULL(tcm_hcd->ts_pinctrl)) { + retval = PTR_ERR(tcm_hcd->ts_pinctrl); + LOGE(tcm_hcd->pdev->dev.parent, + "Target does not use pinctrl %d\n", retval); + goto err_pinctrl_get; + } + + tcm_hcd->pinctrl_state_active + = pinctrl_lookup_state(tcm_hcd->ts_pinctrl, "pmx_ts_active"); + if (IS_ERR_OR_NULL(tcm_hcd->pinctrl_state_active)) { + retval = PTR_ERR(tcm_hcd->pinctrl_state_active); + LOGE(tcm_hcd->pdev->dev.parent, + "Can not lookup %s pinstate %d\n", + PINCTRL_STATE_ACTIVE, retval); + goto err_pinctrl_lookup; + } + + tcm_hcd->pinctrl_state_suspend + = pinctrl_lookup_state(tcm_hcd->ts_pinctrl, "pmx_ts_suspend"); + if (IS_ERR_OR_NULL(tcm_hcd->pinctrl_state_suspend)) { + retval = PTR_ERR(tcm_hcd->pinctrl_state_suspend); + LOGE(tcm_hcd->pdev->dev.parent, + "Can not lookup %s pinstate %d\n", + PINCTRL_STATE_SUSPEND, retval); + goto err_pinctrl_lookup; + } + + tcm_hcd->pinctrl_state_release + = pinctrl_lookup_state(tcm_hcd->ts_pinctrl, "pmx_ts_release"); + if (IS_ERR_OR_NULL(tcm_hcd->pinctrl_state_release)) { + retval = PTR_ERR(tcm_hcd->pinctrl_state_release); + LOGE(tcm_hcd->pdev->dev.parent, + "Can not lookup %s pinstate %d\n", + PINCTRL_STATE_RELEASE, retval); + } + + return retval; + +err_pinctrl_lookup: + devm_pinctrl_put(tcm_hcd->ts_pinctrl); +err_pinctrl_get: + tcm_hcd->ts_pinctrl = NULL; + return retval; +} + +static int syna_tcm_probe(struct platform_device *pdev) +{ + int retval; +#ifdef CREATE_SYSFS + int idx; +#endif + struct syna_tcm_hcd *tcm_hcd; + const struct syna_tcm_board_data *bdata; + const struct syna_tcm_hw_interface *hw_if; + struct drm_panel *active_panel = tcm_get_panel(); + void *cookie; + + hw_if = pdev->dev.platform_data; + if (!hw_if) { + LOGE(&pdev->dev, + "Hardware interface not found\n"); + return -ENODEV; + } + + bdata = hw_if->bdata; + if (!bdata) { + LOGE(&pdev->dev, + "Board data not found\n"); + return -ENODEV; + } + + tcm_hcd = kzalloc(sizeof(*tcm_hcd), GFP_KERNEL); + if (!tcm_hcd) { + LOGE(&pdev->dev, + "Failed to allocate memory for tcm_hcd\n"); + return -ENOMEM; + } + + platform_set_drvdata(pdev, tcm_hcd); + + tcm_hcd->pdev = pdev; + tcm_hcd->hw_if = hw_if; + tcm_hcd->reset = syna_tcm_reset; + tcm_hcd->sleep = syna_tcm_sleep; + tcm_hcd->identify = syna_tcm_identify; + tcm_hcd->enable_irq = syna_tcm_enable_irq; + tcm_hcd->switch_mode = syna_tcm_switch_mode; + tcm_hcd->read_message = syna_tcm_read_message; + tcm_hcd->write_message = syna_tcm_write_message; + tcm_hcd->get_dynamic_config = syna_tcm_get_dynamic_config; + tcm_hcd->set_dynamic_config = syna_tcm_set_dynamic_config; + tcm_hcd->get_data_location = syna_tcm_get_data_location; + + tcm_hcd->rd_chunk_size = RD_CHUNK_SIZE; + tcm_hcd->wr_chunk_size = WR_CHUNK_SIZE; + +#ifdef PREDICTIVE_READING + tcm_hcd->read_length = MIN_READ_LENGTH; +#else + tcm_hcd->read_length = MESSAGE_HEADER_SIZE; +#endif + + tcm_hcd->watchdog.run = RUN_WATCHDOG; + tcm_hcd->update_watchdog = syna_tcm_update_watchdog; + + if (bdata->irq_gpio >= 0) + tcm_hcd->irq = gpio_to_irq(bdata->irq_gpio); + else + tcm_hcd->irq = bdata->irq_gpio; + + mutex_init(&tcm_hcd->extif_mutex); + mutex_init(&tcm_hcd->reset_mutex); + mutex_init(&tcm_hcd->irq_en_mutex); + mutex_init(&tcm_hcd->io_ctrl_mutex); + mutex_init(&tcm_hcd->rw_ctrl_mutex); + mutex_init(&tcm_hcd->command_mutex); + mutex_init(&tcm_hcd->identify_mutex); + + INIT_BUFFER(tcm_hcd->in, false); + INIT_BUFFER(tcm_hcd->out, false); + INIT_BUFFER(tcm_hcd->resp, true); + INIT_BUFFER(tcm_hcd->temp, false); + INIT_BUFFER(tcm_hcd->config, false); + INIT_BUFFER(tcm_hcd->report.buffer, true); + + LOCK_BUFFER(tcm_hcd->in); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &tcm_hcd->in, + tcm_hcd->read_length + 1); + if (retval < 0) { + LOGE(&pdev->dev, + "Failed to allocate memory for tcm_hcd->in.buf\n"); + UNLOCK_BUFFER(tcm_hcd->in); + goto err_alloc_mem; + } + + UNLOCK_BUFFER(tcm_hcd->in); + + atomic_set(&tcm_hcd->command_status, CMD_IDLE); + + atomic_set(&tcm_hcd->helper.task, HELP_NONE); + + device_init_wakeup(&pdev->dev, 1); + + init_waitqueue_head(&tcm_hcd->hdl_wq); + + init_waitqueue_head(&tcm_hcd->reflash_wq); + atomic_set(&tcm_hcd->firmware_flashing, 0); + + if (!mod_pool.initialized) { + mutex_init(&mod_pool.mutex); + INIT_LIST_HEAD(&mod_pool.list); + mod_pool.initialized = true; + } + + retval = syna_tcm_get_regulator(tcm_hcd, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get regulators\n"); + goto err_get_regulator; + } + + retval = syna_tcm_enable_regulator(tcm_hcd, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enable regulators\n"); + goto err_enable_regulator; + } + + retval = syna_tcm_config_gpio(tcm_hcd); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to configure GPIO's\n"); + goto err_config_gpio; + } + + retval = synaptics_tcm_pinctrl_init(tcm_hcd); + if (!retval && tcm_hcd->ts_pinctrl) { + retval = pinctrl_select_state( + tcm_hcd->ts_pinctrl, + tcm_hcd->pinctrl_state_active); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "%s: Failed to select %s pinstate %d\n", + __func__, PINCTRL_STATE_ACTIVE, retval); + } + } + +#ifdef CREATE_SYSFS + 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; + } + } +#endif + +#ifdef CONFIG_DRM + if (active_panel) { + cookie = panel_event_notifier_register(PANEL_EVENT_NOTIFICATION_PRIMARY, + PANEL_EVENT_NOTIFIER_CLIENT_PRIMARY_TOUCH, active_panel, + &syna_tcm_fb_notifier_cb, tcm_hcd); + if (!cookie) { + dev_err(&pdev->dev, + "%s: Failed to register fb notifier client\n", + __func__); + goto err_drm_reg; + } + tcm_hcd->notifier_cookie = cookie; + } + +#elif CONFIG_FB + tcm_hcd->fb_notifier.notifier_call = syna_tcm_fb_notifier_cb; + retval = fb_register_client(&tcm_hcd->fb_notifier); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to register FB notifier client\n"); + } +#endif + + tcm_hcd->notifier_thread = kthread_run(syna_tcm_report_notifier, + tcm_hcd, "syna_tcm_report_notifier"); + if (IS_ERR(tcm_hcd->notifier_thread)) { + retval = PTR_ERR(tcm_hcd->notifier_thread); + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create and run tcm_hcd->notifier_thread\n"); + goto err_create_run_kthread; + } + + tcm_hcd->helper.workqueue = + create_singlethread_workqueue("syna_tcm_helper"); + INIT_WORK(&tcm_hcd->helper.work, syna_tcm_helper_work); + + tcm_hcd->watchdog.workqueue = + create_singlethread_workqueue("syna_tcm_watchdog"); + INIT_DELAYED_WORK(&tcm_hcd->watchdog.work, syna_tcm_watchdog_work); + + tcm_hcd->polling_workqueue = + create_singlethread_workqueue("syna_tcm_polling"); + INIT_DELAYED_WORK(&tcm_hcd->polling_work, syna_tcm_polling_work); + + mod_pool.workqueue = + create_singlethread_workqueue("syna_tcm_module"); + INIT_WORK(&mod_pool.work, syna_tcm_module_work); + mod_pool.tcm_hcd = tcm_hcd; + mod_pool.queue_work = true; + mod_pool.reconstructing = false; + touch_module_init(); + return 0; + + +err_create_run_kthread: +#ifdef CONFIG_DRM + if (active_panel) + panel_event_notifier_unregister(tcm_hcd->notifier_cookie); +#elif CONFIG_FB + fb_unregister_client(&tcm_hcd->fb_notifier); +#endif + +#ifdef CREATE_SYSFS +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); + +#endif +err_config_gpio: + syna_tcm_enable_regulator(tcm_hcd, false); + +err_enable_regulator: + syna_tcm_get_regulator(tcm_hcd, false); + +err_get_regulator: + device_init_wakeup(&pdev->dev, 0); + +err_alloc_mem: + RELEASE_BUFFER(tcm_hcd->report.buffer); + RELEASE_BUFFER(tcm_hcd->config); + RELEASE_BUFFER(tcm_hcd->temp); + RELEASE_BUFFER(tcm_hcd->resp); + RELEASE_BUFFER(tcm_hcd->out); + RELEASE_BUFFER(tcm_hcd->in); + +err_drm_reg: + kfree(tcm_hcd); + + return retval; +} + +static int syna_tcm_deferred_probe(struct device *dev) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = dev_get_drvdata(dev); + + retval = tcm_hcd->enable_irq(tcm_hcd, true, NULL); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enable interrupt\n"); + goto err_enable_irq; + } + retval = tcm_hcd->reset(tcm_hcd, false, false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + tcm_hcd->init_okay = false; + tcm_hcd->watchdog.run = false; + tcm_hcd->update_watchdog(tcm_hcd, false); + tcm_hcd->enable_irq(tcm_hcd, false, false); +#ifndef KEEP_DRIVER_ON_ERROR + goto err_reset; +#endif + } else { + tcm_hcd->init_okay = true; + tcm_hcd->update_watchdog(tcm_hcd, true); + } + + queue_work(mod_pool.workqueue, &mod_pool.work); + + return 0; +#ifndef KEEP_DRIVER_ON_ERROR +err_reset: +#endif +err_enable_irq: + + return retval; +} + + +static int syna_tcm_remove(struct platform_device *pdev) +{ + int idx; + struct syna_tcm_module_handler *mod_handler; + struct syna_tcm_module_handler *tmp_handler; + struct syna_tcm_hcd *tcm_hcd = platform_get_drvdata(pdev); + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + struct drm_panel *active_panel = tcm_get_panel(); + + touch_module_exit(); + mutex_lock(&mod_pool.mutex); + + if (!list_empty(&mod_pool.list)) { + list_for_each_entry_safe(mod_handler, tmp_handler, + &mod_pool.list, link) { + if (mod_handler->mod_cb->remove) + mod_handler->mod_cb->remove(tcm_hcd); + list_del(&mod_handler->link); + kfree(mod_handler); + } + } + + mod_pool.queue_work = false; + cancel_work_sync(&mod_pool.work); + flush_workqueue(mod_pool.workqueue); + destroy_workqueue(mod_pool.workqueue); + + mutex_unlock(&mod_pool.mutex); + + if (tcm_hcd->irq_enabled && bdata->irq_gpio >= 0) { + disable_irq(tcm_hcd->irq); + free_irq(tcm_hcd->irq, tcm_hcd); + } + + cancel_delayed_work_sync(&tcm_hcd->polling_work); + flush_workqueue(tcm_hcd->polling_workqueue); + destroy_workqueue(tcm_hcd->polling_workqueue); + + cancel_delayed_work_sync(&tcm_hcd->watchdog.work); + flush_workqueue(tcm_hcd->watchdog.workqueue); + destroy_workqueue(tcm_hcd->watchdog.workqueue); + + cancel_work_sync(&tcm_hcd->helper.work); + flush_workqueue(tcm_hcd->helper.workqueue); + destroy_workqueue(tcm_hcd->helper.workqueue); + + kthread_stop(tcm_hcd->notifier_thread); + +#ifdef CONFIG_DRM + if (active_panel) + panel_event_notifier_unregister(tcm_hcd->notifier_cookie); +#elif CONFIG_FB + fb_unregister_client(&tcm_hcd->fb_notifier); +#endif + + for (idx = 0; idx < ARRAY_SIZE(dynamic_config_attrs); idx++) { + sysfs_remove_file(tcm_hcd->dynamnic_config_sysfs_dir, + &(*dynamic_config_attrs[idx]).attr); + } + + kobject_put(tcm_hcd->dynamnic_config_sysfs_dir); + + for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) + sysfs_remove_file(tcm_hcd->sysfs_dir, &(*attrs[idx]).attr); + + kobject_put(tcm_hcd->sysfs_dir); + + if (bdata->irq_gpio >= 0) + syna_tcm_set_gpio(tcm_hcd, bdata->irq_gpio, false, 0, 0); + + if (bdata->power_gpio >= 0) + syna_tcm_set_gpio(tcm_hcd, bdata->power_gpio, false, 0, 0); + + if (bdata->reset_gpio >= 0) + syna_tcm_set_gpio(tcm_hcd, bdata->reset_gpio, false, 0, 0); + + syna_tcm_enable_regulator(tcm_hcd, false); + + syna_tcm_get_regulator(tcm_hcd, false); + + device_init_wakeup(&pdev->dev, 0); + + RELEASE_BUFFER(tcm_hcd->report.buffer); + RELEASE_BUFFER(tcm_hcd->config); + RELEASE_BUFFER(tcm_hcd->temp); + RELEASE_BUFFER(tcm_hcd->resp); + RELEASE_BUFFER(tcm_hcd->out); + RELEASE_BUFFER(tcm_hcd->in); + + kfree(tcm_hcd); + + return 0; +} + +#ifdef CONFIG_PM +static const struct dev_pm_ops syna_tcm_dev_pm_ops = { +#if !defined(CONFIG_DRM) && !defined(CONFIG_FB) + .suspend = syna_tcm_suspend, + .resume = syna_tcm_resume, +#endif +}; +#endif + +static struct platform_driver syna_tcm_driver = { + .driver = { + .name = PLATFORM_DRIVER_NAME, + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &syna_tcm_dev_pm_ops, +#endif + }, + .probe = syna_tcm_probe, + .remove = syna_tcm_remove, +}; + +static int __init syna_tcm_module_init(void) +{ + int retval; + + retval = syna_tcm_bus_init(); + if (retval < 0) + return retval; + + return platform_driver_register(&syna_tcm_driver); +} + +static void __exit syna_tcm_module_exit(void) +{ + platform_driver_unregister(&syna_tcm_driver); + + syna_tcm_bus_exit(); +} + +late_initcall(syna_tcm_module_init); +module_exit(syna_tcm_module_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics TCM Touch Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm_core.h b/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm_core.h new file mode 100644 index 0000000000..b13a5c01fc --- /dev/null +++ b/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm_core.h @@ -0,0 +1,686 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2019 Scott Lin + * + * 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 "synaptics_tcm.h" +#ifdef CONFIG_DRM +#include +#include +#elif CONFIG_FB +#include +#include +#endif +#include + +#define SYNAPTICS_TCM_ID_PRODUCT (1 << 0) +#define SYNAPTICS_TCM_ID_VERSION 0x0101 +#define SYNAPTICS_TCM_ID_SUBVERSION 0 + +#define PLATFORM_DRIVER_NAME "synaptics_tcm" + +#define TOUCH_INPUT_NAME "synaptics_tcm_touch" +#define TOUCH_INPUT_PHYS_PATH "synaptics_tcm/touch_input" + +/* #define WAKEUP_GESTURE */ + +#define RD_CHUNK_SIZE 0 /* read length limit in bytes, 0 = unlimited */ +#define WR_CHUNK_SIZE 0 /* write length limit in bytes, 0 = unlimited */ + +#define MESSAGE_HEADER_SIZE 4 +#define MESSAGE_MARKER 0xa5 +#define MESSAGE_PADDING 0x5a + +#define LOGx(func, dev, log, ...) \ + func(dev, "%s: " log, __func__, ##__VA_ARGS__) + +#define LOGy(func, dev, log, ...) \ + func(dev, "%s (line %d): " log, __func__, __LINE__, ##__VA_ARGS__) + +#define LOGD(dev, log, ...) LOGx(dev_dbg, dev, log, ##__VA_ARGS__) +#define LOGI(dev, log, ...) LOGx(dev_info, dev, log, ##__VA_ARGS__) +#define LOGN(dev, log, ...) LOGx(dev_notice, dev, log, ##__VA_ARGS__) +#define LOGW(dev, log, ...) LOGy(dev_warn, dev, log, ##__VA_ARGS__) +#define LOGE(dev, log, ...) LOGy(dev_err, dev, log, ##__VA_ARGS__) + +#define INIT_BUFFER(buffer, is_clone) \ + mutex_init(&buffer.buf_mutex); \ + buffer.clone = is_clone + +#define LOCK_BUFFER(buffer) \ + mutex_lock(&buffer.buf_mutex) + +#define UNLOCK_BUFFER(buffer) \ + mutex_unlock(&buffer.buf_mutex) + +#define RELEASE_BUFFER(buffer) \ + do { \ + if (buffer.clone == false) { \ + kfree(buffer.buf); \ + buffer.buf_size = 0; \ + buffer.data_length = 0; \ + } \ + } while (0) + +#define MAX(a, b) \ + ({__typeof__(a) _a = (a); \ + __typeof__(b) _b = (b); \ + _a > _b ? _a : _b; }) + +#define MIN(a, b) \ + ({__typeof__(a) _a = (a); \ + __typeof__(b) _b = (b); \ + _a < _b ? _a : _b; }) + +#define STR(x) #x + +#define CONCAT(a, b) a##b + +#define SHOW_PROTOTYPE(m_name, a_name) \ +static ssize_t CONCAT(m_name##_sysfs, _##a_name##_show)(struct device *dev, \ + struct device_attribute *attr, char *buf); \ +\ +static struct device_attribute dev_attr_##a_name = \ + __ATTR(a_name, 0444, \ + CONCAT(m_name##_sysfs, _##a_name##_show), \ + syna_tcm_store_error) + +#define STORE_PROTOTYPE(m_name, a_name) \ +static ssize_t CONCAT(m_name##_sysfs, _##a_name##_store)(struct device *dev, \ + struct device_attribute *attr, const char *buf, size_t count); \ +\ +static struct device_attribute dev_attr_##a_name = \ + __ATTR(a_name, 0220, \ + syna_tcm_show_error, \ + CONCAT(m_name##_sysfs, _##a_name##_store)) + +#define SHOW_STORE_PROTOTYPE(m_name, a_name) \ +static ssize_t CONCAT(m_name##_sysfs, _##a_name##_show)(struct device *dev, \ + struct device_attribute *attr, char *buf); \ +\ +static ssize_t CONCAT(m_name##_sysfs, _##a_name##_store)(struct device *dev, \ + struct device_attribute *attr, const char *buf, size_t count); \ +\ +static struct device_attribute dev_attr_##a_name = \ + __ATTR(a_name, 0664, \ + CONCAT(m_name##_sysfs, _##a_name##_show), \ + CONCAT(m_name##_sysfs, _##a_name##_store)) + +#define ATTRIFY(a_name) (&dev_attr_##a_name) + +#define PINCTRL_STATE_ACTIVE "pmx_ts_active" +#define PINCTRL_STATE_SUSPEND "pmx_ts_suspend" +#define PINCTRL_STATE_RELEASE "pmx_ts_release" + +enum module_type { + TCM_TOUCH = 0, + TCM_DEVICE = 1, + TCM_TESTING = 2, + TCM_REFLASH = 3, + TCM_RECOVERY = 4, + TCM_ZEROFLASH = 5, + TCM_DIAGNOSTICS = 6, + TCM_LAST, +}; + +enum boot_mode { + MODE_APPLICATION = 0x01, + MODE_HOST_DOWNLOAD = 0x02, + MODE_BOOTLOADER = 0x0b, + MODE_TDDI_BOOTLOADER = 0x0c, + MODE_PRODUCTION_TEST = 0x0e, +}; + +enum boot_status { + BOOT_STATUS_OK = 0x00, + BOOT_STATUS_BOOTING = 0x01, + BOOT_STATUS_APP_BAD_DISPLAY_CRC = 0xfc, + BOOT_STATUS_BAD_DISPLAY_CONFIG = 0xfd, + BOOT_STATUS_BAD_APP_FIRMWARE = 0xfe, + BOOT_STATUS_WARM_BOOT = 0xff, +}; + +enum app_status { + APP_STATUS_OK = 0x00, + APP_STATUS_BOOTING = 0x01, + APP_STATUS_UPDATING = 0x02, + APP_STATUS_BAD_APP_CONFIG = 0xff, +}; + +enum firmware_mode { + FW_MODE_BOOTLOADER = 0, + FW_MODE_APPLICATION = 1, + FW_MODE_PRODUCTION_TEST = 2, +}; + +enum dynamic_config_id { + DC_UNKNOWN = 0x00, + DC_NO_DOZE, + DC_DISABLE_NOISE_MITIGATION, + DC_INHIBIT_FREQUENCY_SHIFT, + DC_REQUESTED_FREQUENCY, + DC_DISABLE_HSYNC, + DC_REZERO_ON_EXIT_DEEP_SLEEP, + DC_CHARGER_CONNECTED, + DC_NO_BASELINE_RELAXATION, + DC_IN_WAKEUP_GESTURE_MODE, + DC_STIMULUS_FINGERS, + DC_GRIP_SUPPRESSION_ENABLED, + DC_ENABLE_THICK_GLOVE, + DC_ENABLE_GLOVE, +}; + +enum command { + CMD_NONE = 0x00, + CMD_CONTINUE_WRITE = 0x01, + CMD_IDENTIFY = 0x02, + CMD_RESET = 0x04, + CMD_ENABLE_REPORT = 0x05, + CMD_DISABLE_REPORT = 0x06, + CMD_GET_BOOT_INFO = 0x10, + CMD_ERASE_FLASH = 0x11, + CMD_WRITE_FLASH = 0x12, + CMD_READ_FLASH = 0x13, + CMD_RUN_APPLICATION_FIRMWARE = 0x14, + CMD_SPI_MASTER_WRITE_THEN_READ = 0x15, + CMD_REBOOT_TO_ROM_BOOTLOADER = 0x16, + CMD_RUN_BOOTLOADER_FIRMWARE = 0x1f, + CMD_GET_APPLICATION_INFO = 0x20, + CMD_GET_STATIC_CONFIG = 0x21, + CMD_SET_STATIC_CONFIG = 0x22, + CMD_GET_DYNAMIC_CONFIG = 0x23, + CMD_SET_DYNAMIC_CONFIG = 0x24, + CMD_GET_TOUCH_REPORT_CONFIG = 0x25, + CMD_SET_TOUCH_REPORT_CONFIG = 0x26, + CMD_REZERO = 0x27, + CMD_COMMIT_CONFIG = 0x28, + CMD_DESCRIBE_DYNAMIC_CONFIG = 0x29, + CMD_PRODUCTION_TEST = 0x2a, + CMD_SET_CONFIG_ID = 0x2b, + CMD_ENTER_DEEP_SLEEP = 0x2c, + CMD_EXIT_DEEP_SLEEP = 0x2d, + CMD_GET_TOUCH_INFO = 0x2e, + CMD_GET_DATA_LOCATION = 0x2f, + CMD_DOWNLOAD_CONFIG = 0x30, + CMD_ENTER_PRODUCTION_TEST_MODE = 0x31, + CMD_GET_FEATURES = 0x32, +}; + +enum status_code { + STATUS_IDLE = 0x00, + STATUS_OK = 0x01, + STATUS_BUSY = 0x02, + STATUS_CONTINUED_READ = 0x03, + STATUS_NOT_EXECUTED_IN_DEEP_SLEEP = 0x0b, + STATUS_RECEIVE_BUFFER_OVERFLOW = 0x0c, + STATUS_PREVIOUS_COMMAND_PENDING = 0x0d, + STATUS_NOT_IMPLEMENTED = 0x0e, + STATUS_ERROR = 0x0f, + STATUS_INVALID = 0xff, +}; + +enum report_type { + REPORT_IDENTIFY = 0x10, + REPORT_TOUCH = 0x11, + REPORT_DELTA = 0x12, + REPORT_RAW = 0x13, + REPORT_STATUS = 0x1b, + REPORT_PRINTF = 0x82, + REPORT_HDL = 0xfe, +}; + +enum command_status { + CMD_IDLE = 0, + CMD_BUSY = 1, + CMD_ERROR = -1, +}; + +enum flash_area { + BOOTLOADER = 0, + BOOT_CONFIG, + APP_FIRMWARE, + APP_CONFIG, + DISP_CONFIG, + CUSTOM_OTP, + CUSTOM_LCM, + CUSTOM_OEM, + PPDT, +}; + +enum flash_data { + LCM_DATA = 1, + OEM_DATA, + PPDT_DATA, +}; + +enum helper_task { + HELP_NONE = 0, + HELP_RUN_APPLICATION_FIRMWARE, + HELP_SEND_RESET_NOTIFICATION, +}; + +struct syna_tcm_helper { + atomic_t task; + struct work_struct work; + struct workqueue_struct *workqueue; +}; + +struct syna_tcm_watchdog { + bool run; + unsigned char count; + struct delayed_work work; + struct workqueue_struct *workqueue; +}; + +struct syna_tcm_buffer { + bool clone; + unsigned char *buf; + unsigned int buf_size; + unsigned int data_length; + struct mutex buf_mutex; +}; + +struct syna_tcm_report { + unsigned char id; + struct syna_tcm_buffer buffer; +}; + +struct syna_tcm_identification { + unsigned char version; + unsigned char mode; + unsigned char part_number[16]; + unsigned char build_id[4]; + unsigned char max_write_size[2]; +}; + +struct syna_tcm_boot_info { + unsigned char version; + unsigned char status; + unsigned char asic_id[2]; + unsigned char write_block_size_words; + unsigned char erase_page_size_words[2]; + unsigned char max_write_payload_size[2]; + unsigned char last_reset_reason; + unsigned char pc_at_time_of_last_reset[2]; + unsigned char boot_config_start_block[2]; + unsigned char boot_config_size_blocks[2]; + unsigned char display_config_start_block[4]; + unsigned char display_config_length_blocks[2]; + unsigned char backup_display_config_start_block[4]; + unsigned char backup_display_config_length_blocks[2]; + unsigned char custom_otp_start_block[2]; + unsigned char custom_otp_length_blocks[2]; +}; + +struct syna_tcm_app_info { + unsigned char version[2]; + unsigned char status[2]; + unsigned char static_config_size[2]; + unsigned char dynamic_config_size[2]; + unsigned char app_config_start_write_block[2]; + unsigned char app_config_size[2]; + unsigned char max_touch_report_config_size[2]; + unsigned char max_touch_report_payload_size[2]; + unsigned char customer_config_id[16]; + unsigned char max_x[2]; + unsigned char max_y[2]; + unsigned char max_objects[2]; + unsigned char num_of_buttons[2]; + unsigned char num_of_image_rows[2]; + unsigned char num_of_image_cols[2]; + unsigned char has_hybrid_data[2]; +}; + +struct syna_tcm_touch_info { + unsigned char image_2d_scale_factor[4]; + unsigned char image_0d_scale_factor[4]; + unsigned char hybrid_x_scale_factor[4]; + unsigned char hybrid_y_scale_factor[4]; +}; + +struct syna_tcm_message_header { + unsigned char marker; + unsigned char code; + unsigned char length[2]; +}; + +struct syna_tcm_features { + unsigned char byte_0_reserved; + unsigned char byte_1_reserved; + unsigned char dual_firmware:1; + unsigned char byte_2_reserved:7; +} __packed; + +struct syna_tcm_hcd { + pid_t isr_pid; + atomic_t command_status; + atomic_t host_downloading; + atomic_t firmware_flashing; + wait_queue_head_t hdl_wq; + wait_queue_head_t reflash_wq; + int irq; + bool init_okay; + bool do_polling; + bool in_suspend; + bool irq_enabled; + bool host_download_mode; + unsigned char marker; + unsigned char fb_ready; + unsigned char command; + unsigned char async_report_id; + unsigned char status_report_code; + unsigned char response_code; + unsigned int read_length; + unsigned int payload_length; + unsigned int packrat_number; + unsigned int rd_chunk_size; + unsigned int wr_chunk_size; + unsigned int app_status; + struct platform_device *pdev; + struct regulator *pwr_reg; + struct regulator *bus_reg; + struct kobject *sysfs_dir; + struct kobject *dynamnic_config_sysfs_dir; + struct mutex extif_mutex; + struct mutex reset_mutex; + struct mutex irq_en_mutex; + struct mutex io_ctrl_mutex; + struct mutex rw_ctrl_mutex; + struct mutex command_mutex; + struct mutex identify_mutex; + struct delayed_work polling_work; + struct workqueue_struct *polling_workqueue; + struct task_struct *notifier_thread; + struct pinctrl *ts_pinctrl; + struct pinctrl_state *pinctrl_state_active; + struct pinctrl_state *pinctrl_state_suspend; + struct pinctrl_state *pinctrl_state_release; +#if defined(CONFIG_DRM) || defined(CONFIG_FB) + struct notifier_block fb_notifier; +#endif + void *notifier_cookie; + struct syna_tcm_buffer in; + struct syna_tcm_buffer out; + struct syna_tcm_buffer resp; + struct syna_tcm_buffer temp; + struct syna_tcm_buffer config; + struct syna_tcm_report report; + struct syna_tcm_app_info app_info; + struct syna_tcm_boot_info boot_info; + struct syna_tcm_touch_info touch_info; + struct syna_tcm_identification id_info; + struct syna_tcm_helper helper; + struct syna_tcm_watchdog watchdog; + struct syna_tcm_features features; + const struct syna_tcm_hw_interface *hw_if; + int (*reset)(struct syna_tcm_hcd *tcm_hcd, bool hw, bool update_wd); + int (*sleep)(struct syna_tcm_hcd *tcm_hcd, bool en); + int (*identify)(struct syna_tcm_hcd *tcm_hcd, bool id); + int (*enable_irq)(struct syna_tcm_hcd *tcm_hcd, bool en, bool ns); + int (*switch_mode)(struct syna_tcm_hcd *tcm_hcd, + enum firmware_mode mode); + int (*read_message)(struct syna_tcm_hcd *tcm_hcd, + unsigned char *in_buf, unsigned int length); + int (*write_message)(struct syna_tcm_hcd *tcm_hcd, + unsigned char command, unsigned char *payload, + unsigned int length, unsigned char **resp_buf, + unsigned int *resp_buf_size, unsigned int *resp_length, + unsigned char *response_code, + unsigned int polling_delay_ms); + int (*get_dynamic_config)(struct syna_tcm_hcd *tcm_hcd, + enum dynamic_config_id id, unsigned short *value); + int (*set_dynamic_config)(struct syna_tcm_hcd *tcm_hcd, + enum dynamic_config_id id, unsigned short value); + int (*get_data_location)(struct syna_tcm_hcd *tcm_hcd, + enum flash_area area, unsigned int *addr, + unsigned int *length); + int (*read_flash_data)(enum flash_area area, bool run_app_firmware, + struct syna_tcm_buffer *output); + void (*report_touch)(void); + void (*update_watchdog)(struct syna_tcm_hcd *tcm_hcd, bool en); +}; + +struct syna_tcm_module_cb { + enum module_type type; + int (*init)(struct syna_tcm_hcd *tcm_hcd); + int (*remove)(struct syna_tcm_hcd *tcm_hcd); + int (*syncbox)(struct syna_tcm_hcd *tcm_hcd); + int (*asyncbox)(struct syna_tcm_hcd *tcm_hcd); + int (*reset)(struct syna_tcm_hcd *tcm_hcd); + int (*suspend)(struct syna_tcm_hcd *tcm_hcd); + int (*resume)(struct syna_tcm_hcd *tcm_hcd); + int (*early_suspend)(struct syna_tcm_hcd *tcm_hcd); +}; + +struct syna_tcm_module_handler { + bool insert; + bool detach; + struct list_head link; + struct syna_tcm_module_cb *mod_cb; +}; + +struct syna_tcm_module_pool { + bool initialized; + bool queue_work; + bool reconstructing; + struct mutex mutex; + struct list_head list; + struct work_struct work; + struct workqueue_struct *workqueue; + struct syna_tcm_hcd *tcm_hcd; +}; + +struct syna_tcm_bus_io { + unsigned char type; + int (*rmi_read)(struct syna_tcm_hcd *tcm_hcd, unsigned short addr, + unsigned char *data, unsigned int length); + int (*rmi_write)(struct syna_tcm_hcd *tcm_hcd, unsigned short addr, + unsigned char *data, unsigned int length); + int (*read)(struct syna_tcm_hcd *tcm_hcd, unsigned char *data, + unsigned int length); + int (*write)(struct syna_tcm_hcd *tcm_hcd, unsigned char *data, + unsigned int length); +}; + +struct syna_tcm_hw_interface { + struct syna_tcm_board_data *bdata; + const struct syna_tcm_bus_io *bus_io; +}; + +struct drm_panel *tcm_get_panel(void); + +int syna_tcm_bus_init(void); + +void syna_tcm_bus_exit(void); + +int syna_tcm_bus_init_spi(void); + +void syna_tcm_bus_exit_spi(void); + +int syna_tcm_add_module(struct syna_tcm_module_cb *mod_cb, bool insert); + +static inline int syna_tcm_rmi_read(struct syna_tcm_hcd *tcm_hcd, + unsigned short addr, unsigned char *data, unsigned int length) +{ + return tcm_hcd->hw_if->bus_io->rmi_read(tcm_hcd, addr, data, length); +} + +static inline int syna_tcm_rmi_write(struct syna_tcm_hcd *tcm_hcd, + unsigned short addr, unsigned char *data, unsigned int length) +{ + return tcm_hcd->hw_if->bus_io->rmi_write(tcm_hcd, addr, data, length); +} + +static inline int syna_tcm_read(struct syna_tcm_hcd *tcm_hcd, + unsigned char *data, unsigned int length) +{ + return tcm_hcd->hw_if->bus_io->read(tcm_hcd, data, length); +} + +static inline int syna_tcm_write(struct syna_tcm_hcd *tcm_hcd, + unsigned char *data, unsigned int length) +{ + return tcm_hcd->hw_if->bus_io->write(tcm_hcd, data, length); +} + +static inline ssize_t syna_tcm_show_error(struct device *dev, + struct device_attribute *attr, char *buf) +{ + pr_err("%s: Attribute not readable\n", + __func__); + + return -EPERM; +} + +static inline ssize_t syna_tcm_store_error(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + pr_err("%s: Attribute not writable\n", + __func__); + + return -EPERM; +} + +static inline int secure_memcpy(unsigned char *dest, unsigned int dest_size, + const unsigned char *src, unsigned int src_size, + unsigned int count) +{ + if (dest == NULL || src == NULL) + return -EINVAL; + + if (count > dest_size || count > src_size) { + pr_err("%s: src_size = %d, dest_size = %d, count = %d\n", + __func__, src_size, dest_size, count); + return -EINVAL; + } + + memcpy((void *)dest, (const void *)src, count); + + return 0; +} + +static inline int syna_tcm_realloc_mem(struct syna_tcm_hcd *tcm_hcd, + struct syna_tcm_buffer *buffer, unsigned int size) +{ + int retval; + unsigned char *temp; + + if (size > buffer->buf_size) { + temp = buffer->buf; + + buffer->buf = kmalloc(size, GFP_KERNEL); + if (!(buffer->buf)) { + dev_err(tcm_hcd->pdev->dev.parent, + "%s: Failed to allocate memory\n", + __func__); + kfree(temp); + buffer->buf_size = 0; + return -ENOMEM; + } + + retval = secure_memcpy(buffer->buf, + size, + temp, + buffer->buf_size, + buffer->buf_size); + if (retval < 0) { + dev_err(tcm_hcd->pdev->dev.parent, + "%s: Failed to copy data\n", + __func__); + kfree(temp); + kfree(buffer->buf); + buffer->buf_size = 0; + return retval; + } + + kfree(temp); + buffer->buf_size = size; + } + + return 0; +} + +static inline int syna_tcm_alloc_mem(struct syna_tcm_hcd *tcm_hcd, + struct syna_tcm_buffer *buffer, unsigned int size) +{ + if (size > buffer->buf_size) { + kfree(buffer->buf); + buffer->buf = kmalloc(size, GFP_KERNEL); + if (!(buffer->buf)) { + dev_err(tcm_hcd->pdev->dev.parent, + "%s: Failed to allocate memory\n", + __func__); + dev_err(tcm_hcd->pdev->dev.parent, + "%s: Allocation size = %d\n", + __func__, size); + buffer->buf_size = 0; + buffer->data_length = 0; + return -ENOMEM; + } + buffer->buf_size = size; + } + + memset(buffer->buf, 0x00, buffer->buf_size); + buffer->data_length = 0; + + return 0; +} + +static inline unsigned int le2_to_uint(const unsigned char *src) +{ + return (unsigned int)src[0] + + (unsigned int)src[1] * 0x100; +} + +static inline unsigned int le4_to_uint(const unsigned char *src) +{ + return (unsigned int)src[0] + + (unsigned int)src[1] * 0x100 + + (unsigned int)src[2] * 0x10000 + + (unsigned int)src[3] * 0x1000000; +} + +static inline unsigned int ceil_div(unsigned int dividend, + unsigned int divisor) +{ + return (dividend + divisor - 1) / divisor; +} + +#endif diff --git a/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm_device.c b/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm_device.c new file mode 100644 index 0000000000..963e2994bd --- /dev/null +++ b/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm_device.c @@ -0,0 +1,707 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2019 Scott Lin + * + * 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 report.buf\n"); + report = false; + goto exit; + } + idx = 4; + size = count - idx; + offset = 0; + report = true; + break; + case STATUS_CONTINUED_READ: + if (report == false) + goto exit; + if (count >= 2) { + idx = 2; + size = count - idx; + } + break; + default: + goto exit; + } + + if (size) { + size = MIN(size, remaining_size); + retval = secure_memcpy(&device_hcd->report.buf[offset], + device_hcd->report.buf_size - offset, + &data[idx], + count - idx, + size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy touch report data\n"); + report = false; + goto exit; + } else { + offset += size; + remaining_size -= size; + device_hcd->report.data_length += size; + } + } + + if (remaining_size) + goto exit; + + LOCK_BUFFER(tcm_hcd->report.buffer); + + tcm_hcd->report.buffer.buf = device_hcd->report.buf; + tcm_hcd->report.buffer.buf_size = device_hcd->report.buf_size; + tcm_hcd->report.buffer.data_length = device_hcd->report.data_length; + + tcm_hcd->report_touch(); + + UNLOCK_BUFFER(tcm_hcd->report.buffer); + + report = false; + +exit: + UNLOCK_BUFFER(device_hcd->report); +} + +static int device_capture_touch_report_config(unsigned int count) +{ + int retval; + unsigned int size; + unsigned int buf_size; + unsigned char *data; + struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd; + + if (device_hcd->raw_mode) { + if (count < 3) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid write data\n"); + return -EINVAL; + } + + size = le2_to_uint(&device_hcd->out.buf[1]); + + if (count - 3 < size) { + LOGE(tcm_hcd->pdev->dev.parent, + "Incomplete write data\n"); + return -EINVAL; + } + + if (!size) + return 0; + + data = &device_hcd->out.buf[3]; + buf_size = device_hcd->out.buf_size - 3; + } else { + size = count - 1; + + if (!size) + return 0; + + data = &device_hcd->out.buf[1]; + buf_size = device_hcd->out.buf_size - 1; + } + + LOCK_BUFFER(tcm_hcd->config); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &tcm_hcd->config, + size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for tcm_hcd->config.buf\n"); + UNLOCK_BUFFER(tcm_hcd->config); + return retval; + } + + retval = secure_memcpy(tcm_hcd->config.buf, + tcm_hcd->config.buf_size, + data, + buf_size, + size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy touch report config data\n"); + UNLOCK_BUFFER(tcm_hcd->config); + return retval; + } + + tcm_hcd->config.data_length = size; + + UNLOCK_BUFFER(tcm_hcd->config); + + return 0; +} + +#ifdef HAVE_UNLOCKED_IOCTL +static long device_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +#else +static int device_ioctl(struct inode *inp, struct file *filp, unsigned int cmd, + unsigned long arg) +#endif +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + retval = 0; + + switch (cmd) { + case DEVICE_IOC_RESET: + retval = tcm_hcd->reset(tcm_hcd, false, true); + break; + case DEVICE_IOC_IRQ: + if (arg == 0) + retval = tcm_hcd->enable_irq(tcm_hcd, false, false); + else if (arg == 1) + retval = tcm_hcd->enable_irq(tcm_hcd, true, NULL); + break; + case DEVICE_IOC_RAW: + if (arg == 0) { + device_hcd->raw_mode = false; + tcm_hcd->update_watchdog(tcm_hcd, true); + } else if (arg == 1) { + device_hcd->raw_mode = true; + tcm_hcd->update_watchdog(tcm_hcd, false); + } + break; + case DEVICE_IOC_CONCURRENT: + if (arg == 0) + device_hcd->concurrent = false; + else if (arg == 1) + device_hcd->concurrent = true; + break; + default: + retval = -ENOTTY; + break; + } + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static loff_t device_llseek(struct file *filp, loff_t off, int whence) +{ + return -EINVAL; +} + +static ssize_t device_read(struct file *filp, char __user *buf, + size_t count, loff_t *f_pos) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd; + + if (count == 0) + return 0; + + mutex_lock(&tcm_hcd->extif_mutex); + + LOCK_BUFFER(device_hcd->resp); + + if (device_hcd->raw_mode) { + retval = syna_tcm_alloc_mem(tcm_hcd, + &device_hcd->resp, + count); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for resp.buf\n"); + UNLOCK_BUFFER(device_hcd->resp); + goto exit; + } + + retval = tcm_hcd->read_message(tcm_hcd, + device_hcd->resp.buf, + count); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read message\n"); + UNLOCK_BUFFER(device_hcd->resp); + goto exit; + } + } else { + if (count != device_hcd->resp.data_length) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid length information\n"); + UNLOCK_BUFFER(device_hcd->resp); + retval = -EINVAL; + goto exit; + } + } + + if (copy_to_user(buf, device_hcd->resp.buf, count)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy data to user space\n"); + UNLOCK_BUFFER(device_hcd->resp); + retval = -EINVAL; + goto exit; + } + + if (!device_hcd->concurrent) + goto skip_concurrent; + + if (tcm_hcd->report_touch == NULL) { + LOGE(tcm_hcd->pdev->dev.parent, + "Unable to report touch\n"); + device_hcd->concurrent = false; + } + + if (device_hcd->raw_mode) + device_capture_touch_report(count); + +skip_concurrent: + UNLOCK_BUFFER(device_hcd->resp); + + retval = count; + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t device_write(struct file *filp, const char __user *buf, + size_t count, loff_t *f_pos) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd; + + if (count == 0) + return 0; + + mutex_lock(&tcm_hcd->extif_mutex); + + LOCK_BUFFER(device_hcd->out); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &device_hcd->out, + count == 1 ? count + 1 : count); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for device_hcd->out.buf\n"); + UNLOCK_BUFFER(device_hcd->out); + goto exit; + } + + if (copy_from_user(device_hcd->out.buf, buf, count)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy data from user space\n"); + UNLOCK_BUFFER(device_hcd->out); + retval = -EINVAL; + goto exit; + } + + LOCK_BUFFER(device_hcd->resp); + + if (device_hcd->raw_mode) { + retval = tcm_hcd->write_message(tcm_hcd, + device_hcd->out.buf[0], + &device_hcd->out.buf[1], + count - 1, + NULL, + NULL, + NULL, + NULL, + 0); + } else { + mutex_lock(&tcm_hcd->reset_mutex); + retval = tcm_hcd->write_message(tcm_hcd, + device_hcd->out.buf[0], + &device_hcd->out.buf[1], + count - 1, + &device_hcd->resp.buf, + &device_hcd->resp.buf_size, + &device_hcd->resp.data_length, + NULL, + 0); + mutex_unlock(&tcm_hcd->reset_mutex); + } + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command 0x%02x\n", + device_hcd->out.buf[0]); + UNLOCK_BUFFER(device_hcd->resp); + UNLOCK_BUFFER(device_hcd->out); + goto exit; + } + + if (count && device_hcd->out.buf[0] == CMD_SET_TOUCH_REPORT_CONFIG) { + retval = device_capture_touch_report_config(count); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to capture touch report config\n"); + } + } + + UNLOCK_BUFFER(device_hcd->out); + + if (device_hcd->raw_mode) + retval = count; + else + retval = device_hcd->resp.data_length; + + UNLOCK_BUFFER(device_hcd->resp); + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static int device_open(struct inode *inp, struct file *filp) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + if (device_hcd->ref_count < 1) { + device_hcd->ref_count++; + retval = 0; + } else { + retval = -EACCES; + } + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static int device_release(struct inode *inp, struct file *filp) +{ + struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + if (device_hcd->ref_count) + device_hcd->ref_count--; + + mutex_unlock(&tcm_hcd->extif_mutex); + + return 0; +} + +static char *device_devnode(struct device *dev, umode_t *mode) +{ + if (!mode) + return NULL; + + /* S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; */ + *mode = 0666; + + return kasprintf(GFP_KERNEL, "%s/%s", PLATFORM_DRIVER_NAME, + dev_name(dev)); +} + +static int device_create_class(void) +{ + struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd; + + if (device_hcd->class != NULL) + return 0; + + device_hcd->class = class_create(THIS_MODULE, PLATFORM_DRIVER_NAME); + + if (IS_ERR(device_hcd->class)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create class\n"); + return -ENODEV; + } + + device_hcd->class->devnode = device_devnode; + + return 0; +} + +static const struct file_operations device_fops = { + .owner = THIS_MODULE, +#ifdef HAVE_UNLOCKED_IOCTL + .unlocked_ioctl = device_ioctl, +#ifdef HAVE_COMPAT_IOCTL + .compat_ioctl = device_ioctl, +#endif +#else + .ioctl = device_ioctl, +#endif + .llseek = device_llseek, + .read = device_read, + .write = device_write, + .open = device_open, + .release = device_release, +}; + +static int device_init(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + dev_t dev_num; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + device_hcd = kzalloc(sizeof(*device_hcd), GFP_KERNEL); + if (!device_hcd) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for device_hcd\n"); + return -ENOMEM; + } + + device_hcd->tcm_hcd = tcm_hcd; + + device_hcd->concurrent = CONCURRENT; + + INIT_BUFFER(device_hcd->out, false); + INIT_BUFFER(device_hcd->resp, false); + INIT_BUFFER(device_hcd->report, false); + + if (rmidev_major_num) { + dev_num = MKDEV(rmidev_major_num, 0); + retval = register_chrdev_region(dev_num, 1, + PLATFORM_DRIVER_NAME); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to register char device\n"); + goto err_register_chrdev_region; + } + } else { + retval = alloc_chrdev_region(&dev_num, 0, 1, + PLATFORM_DRIVER_NAME); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate char device\n"); + goto err_alloc_chrdev_region; + } + + rmidev_major_num = MAJOR(dev_num); + } + + device_hcd->dev_num = dev_num; + + cdev_init(&device_hcd->char_dev, &device_fops); + + retval = cdev_add(&device_hcd->char_dev, dev_num, 1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to add char device\n"); + goto err_add_chardev; + } + + retval = device_create_class(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create class\n"); + goto err_create_class; + } + + device_hcd->device = device_create(device_hcd->class, NULL, + device_hcd->dev_num, NULL, CHAR_DEVICE_NAME"%d", + MINOR(device_hcd->dev_num)); + if (IS_ERR(device_hcd->device)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create device\n"); + retval = -ENODEV; + goto err_create_device; + } + + if (bdata->irq_gpio >= 0) { + retval = gpio_export(bdata->irq_gpio, false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to export GPIO\n"); + } else { + retval = gpio_export_link(&tcm_hcd->pdev->dev, + "attn", bdata->irq_gpio); + if (retval < 0) + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to export GPIO link\n"); + } + } + + return 0; + +err_create_device: + class_destroy(device_hcd->class); + +err_create_class: + cdev_del(&device_hcd->char_dev); + +err_add_chardev: + unregister_chrdev_region(dev_num, 1); + +err_alloc_chrdev_region: +err_register_chrdev_region: + RELEASE_BUFFER(device_hcd->report); + RELEASE_BUFFER(device_hcd->resp); + RELEASE_BUFFER(device_hcd->out); + + kfree(device_hcd); + device_hcd = NULL; + + return retval; +} + +static int device_remove(struct syna_tcm_hcd *tcm_hcd) +{ + if (!device_hcd) + goto exit; + + device_destroy(device_hcd->class, device_hcd->dev_num); + + class_destroy(device_hcd->class); + + cdev_del(&device_hcd->char_dev); + + unregister_chrdev_region(device_hcd->dev_num, 1); + + RELEASE_BUFFER(device_hcd->report); + RELEASE_BUFFER(device_hcd->resp); + RELEASE_BUFFER(device_hcd->out); + + kfree(device_hcd); + device_hcd = NULL; + +exit: + complete(&device_remove_complete); + + return 0; +} + +static int device_reset(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + + if (!device_hcd) { + retval = device_init(tcm_hcd); + return retval; + } + + return 0; +} + +static struct syna_tcm_module_cb device_module = { + .type = TCM_DEVICE, + .init = device_init, + .remove = device_remove, + .syncbox = NULL, + .asyncbox = NULL, + .reset = device_reset, + .suspend = NULL, + .resume = NULL, + .early_suspend = NULL, +}; + +static int __init device_module_init(void) +{ + return syna_tcm_add_module(&device_module, true); +} + +static void __exit device_module_exit(void) +{ + syna_tcm_add_module(&device_module, false); + + wait_for_completion(&device_remove_complete); +} + +module_init(device_module_init); +module_exit(device_module_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics TCM Device Module"); +MODULE_LICENSE("GPL v2"); diff --git a/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm_diagnostics.c b/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm_diagnostics.c new file mode 100644 index 0000000000..92472e1086 --- /dev/null +++ b/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm_diagnostics.c @@ -0,0 +1,561 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2019 Scott Lin + * + * 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 "synaptics_tcm_core.h" + +#define SYSFS_DIR_NAME "diagnostics" + +enum pingpong_state { + PING = 0, + PONG = 1, +}; + +struct diag_hcd { + pid_t pid; + unsigned char report_type; + enum pingpong_state state; + struct kobject *sysfs_dir; + struct siginfo sigio; + struct task_struct *task; + struct syna_tcm_buffer ping; + struct syna_tcm_buffer pong; + struct syna_tcm_hcd *tcm_hcd; +}; + +DECLARE_COMPLETION(diag_remove_complete); + +static struct diag_hcd *diag_hcd; + +STORE_PROTOTYPE(diag, pid); +SHOW_PROTOTYPE(diag, size); +STORE_PROTOTYPE(diag, type); +SHOW_PROTOTYPE(diag, rows); +SHOW_PROTOTYPE(diag, cols); +SHOW_PROTOTYPE(diag, hybrid); +SHOW_PROTOTYPE(diag, buttons); + +static struct device_attribute *attrs[] = { + ATTRIFY(pid), + ATTRIFY(size), + ATTRIFY(type), + ATTRIFY(rows), + ATTRIFY(cols), + ATTRIFY(hybrid), + ATTRIFY(buttons), +}; + +static ssize_t diag_sysfs_data_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static struct bin_attribute bin_attr = { + .attr = { + .name = "data", + .mode = 0444, + }, + .size = 0, + .read = diag_sysfs_data_show, +}; + +static ssize_t diag_sysfs_pid_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned int input; + struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd; + + if (kstrtouint(buf, 10, &input)) + return -EINVAL; + + mutex_lock(&tcm_hcd->extif_mutex); + + diag_hcd->pid = input; + + if (diag_hcd->pid) { + diag_hcd->task = pid_task(find_vpid(diag_hcd->pid), + PIDTYPE_PID); + if (!diag_hcd->task) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to locate task\n"); + retval = -EINVAL; + goto exit; + } + } + + retval = count; + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t diag_sysfs_size_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + if (diag_hcd->state == PING) { + LOCK_BUFFER(diag_hcd->ping); + + retval = snprintf(buf, PAGE_SIZE, + "%u\n", + diag_hcd->ping.data_length); + + UNLOCK_BUFFER(diag_hcd->ping); + } else { + LOCK_BUFFER(diag_hcd->pong); + + retval = snprintf(buf, PAGE_SIZE, + "%u\n", + diag_hcd->pong.data_length); + + UNLOCK_BUFFER(diag_hcd->pong); + } + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t diag_sysfs_type_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int input; + struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd; + + if (kstrtouint(buf, 10, &input)) + return -EINVAL; + + mutex_lock(&tcm_hcd->extif_mutex); + + diag_hcd->report_type = (unsigned char)input; + + mutex_unlock(&tcm_hcd->extif_mutex); + + return count; +} + +static ssize_t diag_sysfs_rows_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + unsigned int rows; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + if (tcm_hcd->id_info.mode != MODE_APPLICATION || + tcm_hcd->app_status != APP_STATUS_OK) { + retval = -ENODEV; + goto exit; + } + + app_info = &tcm_hcd->app_info; + rows = le2_to_uint(app_info->num_of_image_rows); + + retval = snprintf(buf, PAGE_SIZE, "%u\n", rows); + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t diag_sysfs_cols_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + unsigned int cols; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + if (tcm_hcd->id_info.mode != MODE_APPLICATION || + tcm_hcd->app_status != APP_STATUS_OK) { + retval = -ENODEV; + goto exit; + } + + app_info = &tcm_hcd->app_info; + cols = le2_to_uint(app_info->num_of_image_cols); + + retval = snprintf(buf, PAGE_SIZE, "%u\n", cols); + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t diag_sysfs_hybrid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + unsigned int hybrid; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + if (tcm_hcd->id_info.mode != MODE_APPLICATION || + tcm_hcd->app_status != APP_STATUS_OK) { + retval = -ENODEV; + goto exit; + } + + app_info = &tcm_hcd->app_info; + hybrid = le2_to_uint(app_info->has_hybrid_data); + + retval = snprintf(buf, PAGE_SIZE, "%u\n", hybrid); + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t diag_sysfs_buttons_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + unsigned int buttons; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + if (tcm_hcd->id_info.mode != MODE_APPLICATION || + tcm_hcd->app_status != APP_STATUS_OK) { + retval = -ENODEV; + goto exit; + } + + app_info = &tcm_hcd->app_info; + buttons = le2_to_uint(app_info->num_of_buttons); + + retval = snprintf(buf, PAGE_SIZE, "%u\n", buttons); + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t diag_sysfs_data_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + unsigned int readlen; + struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + retval = 0; + + if (diag_hcd->state == PING) { + LOCK_BUFFER(diag_hcd->ping); + + if (diag_hcd->ping.data_length == 0) { + readlen = 0; + goto exit; + } + + readlen = MIN(count, diag_hcd->ping.data_length - pos); + + if (diag_hcd->ping.data_length) { + retval = secure_memcpy(buf, + count, + &diag_hcd->ping.buf[pos], + diag_hcd->ping.buf_size - pos, + readlen); + } + + UNLOCK_BUFFER(diag_hcd->ping); + } else { + LOCK_BUFFER(diag_hcd->pong); + + if (diag_hcd->pong.data_length == 0) { + readlen = 0; + goto exit; + } + + readlen = MIN(count, diag_hcd->pong.data_length - pos); + + if (diag_hcd->pong.data_length) { + retval = secure_memcpy(buf, + count, + &diag_hcd->pong.buf[pos], + diag_hcd->pong.buf_size - pos, + readlen); + } + + UNLOCK_BUFFER(diag_hcd->pong); + } + +exit: + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy report data\n"); + } else { + retval = readlen; + } + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static void diag_report(void) +{ + int retval; + static enum pingpong_state state = PING; + struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd; + + if (state == PING) { + LOCK_BUFFER(diag_hcd->ping); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &diag_hcd->ping, + tcm_hcd->report.buffer.data_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for ping.buf\n"); + UNLOCK_BUFFER(diag_hcd->ping); + return; + } + + retval = secure_memcpy(diag_hcd->ping.buf, + diag_hcd->ping.buf_size, + tcm_hcd->report.buffer.buf, + tcm_hcd->report.buffer.buf_size, + tcm_hcd->report.buffer.data_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy report data\n"); + UNLOCK_BUFFER(diag_hcd->ping); + return; + } + + diag_hcd->ping.data_length = tcm_hcd->report.buffer.data_length; + + UNLOCK_BUFFER(diag_hcd->ping); + + diag_hcd->state = state; + state = PONG; + } else { + LOCK_BUFFER(diag_hcd->pong); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &diag_hcd->pong, + tcm_hcd->report.buffer.data_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for pong.buf\n"); + UNLOCK_BUFFER(diag_hcd->pong); + return; + } + + retval = secure_memcpy(diag_hcd->pong.buf, + diag_hcd->pong.buf_size, + tcm_hcd->report.buffer.buf, + tcm_hcd->report.buffer.buf_size, + tcm_hcd->report.buffer.data_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy report data\n"); + UNLOCK_BUFFER(diag_hcd->pong); + return; + } + + diag_hcd->pong.data_length = tcm_hcd->report.buffer.data_length; + + UNLOCK_BUFFER(diag_hcd->pong); + + diag_hcd->state = state; + state = PING; + } +} + +static int diag_init(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + int idx; + + diag_hcd = kzalloc(sizeof(*diag_hcd), GFP_KERNEL); + if (!diag_hcd) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for diag_hcd\n"); + return -ENOMEM; + } + + diag_hcd->tcm_hcd = tcm_hcd; + diag_hcd->state = PING; + + INIT_BUFFER(diag_hcd->ping, false); + INIT_BUFFER(diag_hcd->pong, false); + + memset(&diag_hcd->sigio, 0x00, sizeof(diag_hcd->sigio)); + diag_hcd->sigio.si_signo = SIGIO; + diag_hcd->sigio.si_code = SI_USER; + + diag_hcd->sysfs_dir = kobject_create_and_add(SYSFS_DIR_NAME, + tcm_hcd->sysfs_dir); + if (!diag_hcd->sysfs_dir) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs directory\n"); + retval = -EINVAL; + goto err_sysfs_create_dir; + } + + for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) { + retval = sysfs_create_file(diag_hcd->sysfs_dir, + &(*attrs[idx]).attr); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs file\n"); + goto err_sysfs_create_file; + } + } + + retval = sysfs_create_bin_file(diag_hcd->sysfs_dir, &bin_attr); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs bin file\n"); + goto err_sysfs_create_bin_file; + } + + return 0; + +err_sysfs_create_bin_file: +err_sysfs_create_file: + for (idx--; idx >= 0; idx--) + sysfs_remove_file(diag_hcd->sysfs_dir, &(*attrs[idx]).attr); + + kobject_put(diag_hcd->sysfs_dir); + +err_sysfs_create_dir: + RELEASE_BUFFER(diag_hcd->pong); + RELEASE_BUFFER(diag_hcd->ping); + + kfree(diag_hcd); + diag_hcd = NULL; + + return retval; +} + +static int diag_remove(struct syna_tcm_hcd *tcm_hcd) +{ + int idx; + + if (!diag_hcd) + goto exit; + + sysfs_remove_bin_file(diag_hcd->sysfs_dir, &bin_attr); + + for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) + sysfs_remove_file(diag_hcd->sysfs_dir, &(*attrs[idx]).attr); + + kobject_put(diag_hcd->sysfs_dir); + + RELEASE_BUFFER(diag_hcd->pong); + RELEASE_BUFFER(diag_hcd->ping); + + kfree(diag_hcd); + diag_hcd = NULL; + +exit: + complete(&diag_remove_complete); + + return 0; +} + +static int diag_syncbox(struct syna_tcm_hcd *tcm_hcd) +{ + if (!diag_hcd) + return 0; + + if (tcm_hcd->report.id == diag_hcd->report_type) + diag_report(); + + return 0; +} + +static int diag_reset(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + + if (!diag_hcd) { + retval = diag_init(tcm_hcd); + return retval; + } + + return 0; +} + +static struct syna_tcm_module_cb diag_module = { + .type = TCM_DIAGNOSTICS, + .init = diag_init, + .remove = diag_remove, + .syncbox = diag_syncbox, + .asyncbox = NULL, + .reset = diag_reset, + .suspend = NULL, + .resume = NULL, + .early_suspend = NULL, +}; + +static int __init diag_module_init(void) +{ + return syna_tcm_add_module(&diag_module, true); +} + +static void __exit diag_module_exit(void) +{ + syna_tcm_add_module(&diag_module, false); + + wait_for_completion(&diag_remove_complete); +} + +module_init(diag_module_init); +module_exit(diag_module_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics TCM Diagnostics Module"); +MODULE_LICENSE("GPL v2"); diff --git a/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm_i2c.c b/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm_i2c.c new file mode 100644 index 0000000000..5783839058 --- /dev/null +++ b/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm_i2c.c @@ -0,0 +1,533 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2019 Scott Lin + * + * 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" +#include "linux/moduleparam.h" + +#define XFER_ATTEMPTS 10 + +static unsigned char *buf; + +static unsigned int buf_size; + +static struct syna_tcm_bus_io bus_io; + +static struct syna_tcm_hw_interface hw_if; + +static struct platform_device *syna_tcm_i2c_device; + +static struct drm_panel *active_tcm_panel; + +struct drm_panel *tcm_get_panel(void) +{ + return active_tcm_panel; +} +EXPORT_SYMBOL(tcm_get_panel); + +#ifdef CONFIG_OF +static int parse_dt(struct device *dev, struct syna_tcm_board_data *bdata) +{ + int retval; + struct device_node *np = dev->of_node; + + retval = of_get_named_gpio_flags(np, + "synaptics,irq-gpio", 0, + (enum of_gpio_flags *)&bdata->irq_flags); + if (!gpio_is_valid(retval)) { + if (retval != -EPROBE_DEFER) + dev_err(dev, "Error getting irq_gpio\n"); + return retval; + } + bdata->irq_gpio = retval; + + of_property_read_u32(np, "synaptics,irq-on-state", + &bdata->irq_on_state); + of_property_read_string(np, "synaptics,pwr-reg-name", + &bdata->pwr_reg_name); + of_property_read_string(np, "synaptics,bus-reg-name", + &bdata->bus_reg_name); + of_property_read_string(np, "synaptics,firmware-name", + &bdata->fw_name); + + bdata->power_gpio = of_get_named_gpio_flags(np, + "synaptics,power-gpio", 0, NULL); + + retval = of_property_read_u32(np, "synaptics,power-on-state", + &bdata->power_on_state); + if (retval < 0) { + LOGD(dev, "Failed to read synaptics,power-on-state\n"); + bdata->power_on_state = 0; + } + + retval = of_property_read_u32(np, "synaptics,power-delay-ms", + &bdata->power_delay_ms); + if (retval < 0) { + LOGE(dev, "Failed to read synaptics,power-delay-ms\n"); + return retval; + } + + retval = of_get_named_gpio_flags(np, + "synaptics,reset-gpio", 0, NULL); + if (!gpio_is_valid(retval)) { + if (retval != -EPROBE_DEFER) + dev_err(dev, "Error getting reset gpio\n"); + return retval; + } + bdata->reset_gpio = retval; + + retval = of_property_read_u32(np, "synaptics,reset-on-state", + &bdata->reset_on_state); + if (retval < 0) { + LOGE(dev, "Failed to read synaptics,reset-on-state\n"); + return retval; + } + + retval = of_property_read_u32(np, "synaptics,reset-active-ms", + &bdata->reset_active_ms); + if (retval < 0) { + LOGE(dev, "Failed to read synaptics,reset-active-ms\n"); + return retval; + } + + retval = of_property_read_u32(np, "synaptics,reset-delay-ms", + &bdata->reset_delay_ms); + if (retval < 0) { + LOGE(dev, "Unable to read synaptics,reset-delay-ms\n"); + return retval; + } + + bdata->x_flip = of_property_read_bool(np, "synaptics,x-flip"); + bdata->y_flip = of_property_read_bool(np, "synaptics,y-flip"); + bdata->swap_axes = of_property_read_bool(np, "synaptics,swap-axes"); + + retval = of_property_read_u32(np, "synaptics,ubl-i2c-addr", + &bdata->ubl_i2c_addr); + if (retval < 0) { + LOGE(dev, "Unable to read synaptics,ubl-i2c-addr\n"); + return retval; + } + + bdata->extend_report = of_property_read_bool(np, + "synaptics,extend_report"); + + return 0; +} +#endif + +static int syna_tcm_i2c_alloc_mem(struct syna_tcm_hcd *tcm_hcd, + unsigned int size) +{ + struct i2c_client *i2c = to_i2c_client(tcm_hcd->pdev->dev.parent); + + if (size > buf_size) { + if (buf_size) + kfree(buf); + buf = kmalloc(size, GFP_KERNEL); + if (!buf) { + LOGE(&i2c->dev, + "Failed to allocate memory for buf\n"); + buf_size = 0; + return -ENOMEM; + } + buf_size = size; + } + + return 0; +} + +static int syna_tcm_i2c_rmi_read(struct syna_tcm_hcd *tcm_hcd, + unsigned short addr, unsigned char *data, unsigned int length) +{ + int retval = 0; + unsigned char address; + unsigned int attempt; + struct i2c_msg msg[2]; + struct i2c_client *i2c = to_i2c_client(tcm_hcd->pdev->dev.parent); + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + mutex_lock(&tcm_hcd->io_ctrl_mutex); + + address = (unsigned char)addr; + + msg[0].addr = bdata->ubl_i2c_addr; + msg[0].flags = 0; + msg[0].len = 1; + msg[0].buf = &address; + + msg[1].addr = bdata->ubl_i2c_addr; + msg[1].flags = I2C_M_RD; + msg[1].len = length; + msg[1].buf = data; + + for (attempt = 0; attempt < XFER_ATTEMPTS; attempt++) { + if (i2c_transfer(i2c->adapter, msg, 2) == 2) { + retval = length; + goto exit; + } + + LOGD(&i2c->dev, "Transfer attempt %d times\n", attempt + 1); + + if (attempt + 1 == XFER_ATTEMPTS) { + LOGE(&i2c->dev, "Transfer failed\n"); + retval = -EIO; + goto exit; + } + + msleep(20); + } + +exit: + mutex_unlock(&tcm_hcd->io_ctrl_mutex); + + return retval; +} + +static int syna_tcm_i2c_rmi_write(struct syna_tcm_hcd *tcm_hcd, + unsigned short addr, unsigned char *data, unsigned int length) +{ + int retval; + unsigned int attempt; + unsigned int byte_count; + struct i2c_msg msg; + struct i2c_client *i2c = to_i2c_client(tcm_hcd->pdev->dev.parent); + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + mutex_lock(&tcm_hcd->io_ctrl_mutex); + + byte_count = length + 1; + + retval = syna_tcm_i2c_alloc_mem(tcm_hcd, byte_count); + if (retval < 0) { + LOGE(&i2c->dev, + "Failed to allocate memory\n"); + goto exit; + } + + buf[0] = (unsigned char)addr; + retval = secure_memcpy(&buf[1], + buf_size - 1, + data, + length, + length); + if (retval < 0) { + LOGE(&i2c->dev, + "Failed to copy write data\n"); + goto exit; + } + + msg.addr = bdata->ubl_i2c_addr; + msg.flags = 0; + msg.len = byte_count; + msg.buf = buf; + + for (attempt = 0; attempt < XFER_ATTEMPTS; attempt++) { + if (i2c_transfer(i2c->adapter, &msg, 1) == 1) { + retval = length; + goto exit; + } + + LOGD(&i2c->dev, "Transfer attempt %d times\n", attempt + 1); + + if (attempt + 1 == XFER_ATTEMPTS) { + LOGE(&i2c->dev, "Transfer failed\n"); + retval = -EIO; + goto exit; + } + + msleep(20); + } + +exit: + mutex_unlock(&tcm_hcd->io_ctrl_mutex); + + return retval; +} + +static int syna_tcm_i2c_read(struct syna_tcm_hcd *tcm_hcd, unsigned char *data, + unsigned int length) +{ + int retval = 0; + unsigned int attempt; + struct i2c_msg msg; + struct i2c_client *i2c = to_i2c_client(tcm_hcd->pdev->dev.parent); + + mutex_lock(&tcm_hcd->io_ctrl_mutex); + + msg.addr = i2c->addr; + msg.flags = I2C_M_RD; + msg.len = length; + msg.buf = data; + + for (attempt = 0; attempt < XFER_ATTEMPTS; attempt++) { + if (i2c_transfer(i2c->adapter, &msg, 1) == 1) { + retval = length; + goto exit; + } + + LOGD(&i2c->dev, "Transfer attempt %d times\n", attempt + 1); + + if (attempt + 1 == XFER_ATTEMPTS) { + LOGE(&i2c->dev, "Transfer failed\n"); + retval = -EIO; + goto exit; + } + + msleep(20); + } + +exit: + mutex_unlock(&tcm_hcd->io_ctrl_mutex); + + return retval; +} + +static int syna_tcm_i2c_write(struct syna_tcm_hcd *tcm_hcd, unsigned char *data, + unsigned int length) +{ + int retval = 0; + unsigned int attempt; + struct i2c_msg msg; + struct i2c_client *i2c = to_i2c_client(tcm_hcd->pdev->dev.parent); + + mutex_lock(&tcm_hcd->io_ctrl_mutex); + + msg.addr = i2c->addr; + msg.flags = 0; + msg.len = length; + msg.buf = data; + + for (attempt = 0; attempt < XFER_ATTEMPTS; attempt++) { + if (i2c_transfer(i2c->adapter, &msg, 1) == 1) { + retval = length; + goto exit; + } + + LOGD(&i2c->dev, "Transfer attempt %d times\n", attempt + 1); + + if (attempt + 1 == XFER_ATTEMPTS) { + LOGE(&i2c->dev, "Transfer failed\n"); + retval = -EIO; + goto exit; + } + + msleep(20); + } + +exit: + mutex_unlock(&tcm_hcd->io_ctrl_mutex); + + return retval; +} + +static int syna_tcm_check_dt(struct device_node *np) +{ + int i; + int count; + struct device_node *node; + struct drm_panel *panel; + + count = of_count_phandle_with_args(np, "panel", NULL); + if (count <= 0) + return 0; + + for (i = 0; i < count; i++) { + node = of_parse_phandle(np, "panel", i); + panel = of_drm_find_panel(node); + of_node_put(node); + if (!IS_ERR(panel)) { + active_tcm_panel = panel; + return 0; + } + } + + return PTR_ERR(panel); +} + +static int syna_tcm_check_default_tp(struct device_node *dt, const char *prop) +{ + const char *active_tp; + const char *compatible; + char *start; + int ret; + + ret = of_property_read_string(dt->parent, prop, &active_tp); + if (ret) { + pr_err(" %s:fail to read %s %d\n", __func__, prop, ret); + return -ENODEV; + } + + ret = of_property_read_string(dt, "compatible", &compatible); + if (ret < 0) { + pr_err(" %s:fail to read %s %d\n", __func__, "compatible", ret); + return -ENODEV; + } + + start = strnstr(active_tp, compatible, strlen(active_tp)); + if (start == NULL) { + pr_err(" %s:no match compatible, %s, %s\n", + __func__, compatible, active_tp); + ret = -ENODEV; + } + + return ret; +} + +static int syna_tcm_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *dev_id) +{ + int retval; + struct device_node *dt = i2c->dev.of_node; + + retval = syna_tcm_check_dt(dt); + if (retval == -EPROBE_DEFER) + return retval; + + if (retval) { + if (!syna_tcm_check_default_tp(dt, "qcom,i2c-touch-active")) + retval = -EPROBE_DEFER; + else + retval = -ENODEV; + + return retval; + } + + syna_tcm_i2c_device = platform_device_alloc(PLATFORM_DRIVER_NAME, 0); + if (!syna_tcm_i2c_device) { + LOGE(&i2c->dev, + "Failed to allocate platform device\n"); + return -ENOMEM; + } + +#ifdef CONFIG_OF + hw_if.bdata = devm_kzalloc(&i2c->dev, sizeof(*hw_if.bdata), GFP_KERNEL); + if (!hw_if.bdata) { + LOGE(&i2c->dev, + "Failed to allocate memory for board data\n"); + return -ENOMEM; + } + retval = parse_dt(&i2c->dev, hw_if.bdata); + if (retval < 0) { + LOGE(&i2c->dev, "Failed to parse dt\n"); + return retval; + } +#else + hw_if.bdata = i2c->dev.platform_data; +#endif + + bus_io.type = BUS_I2C; + bus_io.read = syna_tcm_i2c_read; + bus_io.write = syna_tcm_i2c_write; + bus_io.rmi_read = syna_tcm_i2c_rmi_read; + bus_io.rmi_write = syna_tcm_i2c_rmi_write; + + hw_if.bus_io = &bus_io; + + syna_tcm_i2c_device->dev.parent = &i2c->dev; + syna_tcm_i2c_device->dev.platform_data = &hw_if; + + retval = platform_device_add(syna_tcm_i2c_device); + if (retval < 0) { + LOGE(&i2c->dev, + "Failed to add platform device\n"); + return retval; + } + + return 0; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0)) +static void syna_tcm_i2c_remove(struct i2c_client *i2c) +{ + syna_tcm_i2c_device->dev.platform_data = NULL; + + platform_device_unregister(syna_tcm_i2c_device); +} +#else +static int syna_tcm_i2c_remove(struct i2c_client *i2c) +{ + syna_tcm_i2c_device->dev.platform_data = NULL; + + platform_device_unregister(syna_tcm_i2c_device); + + return 0; +} +#endif + +static const struct i2c_device_id syna_tcm_id_table[] = { + {I2C_MODULE_NAME, 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, syna_tcm_id_table); + +#ifdef CONFIG_OF +static const struct of_device_id syna_tcm_of_match_table[] = { + { + .compatible = "synaptics,tcm-i2c", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, syna_tcm_of_match_table); +#else +#define syna_tcm_of_match_table NULL +#endif + +static struct i2c_driver syna_tcm_i2c_driver = { + .driver = { + .name = I2C_MODULE_NAME, + .owner = THIS_MODULE, + .of_match_table = syna_tcm_of_match_table, + }, + .probe = syna_tcm_i2c_probe, + .remove = syna_tcm_i2c_remove, + .id_table = syna_tcm_id_table, +}; + +int syna_tcm_bus_init(void) +{ + return i2c_add_driver(&syna_tcm_i2c_driver); +} +EXPORT_SYMBOL(syna_tcm_bus_init); + +void syna_tcm_bus_exit(void) +{ + kfree(buf); + + i2c_del_driver(&syna_tcm_i2c_driver); +} +EXPORT_SYMBOL(syna_tcm_bus_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics TCM I2C Bus Module"); +MODULE_LICENSE("GPL v2"); diff --git a/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm_recovery.c b/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm_recovery.c new file mode 100644 index 0000000000..3be072faf1 --- /dev/null +++ b/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm_recovery.c @@ -0,0 +1,898 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2019 Scott Lin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#include "synaptics_tcm_core.h" + +#define SET_UP_RECOVERY_MODE true + +#define ENABLE_SYSFS_INTERFACE true + +#define SYSFS_DIR_NAME "recovery" + +#define IHEX_BUF_SIZE (2048 * 1024) + +#define DATA_BUF_SIZE (512 * 1024) + +#define IHEX_RECORD_SIZE 14 + +#define PDT_START_ADDR 0x00e9 + +#define UBL_FN_NUMBER 0x35 + +#define F35_CHUNK_SIZE 16 + +#define F35_CHUNK_SIZE_WORDS 8 + +#define F35_ERASE_ALL_WAIT_MS 5000 + +#define F35_ERASE_ALL_POLL_MS 100 + +#define F35_DATA5_OFFSET 5 + +#define F35_CTRL3_OFFSET 18 + +#define F35_RESET_COMMAND 16 + +#define F35_ERASE_ALL_COMMAND 3 + +#define F35_WRITE_CHUNK_COMMAND 2 + +#define F35_READ_FLASH_STATUS_COMMAND 1 + +struct rmi_pdt_entry { + unsigned char query_base_addr; + unsigned char command_base_addr; + unsigned char control_base_addr; + unsigned char data_base_addr; + unsigned char intr_src_count:3; + unsigned char reserved_1:2; + unsigned char fn_version:2; + unsigned char reserved_2:1; + unsigned char fn_number; +} __packed; + +struct rmi_addr { + unsigned short query_base; + unsigned short command_base; + unsigned short control_base; + unsigned short data_base; +}; + +struct recovery_hcd { + bool set_up_recovery_mode; + unsigned char chunk_buf[F35_CHUNK_SIZE + 3]; + unsigned char out_buf[3]; + unsigned char *ihex_buf; + unsigned char *data_buf; + unsigned int ihex_size; + unsigned int ihex_records; + unsigned int data_entries; + struct kobject *sysfs_dir; + struct rmi_addr f35_addr; + struct syna_tcm_hcd *tcm_hcd; +}; + +DECLARE_COMPLETION(recovery_remove_complete); + +static struct recovery_hcd *recovery_hcd; + +static int recovery_do_recovery(void); + +STORE_PROTOTYPE(recovery, recovery); + +static struct device_attribute *attrs[] = { + ATTRIFY(recovery), +}; + +static ssize_t recovery_sysfs_ihex_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static struct bin_attribute bin_attr = { + .attr = { + .name = "ihex", + .mode = 0220, + }, + .size = 0, + .write = recovery_sysfs_ihex_store, +}; + +static ssize_t recovery_sysfs_recovery_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned int input; + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + + if (kstrtouint(buf, 10, &input)) + return -EINVAL; + + if (input == 1) + recovery_hcd->set_up_recovery_mode = true; + else if (input == 2) + recovery_hcd->set_up_recovery_mode = false; + else + return -EINVAL; + + mutex_lock(&tcm_hcd->extif_mutex); + + if (recovery_hcd->ihex_size == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get ihex data\n"); + retval = -EINVAL; + goto exit; + } + + if (recovery_hcd->ihex_size % IHEX_RECORD_SIZE) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid ihex data\n"); + retval = -EINVAL; + goto exit; + } + + recovery_hcd->ihex_records = recovery_hcd->ihex_size / IHEX_RECORD_SIZE; + + retval = recovery_do_recovery(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do recovery\n"); + goto exit; + } + + retval = count; + +exit: + recovery_hcd->set_up_recovery_mode = SET_UP_RECOVERY_MODE; + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t recovery_sysfs_ihex_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + retval = secure_memcpy(&recovery_hcd->ihex_buf[pos], + IHEX_BUF_SIZE - pos, + buf, + count, + count); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy ihex data\n"); + recovery_hcd->ihex_size = 0; + goto exit; + } + + recovery_hcd->ihex_size = pos + count; + + retval = count; + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static int recovery_device_reset(void) +{ + int retval; + unsigned char command; + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + command = F35_RESET_COMMAND; + + retval = syna_tcm_rmi_write(tcm_hcd, + recovery_hcd->f35_addr.control_base + F35_CTRL3_OFFSET, + &command, + sizeof(command)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write F$35 command\n"); + return retval; + } + + msleep(bdata->reset_delay_ms); + + return 0; +} + +static int recovery_add_data_entry(unsigned char data) +{ + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + + if (recovery_hcd->data_entries >= DATA_BUF_SIZE) { + LOGE(tcm_hcd->pdev->dev.parent, + "Reached data buffer size limit\n"); + return -EINVAL; + } + + recovery_hcd->data_buf[recovery_hcd->data_entries++] = data; + + return 0; +} + +static int recovery_add_padding(unsigned int *words) +{ + int retval; + unsigned int padding; + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + + padding = (F35_CHUNK_SIZE_WORDS - *words % F35_CHUNK_SIZE_WORDS); + padding %= F35_CHUNK_SIZE_WORDS; + + while (padding) { + retval = recovery_add_data_entry(0xff); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to add data entry\n"); + return retval; + } + + retval = recovery_add_data_entry(0xff); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to add data entry\n"); + return retval; + } + + (*words)++; + padding--; + } + + return 0; +} + +static int recovery_parse_ihex(void) +{ + int retval; + unsigned char colon; + unsigned char *buf; + unsigned int addr; + unsigned int type; + unsigned int addrl; + unsigned int addrh; + unsigned int data0; + unsigned int data1; + unsigned int count; + unsigned int words; + unsigned int offset; + unsigned int record; + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + + words = 0; + + offset = 0; + + buf = recovery_hcd->ihex_buf; + + recovery_hcd->data_entries = 0; + + for (record = 0; record < recovery_hcd->ihex_records; record++) { + buf[(record + 1) * IHEX_RECORD_SIZE - 1] = 0x00; + retval = sscanf(&buf[record * IHEX_RECORD_SIZE], + "%c%02x%02x%02x%02x%02x%02x", + &colon, + &count, + &addrh, + &addrl, + &type, + &data0, + &data1); + if (retval != 7) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read ihex record\n"); + return -EINVAL; + } + + if (type == 0x00) { + if ((words % F35_CHUNK_SIZE_WORDS) == 0) { + addr = (addrh << 8) + addrl; + addr += offset; + addr >>= 4; + + retval = recovery_add_data_entry(addr); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to add data entry\n"); + return retval; + } + + retval = recovery_add_data_entry(addr >> 8); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to add data entry\n"); + return retval; + } + } + + retval = recovery_add_data_entry(data0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to add data entry\n"); + return retval; + } + + retval = recovery_add_data_entry(data1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to add data entry\n"); + return retval; + } + + words++; + } else if (type == 0x02) { + retval = recovery_add_padding(&words); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to add padding\n"); + return retval; + } + + offset = (data0 << 8) + data1; + offset <<= 4; + } + } + + retval = recovery_add_padding(&words); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to add padding\n"); + return retval; + } + + return 0; +} + +static int recovery_check_status(void) +{ + int retval; + unsigned char status; + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + + retval = syna_tcm_rmi_read(tcm_hcd, + recovery_hcd->f35_addr.data_base, + &status, + sizeof(status)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read status\n"); + return retval; + } + + status = status & 0x1f; + + if (status != 0x00) { + LOGE(tcm_hcd->pdev->dev.parent, + "Recovery mode status = 0x%02x\n", + status); + return -EINVAL; + } + + return 0; +} + +static int recovery_write_flash(void) +{ + int retval; + unsigned char *data_ptr; + unsigned int chunk_buf_size; + unsigned int chunk_data_size; + unsigned int entries_written; + unsigned int entries_to_write; + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + + entries_written = 0; + + data_ptr = recovery_hcd->data_buf; + + chunk_buf_size = sizeof(recovery_hcd->chunk_buf); + + chunk_data_size = chunk_buf_size - 1; + + recovery_hcd->chunk_buf[chunk_buf_size - 1] = F35_WRITE_CHUNK_COMMAND; + + while (entries_written < recovery_hcd->data_entries) { + entries_to_write = F35_CHUNK_SIZE + 2; + + retval = secure_memcpy(recovery_hcd->chunk_buf, + chunk_buf_size - 1, + data_ptr, + recovery_hcd->data_entries - entries_written, + entries_to_write); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy chunk data\n"); + return retval; + } + + retval = syna_tcm_rmi_write(tcm_hcd, + recovery_hcd->f35_addr.control_base, + recovery_hcd->chunk_buf, + chunk_buf_size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write chunk data\n"); + return retval; + } + + data_ptr += entries_to_write; + entries_written += entries_to_write; + } + + retval = recovery_check_status(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get no error recovery mode status\n"); + return retval; + } + + return 0; +} + +static int recovery_poll_erase_completion(void) +{ + int retval; + unsigned char status; + unsigned char command; + unsigned char data_base; + unsigned int timeout; + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + + timeout = F35_ERASE_ALL_WAIT_MS; + + data_base = recovery_hcd->f35_addr.data_base; + + do { + command = F35_READ_FLASH_STATUS_COMMAND; + + retval = syna_tcm_rmi_write(tcm_hcd, + recovery_hcd->f35_addr.command_base, + &command, + sizeof(command)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write F$35 command\n"); + return retval; + } + + do { + retval = syna_tcm_rmi_read(tcm_hcd, + recovery_hcd->f35_addr.command_base, + &command, + sizeof(command)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read command status\n"); + return retval; + } + + if (command == 0x00) + break; + + if (timeout == 0) + break; + + msleep(F35_ERASE_ALL_POLL_MS); + timeout -= F35_ERASE_ALL_POLL_MS; + } while (true); + + if (command != 0 && timeout == 0) { + retval = -EINVAL; + goto exit; + } + + retval = syna_tcm_rmi_read(tcm_hcd, + data_base + F35_DATA5_OFFSET, + &status, + sizeof(status)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read flash status\n"); + return retval; + } + + if ((status & 0x01) == 0x00) + break; + + if (timeout == 0) { + retval = -EINVAL; + goto exit; + } + + msleep(F35_ERASE_ALL_POLL_MS); + timeout -= F35_ERASE_ALL_POLL_MS; + } while (true); + + retval = 0; + +exit: + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get erase completion\n"); + } + + return retval; +} + +static int recovery_erase_flash(void) +{ + int retval; + unsigned char command; + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + + command = F35_ERASE_ALL_COMMAND; + + retval = syna_tcm_rmi_write(tcm_hcd, + recovery_hcd->f35_addr.control_base + F35_CTRL3_OFFSET, + &command, + sizeof(command)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write F$35 command\n"); + return retval; + } + + if (recovery_hcd->f35_addr.command_base) { + retval = recovery_poll_erase_completion(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to wait for erase completion\n"); + return retval; + } + } else { + msleep(F35_ERASE_ALL_WAIT_MS); + } + + retval = recovery_check_status(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get no error recovery mode status\n"); + return retval; + } + + return 0; +} + +static int recovery_set_up_recovery_mode(void) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + retval = tcm_hcd->identify(tcm_hcd, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do identification\n"); + return retval; + } + + if (tcm_hcd->id_info.mode == MODE_APPLICATION) { + retval = tcm_hcd->switch_mode(tcm_hcd, FW_MODE_BOOTLOADER); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enter bootloader mode\n"); + return retval; + } + } + + retval = tcm_hcd->write_message(tcm_hcd, + recovery_hcd->out_buf[0], + &recovery_hcd->out_buf[1], + 2, + NULL, + NULL, + NULL, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_REBOOT_TO_ROM_BOOTLOADER)); + return retval; + } + + msleep(bdata->reset_delay_ms); + + return 0; +} + +static int recovery_do_recovery(void) +{ + int retval; + struct rmi_pdt_entry p_entry; + struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd; + + retval = recovery_parse_ihex(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to parse ihex data\n"); + return retval; + } + + if (recovery_hcd->set_up_recovery_mode) { + retval = recovery_set_up_recovery_mode(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set up recovery mode\n"); + return retval; + } + } + + tcm_hcd->update_watchdog(tcm_hcd, false); + + retval = syna_tcm_rmi_read(tcm_hcd, + PDT_START_ADDR, + (unsigned char *)&p_entry, + sizeof(p_entry)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read PDT entry\n"); + return retval; + } + + if (p_entry.fn_number != UBL_FN_NUMBER) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to find F$35\n"); + return -ENODEV; + } + + recovery_hcd->f35_addr.query_base = p_entry.query_base_addr; + recovery_hcd->f35_addr.command_base = p_entry.command_base_addr; + recovery_hcd->f35_addr.control_base = p_entry.control_base_addr; + recovery_hcd->f35_addr.data_base = p_entry.data_base_addr; + + LOGN(tcm_hcd->pdev->dev.parent, + "Start of recovery\n"); + + retval = recovery_erase_flash(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to erase flash\n"); + return retval; + } + + LOGN(tcm_hcd->pdev->dev.parent, + "Flash erased\n"); + + retval = recovery_write_flash(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write to flash\n"); + return retval; + } + + LOGN(tcm_hcd->pdev->dev.parent, + "Flash written\n"); + + retval = recovery_device_reset(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + return retval; + } + + LOGN(tcm_hcd->pdev->dev.parent, + "End of recovery\n"); + + if (recovery_hcd->set_up_recovery_mode) + return 0; + + tcm_hcd->update_watchdog(tcm_hcd, true); + + retval = tcm_hcd->enable_irq(tcm_hcd, true, NULL); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enable interrupt\n"); + return retval; + } + + retval = tcm_hcd->identify(tcm_hcd, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do identification\n"); + return retval; + } + + if (tcm_hcd->id_info.mode != MODE_APPLICATION) { + retval = tcm_hcd->switch_mode(tcm_hcd, FW_MODE_APPLICATION); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run application firmware\n"); + return retval; + } + } + + return 0; +} + +static int recovery_init(struct syna_tcm_hcd *tcm_hcd) +{ + int retval = 0; + int idx; + + recovery_hcd = kzalloc(sizeof(*recovery_hcd), GFP_KERNEL); + if (!recovery_hcd) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for recovery_hcd\n"); + return -ENOMEM; + } + + recovery_hcd->ihex_buf = kzalloc(IHEX_BUF_SIZE, GFP_KERNEL); + if (!recovery_hcd->ihex_buf) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for ihex_buf\n"); + goto err_allocate_ihex_buf; + } + + recovery_hcd->data_buf = kzalloc(DATA_BUF_SIZE, GFP_KERNEL); + if (!recovery_hcd->data_buf) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for data_buf\n"); + goto err_allocate_data_buf; + } + + recovery_hcd->tcm_hcd = tcm_hcd; + + recovery_hcd->set_up_recovery_mode = SET_UP_RECOVERY_MODE; + + recovery_hcd->out_buf[0] = CMD_REBOOT_TO_ROM_BOOTLOADER; + recovery_hcd->out_buf[1] = 0; + recovery_hcd->out_buf[2] = 0; + + if (!ENABLE_SYSFS_INTERFACE) + return 0; + + recovery_hcd->sysfs_dir = kobject_create_and_add(SYSFS_DIR_NAME, + tcm_hcd->sysfs_dir); + if (!recovery_hcd->sysfs_dir) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs directory\n"); + retval = -EINVAL; + goto err_sysfs_create_dir; + } + + for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) { + retval = sysfs_create_file(recovery_hcd->sysfs_dir, + &(*attrs[idx]).attr); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs file\n"); + goto err_sysfs_create_file; + } + } + + retval = sysfs_create_bin_file(recovery_hcd->sysfs_dir, &bin_attr); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs bin file\n"); + goto err_sysfs_create_bin_file; + } + + return 0; + +err_sysfs_create_bin_file: +err_sysfs_create_file: + for (idx--; idx >= 0; idx--) + sysfs_remove_file(recovery_hcd->sysfs_dir, &(*attrs[idx]).attr); + + kobject_put(recovery_hcd->sysfs_dir); + +err_sysfs_create_dir: + kfree(recovery_hcd->data_buf); +err_allocate_data_buf: + kfree(recovery_hcd->ihex_buf); +err_allocate_ihex_buf: + kfree(recovery_hcd); + recovery_hcd = NULL; + + return retval; +} + +static int recovery_remove(struct syna_tcm_hcd *tcm_hcd) +{ + int idx; + + if (!recovery_hcd) + goto exit; + + if (ENABLE_SYSFS_INTERFACE) { + sysfs_remove_bin_file(recovery_hcd->sysfs_dir, &bin_attr); + + for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) { + sysfs_remove_file(recovery_hcd->sysfs_dir, + &(*attrs[idx]).attr); + } + + kobject_put(recovery_hcd->sysfs_dir); + } + + kfree(recovery_hcd->data_buf); + kfree(recovery_hcd->ihex_buf); + kfree(recovery_hcd); + recovery_hcd = NULL; + +exit: + complete(&recovery_remove_complete); + + return 0; +} + +static int recovery_reset(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + + if (!recovery_hcd) { + retval = recovery_init(tcm_hcd); + return retval; + } + + return 0; +} + +static struct syna_tcm_module_cb recovery_module = { + .type = TCM_RECOVERY, + .init = recovery_init, + .remove = recovery_remove, + .syncbox = NULL, + .asyncbox = NULL, + .reset = recovery_reset, + .suspend = NULL, + .resume = NULL, + .early_suspend = NULL, +}; + +static int __init recovery_module_init(void) +{ + return syna_tcm_add_module(&recovery_module, true); +} + +static void __exit recovery_module_exit(void) +{ + syna_tcm_add_module(&recovery_module, false); + + wait_for_completion(&recovery_remove_complete); +} + +module_init(recovery_module_init); +module_exit(recovery_module_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics TCM Recovery Module"); +MODULE_LICENSE("GPL v2"); diff --git a/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm_reflash.c b/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm_reflash.c new file mode 100644 index 0000000000..bfb6e6db17 --- /dev/null +++ b/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm_reflash.c @@ -0,0 +1,2193 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2019 Scott Lin + * + * 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_SYSFS_INTERFACE true + +#define SYSFS_DIR_NAME "reflash" + +#define CUSTOM_DIR_NAME "custom" + +#define FW_IMAGE_NAME "synaptics_firmware.img" + +#define BOOT_CONFIG_ID "BOOT_CONFIG" + +#define APP_CODE_ID "APP_CODE" + +#define PROD_TEST_ID "APP_PROD_TEST" + +#define APP_CONFIG_ID "APP_CONFIG" + +#define DISP_CONFIG_ID "DISPLAY" + +#define FB_READY_COUNT 2 + +#define FB_READY_WAIT_MS 100 + +#define FB_READY_TIMEOUT_S 80 + +#define IMAGE_FILE_MAGIC_VALUE 0x4818472b + +#define FLASH_AREA_MAGIC_VALUE 0x7c05e516 + +#define BOOT_CONFIG_SIZE 8 + +#define BOOT_CONFIG_SLOTS 16 + +#define IMAGE_BUF_SIZE (512 * 1024) + +#define ERASE_FLASH_DELAY_MS 500 + +#define WRITE_FLASH_DELAY_MS 20 + +#define REFLASH (1 << 0) + +#define FORCE_UPDATE (1 << 1) + +#define APP_CFG_UPDATE (1 << 2) + +#define DISP_CFG_UPDATE (1 << 3) + +#define BOOT_CFG_UPDATE (1 << 4) + +#define BOOT_CFG_LOCKDOWN (1 << 5) + +#define reflash_write(p_name) \ +static int reflash_write_##p_name(void) \ +{ \ + int retval; \ + unsigned int size; \ + unsigned int flash_addr; \ + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; \ + const unsigned char *data; \ +\ + data = reflash_hcd->image_info.p_name.data; \ + size = reflash_hcd->image_info.p_name.size; \ + flash_addr = reflash_hcd->image_info.p_name.flash_addr; \ +\ + retval = reflash_write_flash(flash_addr, data, size); \ + if (retval < 0) { \ + LOGE(tcm_hcd->pdev->dev.parent, \ + "Failed to write to flash\n"); \ + return retval; \ + } \ +\ + return 0; \ +} + +#define reflash_erase(p_name) \ +static int reflash_erase_##p_name(void) \ +{ \ + int retval; \ + unsigned int size; \ + unsigned int flash_addr; \ + unsigned int page_start; \ + unsigned int page_count; \ + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; \ +\ + flash_addr = reflash_hcd->image_info.p_name.flash_addr; \ +\ + page_start = flash_addr / reflash_hcd->page_size; \ +\ + size = reflash_hcd->image_info.p_name.size; \ + page_count = ceil_div(size, reflash_hcd->page_size); \ +\ + LOGD(tcm_hcd->pdev->dev.parent, \ + "Page start = %d\n", \ + page_start); \ +\ + LOGD(tcm_hcd->pdev->dev.parent, \ + "Page count = %d\n", \ + page_count); \ +\ + retval = reflash_erase_flash(page_start, page_count); \ + if (retval < 0) { \ + LOGE(tcm_hcd->pdev->dev.parent, \ + "Failed to erase flash pages\n"); \ + return retval; \ + } \ +\ + return 0; \ +} + +#define reflash_update(p_name) \ +static int reflash_update_##p_name(bool reset) \ +{ \ + int retval; \ + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; \ +\ + retval = reflash_set_up_flash_access(); \ + if (retval < 0) { \ + LOGE(tcm_hcd->pdev->dev.parent, \ + "Failed to set up flash access\n"); \ + return retval; \ + } \ +\ + tcm_hcd->update_watchdog(tcm_hcd, false); \ +\ + retval = reflash_check_##p_name(); \ + if (retval < 0) { \ + LOGE(tcm_hcd->pdev->dev.parent, \ + "Failed "#p_name" partition check\n"); \ + reset = true; \ + goto reset; \ + } \ +\ + retval = reflash_erase_##p_name(); \ + if (retval < 0) { \ + LOGE(tcm_hcd->pdev->dev.parent, \ + "Failed to erase "#p_name" partition\n"); \ + reset = true; \ + goto reset; \ + } \ +\ + LOGN(tcm_hcd->pdev->dev.parent, \ + "Partition erased ("#p_name")\n"); \ +\ + retval = reflash_write_##p_name(); \ + if (retval < 0) { \ + LOGE(tcm_hcd->pdev->dev.parent, \ + "Failed to write "#p_name" partition\n"); \ + reset = true; \ + goto reset; \ + } \ +\ + LOGN(tcm_hcd->pdev->dev.parent, \ + "Partition written ("#p_name")\n"); \ +\ + retval = 0; \ +\ +reset: \ + if (!reset) \ + goto exit; \ +\ + if (tcm_hcd->reset(tcm_hcd, false, true) < 0) { \ + LOGE(tcm_hcd->pdev->dev.parent, \ + "Failed to do reset\n"); \ + } \ +\ +exit: \ + tcm_hcd->update_watchdog(tcm_hcd, true); \ +\ + return retval; \ +} + +#define reflash_show_data() \ +{ \ + LOCK_BUFFER(reflash_hcd->read); \ +\ + readlen = MIN(count, reflash_hcd->read.data_length - pos); \ +\ + retval = secure_memcpy(buf, \ + count, \ + &reflash_hcd->read.buf[pos], \ + reflash_hcd->read.buf_size - pos, \ + readlen); \ + if (retval < 0) { \ + LOGE(tcm_hcd->pdev->dev.parent, \ + "Failed to copy read data\n"); \ + } else { \ + retval = readlen; \ + } \ +\ + UNLOCK_BUFFER(reflash_hcd->read); \ +} + +enum update_area { + NONE = 0, + FIRMWARE_CONFIG, + CONFIG_ONLY, +}; + +struct app_config_header { + unsigned short magic_value[4]; + unsigned char checksum[4]; + unsigned char length[2]; + unsigned char build_id[4]; + unsigned char customer_config_id[16]; +}; + +struct area_descriptor { + unsigned char magic_value[4]; + unsigned char id_string[16]; + unsigned char flags[4]; + unsigned char flash_addr_words[4]; + unsigned char length[4]; + unsigned char checksum[4]; +}; + +struct block_data { + const unsigned char *data; + unsigned int size; + unsigned int flash_addr; +}; + +struct image_info { + struct block_data boot_config; + struct block_data app_firmware; + struct block_data prod_test_firmware; + struct block_data app_config; + struct block_data disp_config; +}; + +struct image_header { + unsigned char magic_value[4]; + unsigned char num_of_areas[4]; +}; + +struct boot_config { + union { + unsigned char i2c_address; + struct { + unsigned char cpha:1; + unsigned char cpol:1; + unsigned char word0_b2__7:6; + } __packed; + }; + unsigned char attn_polarity:1; + unsigned char attn_drive:2; + unsigned char attn_pullup:1; + unsigned char word0_b12__14:3; + unsigned char used:1; + unsigned short customer_part_id; + unsigned short boot_timeout; + unsigned short continue_on_reset:1; + unsigned short word3_b1__15:15; +} __packed; + +struct reflash_hcd { + bool force_update; + bool disp_cfg_update; + const unsigned char *image; + unsigned char *image_buf; + unsigned int image_size; + unsigned int page_size; + unsigned int write_block_size; + unsigned int max_write_payload_size; + const struct firmware *fw_entry; + struct mutex reflash_mutex; + struct kobject *sysfs_dir; + struct kobject *custom_dir; + struct work_struct work; + struct workqueue_struct *workqueue; + struct image_info image_info; + struct syna_tcm_buffer out; + struct syna_tcm_buffer resp; + struct syna_tcm_buffer read; + struct syna_tcm_hcd *tcm_hcd; +}; + +DECLARE_COMPLETION(reflash_remove_complete); + +static struct reflash_hcd *reflash_hcd; + +static int reflash_get_fw_image(void); + +static int reflash_read_data(enum flash_area area, bool run_app_firmware, + struct syna_tcm_buffer *output); + +static int reflash_update_custom_otp(const unsigned char *data, + unsigned int offset, unsigned int datalen); + +static int reflash_update_custom_lcm(const unsigned char *data, + unsigned int offset, unsigned int datalen); + +static int reflash_update_custom_oem(const unsigned char *data, + unsigned int offset, unsigned int datalen); + +static int reflash_update_boot_config(bool lock); + +static int reflash_update_app_config(bool reset); + +static int reflash_update_disp_config(bool reset); + +static int reflash_do_reflash(void); + +STORE_PROTOTYPE(reflash, reflash); + +static struct device_attribute *attrs[] = { + ATTRIFY(reflash), +}; + +static ssize_t reflash_sysfs_image_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t reflash_sysfs_lockdown_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t reflash_sysfs_lockdown_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t reflash_sysfs_lcm_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t reflash_sysfs_lcm_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t reflash_sysfs_oem_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t reflash_sysfs_oem_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static struct bin_attribute bin_attrs[] = { + { + .attr = { + .name = "image", + .mode = 0220, + }, + .size = 0, + .write = reflash_sysfs_image_store, + }, + { + .attr = { + .name = "lockdown", + .mode = 0664, + }, + .size = 0, + .read = reflash_sysfs_lockdown_show, + .write = reflash_sysfs_lockdown_store, + }, + { + .attr = { + .name = "lcm", + .mode = 0664, + }, + .size = 0, + .read = reflash_sysfs_lcm_show, + .write = reflash_sysfs_lcm_store, + }, + { + .attr = { + .name = "oem", + .mode = 0664, + }, + .size = 0, + .read = reflash_sysfs_oem_show, + .write = reflash_sysfs_oem_store, + }, +}; + +static ssize_t reflash_sysfs_reflash_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned int input; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + if (kstrtouint(buf, 10, &input)) + return -EINVAL; + + mutex_lock(&tcm_hcd->extif_mutex); + + pm_stay_awake(&tcm_hcd->pdev->dev); + + mutex_lock(&reflash_hcd->reflash_mutex); + + if (reflash_hcd->image_size != 0) + reflash_hcd->image = reflash_hcd->image_buf; + + reflash_hcd->force_update = input & FORCE_UPDATE ? true : false; + + if (input & REFLASH || input & FORCE_UPDATE) { + retval = reflash_do_reflash(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reflash\n"); + goto exit; + } + } + + if ((input & ~(REFLASH | FORCE_UPDATE)) == 0) { + retval = count; + goto exit; + } + + retval = reflash_get_fw_image(); + if (retval < 0) { + LOGD(tcm_hcd->pdev->dev.parent, + "Failed to get firmware image\n"); + goto exit; + } + + if (input & BOOT_CFG_LOCKDOWN) { + retval = reflash_update_boot_config(true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to lockdown boot config\n"); + goto exit; + } + } else if (input & BOOT_CFG_UPDATE) { + retval = reflash_update_boot_config(false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to update boot config\n"); + goto exit; + } + } + + if (input & REFLASH || input & FORCE_UPDATE) { + retval = count; + goto exit; + } + + if (input & DISP_CFG_UPDATE) { + if (input & APP_CFG_UPDATE) + retval = reflash_update_disp_config(false); + else + retval = reflash_update_disp_config(true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to reflash display config\n"); + goto exit; + } + } + + if (input & APP_CFG_UPDATE) { + retval = reflash_update_app_config(true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to reflash application config\n"); + goto exit; + } + } + + retval = count; + +exit: + if (reflash_hcd->fw_entry) { + release_firmware(reflash_hcd->fw_entry); + reflash_hcd->fw_entry = NULL; + } + + reflash_hcd->image = NULL; + reflash_hcd->image_size = 0; + reflash_hcd->force_update = FORCE_REFLASH; + + mutex_unlock(&reflash_hcd->reflash_mutex); + + pm_relax(&tcm_hcd->pdev->dev); + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t reflash_sysfs_image_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + retval = secure_memcpy(&reflash_hcd->image_buf[pos], + IMAGE_BUF_SIZE - pos, + buf, + count, + count); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy firmware image data\n"); + reflash_hcd->image_size = 0; + goto exit; + } + + reflash_hcd->image_size = pos + count; + + retval = count; + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t reflash_sysfs_lockdown_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + unsigned int readlen; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + mutex_lock(&reflash_hcd->reflash_mutex); + + retval = reflash_read_data(CUSTOM_OTP, true, NULL); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read lockdown data\n"); + goto exit; + } + + reflash_show_data(); + +exit: + mutex_unlock(&reflash_hcd->reflash_mutex); + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t reflash_sysfs_lockdown_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + pm_stay_awake(&tcm_hcd->pdev->dev); + + mutex_lock(&reflash_hcd->reflash_mutex); + + retval = reflash_update_custom_otp(buf, pos, count); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to update custom OTP data\n"); + goto exit; + } + + retval = count; + +exit: + mutex_unlock(&reflash_hcd->reflash_mutex); + + pm_relax(&tcm_hcd->pdev->dev); + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t reflash_sysfs_lcm_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + unsigned int readlen; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + mutex_lock(&reflash_hcd->reflash_mutex); + + retval = reflash_read_data(CUSTOM_LCM, true, NULL); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read LCM data\n"); + goto exit; + } + + reflash_show_data(); + +exit: + mutex_unlock(&reflash_hcd->reflash_mutex); + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t reflash_sysfs_lcm_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + pm_stay_awake(&tcm_hcd->pdev->dev); + + mutex_lock(&reflash_hcd->reflash_mutex); + + retval = reflash_update_custom_lcm(buf, pos, count); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to update custom LCM data\n"); + goto exit; + } + + retval = count; + +exit: + mutex_unlock(&reflash_hcd->reflash_mutex); + + pm_relax(&tcm_hcd->pdev->dev); + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t reflash_sysfs_oem_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + unsigned int readlen; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + mutex_lock(&reflash_hcd->reflash_mutex); + + retval = reflash_read_data(CUSTOM_OEM, true, NULL); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read OEM data\n"); + goto exit; + } + + reflash_show_data(); + +exit: + mutex_unlock(&reflash_hcd->reflash_mutex); + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t reflash_sysfs_oem_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + pm_stay_awake(&tcm_hcd->pdev->dev); + + mutex_lock(&reflash_hcd->reflash_mutex); + + retval = reflash_update_custom_oem(buf, pos, count); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to update custom OEM data\n"); + goto exit; + } + + retval = count; + +exit: + mutex_unlock(&reflash_hcd->reflash_mutex); + + pm_relax(&tcm_hcd->pdev->dev); + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static int reflash_set_up_flash_access(void) +{ + int retval; + unsigned int temp; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + retval = tcm_hcd->identify(tcm_hcd, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do identification\n"); + return retval; + } + + if (tcm_hcd->id_info.mode == MODE_APPLICATION) { + retval = tcm_hcd->switch_mode(tcm_hcd, FW_MODE_BOOTLOADER); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enter bootloader mode\n"); + return retval; + } + } + + temp = tcm_hcd->boot_info.write_block_size_words; + reflash_hcd->write_block_size = temp * 2; + + temp = le2_to_uint(tcm_hcd->boot_info.erase_page_size_words); + reflash_hcd->page_size = temp * 2; + + temp = le2_to_uint(tcm_hcd->boot_info.max_write_payload_size); + reflash_hcd->max_write_payload_size = temp; + + LOGD(tcm_hcd->pdev->dev.parent, + "Write block size = %d\n", + reflash_hcd->write_block_size); + + LOGD(tcm_hcd->pdev->dev.parent, + "Page size = %d\n", + reflash_hcd->page_size); + + LOGD(tcm_hcd->pdev->dev.parent, + "Max write payload size = %d\n", + reflash_hcd->max_write_payload_size); + + if (reflash_hcd->write_block_size > (tcm_hcd->wr_chunk_size - 5)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Write size greater than available chunk space\n"); + return -EINVAL; + } + + return 0; +} + +static int reflash_parse_fw_image(void) +{ + unsigned int idx; + unsigned int addr; + unsigned int offset; + unsigned int length; + unsigned int checksum; + unsigned int flash_addr; + unsigned int magic_value; + unsigned int num_of_areas; + struct image_header *header; + struct image_info *image_info; + struct area_descriptor *descriptor; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + const unsigned char *image; + const unsigned char *content; + + image = reflash_hcd->image; + image_info = &reflash_hcd->image_info; + header = (struct image_header *)image; + + reflash_hcd->disp_cfg_update = false; + + magic_value = le4_to_uint(header->magic_value); + if (magic_value != IMAGE_FILE_MAGIC_VALUE) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid image file magic value\n"); + return -EINVAL; + } + + memset(image_info, 0x00, sizeof(*image_info)); + + offset = sizeof(*header); + num_of_areas = le4_to_uint(header->num_of_areas); + + for (idx = 0; idx < num_of_areas; idx++) { + addr = le4_to_uint(image + offset); + descriptor = (struct area_descriptor *)(image + addr); + offset += 4; + + magic_value = le4_to_uint(descriptor->magic_value); + if (magic_value != FLASH_AREA_MAGIC_VALUE) + continue; + + length = le4_to_uint(descriptor->length); + content = (unsigned char *)descriptor + sizeof(*descriptor); + flash_addr = le4_to_uint(descriptor->flash_addr_words) * 2; + checksum = le4_to_uint(descriptor->checksum); + + if (!memcmp((char *)descriptor->id_string, + BOOT_CONFIG_ID, + strlen(BOOT_CONFIG_ID))) { + if (checksum != (crc32(~0, content, length) ^ ~0)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Boot config checksum error\n"); + return -EINVAL; + } + image_info->boot_config.size = length; + image_info->boot_config.data = content; + image_info->boot_config.flash_addr = flash_addr; + LOGD(tcm_hcd->pdev->dev.parent, + "Boot config size = %d\n", + length); + LOGD(tcm_hcd->pdev->dev.parent, + "Boot config flash address = 0x%08x\n", + flash_addr); + } else if (!memcmp((char *)descriptor->id_string, + APP_CODE_ID, + strlen(APP_CODE_ID))) { + if (checksum != (crc32(~0, content, length) ^ ~0)) { + LOGE(tcm_hcd->pdev->dev.parent, + "APP firmware checksum error\n"); + return -EINVAL; + } + image_info->app_firmware.size = length; + image_info->app_firmware.data = content; + image_info->app_firmware.flash_addr = flash_addr; + LOGD(tcm_hcd->pdev->dev.parent, + "Application firmware size = %d\n", + length); + LOGD(tcm_hcd->pdev->dev.parent, + "Application firmware flash address = 0x%08x\n", + flash_addr); + } else if (!memcmp((char *)descriptor->id_string, + PROD_TEST_ID, + strlen(PROD_TEST_ID))) { + if (checksum != (crc32(~0, content, length) ^ ~0)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Production test checksum error\n"); + return -EINVAL; + } + image_info->prod_test_firmware.size = length; + image_info->prod_test_firmware.data = content; + image_info->prod_test_firmware.flash_addr = flash_addr; + LOGD(tcm_hcd->pdev->dev.parent, + "Production test firmware size = %d\n", + length); + LOGD(tcm_hcd->pdev->dev.parent, + "Production test flash address = 0x%08x\n", + flash_addr); + } else if (!memcmp((char *)descriptor->id_string, + APP_CONFIG_ID, + strlen(APP_CONFIG_ID))) { + if (checksum != (crc32(~0, content, length) ^ ~0)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Application config checksum error\n"); + return -EINVAL; + } + image_info->app_config.size = length; + image_info->app_config.data = content; + image_info->app_config.flash_addr = flash_addr; + LOGD(tcm_hcd->pdev->dev.parent, + "Application config size = %d\n", + length); + LOGD(tcm_hcd->pdev->dev.parent, + "Application config flash address = 0x%08x\n", + flash_addr); + } else if (!memcmp((char *)descriptor->id_string, + DISP_CONFIG_ID, + strlen(DISP_CONFIG_ID))) { + if (checksum != (crc32(~0, content, length) ^ ~0)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Display config checksum error\n"); + return -EINVAL; + } + reflash_hcd->disp_cfg_update = true; + image_info->disp_config.size = length; + image_info->disp_config.data = content; + image_info->disp_config.flash_addr = flash_addr; + LOGD(tcm_hcd->pdev->dev.parent, + "Display config size = %d\n", + length); + LOGD(tcm_hcd->pdev->dev.parent, + "Display config flash address = 0x%08x\n", + flash_addr); + } + } + + return 0; +} + +static int reflash_get_fw_image(void) +{ + int retval; + const char *fw_name; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + if (bdata->fw_name) + fw_name = bdata->fw_name; + else + fw_name = FW_IMAGE_NAME; + + if (reflash_hcd->image == NULL) { + retval = request_firmware(&reflash_hcd->fw_entry, fw_name, + tcm_hcd->pdev->dev.parent); + if (retval < 0) { + LOGD(tcm_hcd->pdev->dev.parent, + "Failed to request %s\n", + fw_name); + return retval; + } + + LOGD(tcm_hcd->pdev->dev.parent, + "Firmware image size = %d\n", + (unsigned int)reflash_hcd->fw_entry->size); + + reflash_hcd->image = reflash_hcd->fw_entry->data; + reflash_hcd->image_size = reflash_hcd->fw_entry->size; + } + + retval = reflash_parse_fw_image(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to parse firmware image\n"); + return retval; + } + + return 0; +} + +static enum update_area reflash_compare_id_info(void) +{ + enum update_area update_area; + unsigned int idx; + unsigned int image_fw_id; + unsigned int device_fw_id; + unsigned char *image_config_id; + unsigned char *device_config_id; + struct app_config_header *header; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + const unsigned char *app_config_data; + + update_area = NONE; + + if (reflash_hcd->image_info.app_config.size < sizeof(*header)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid application config in image file\n"); + goto exit; + } + + app_config_data = reflash_hcd->image_info.app_config.data; + header = (struct app_config_header *)app_config_data; + + if (reflash_hcd->force_update) { + update_area = FIRMWARE_CONFIG; + goto exit; + } + + if (tcm_hcd->id_info.mode != MODE_APPLICATION) { + update_area = FIRMWARE_CONFIG; + goto exit; + } + + image_fw_id = le4_to_uint(header->build_id); + device_fw_id = tcm_hcd->packrat_number; + + if (image_fw_id > device_fw_id) { + LOGN(tcm_hcd->pdev->dev.parent, + "Image firmware ID newer than device firmware ID\n"); + update_area = FIRMWARE_CONFIG; + goto exit; + } else if (image_fw_id < device_fw_id) { + LOGN(tcm_hcd->pdev->dev.parent, + "Image firmware ID older than device firmware ID\n"); + update_area = NONE; + goto exit; + } + + image_config_id = header->customer_config_id; + device_config_id = tcm_hcd->app_info.customer_config_id; + + for (idx = 0; idx < 16; idx++) { + if (image_config_id[idx] > device_config_id[idx]) { + LOGN(tcm_hcd->pdev->dev.parent, + "Image config ID newer than device's ID\n"); + update_area = CONFIG_ONLY; + goto exit; + } else if (image_config_id[idx] < device_config_id[idx]) { + LOGN(tcm_hcd->pdev->dev.parent, + "Image config ID older than device's ID\n"); + update_area = NONE; + goto exit; + } + } + + update_area = NONE; + +exit: + if (update_area == NONE) + LOGD(tcm_hcd->pdev->dev.parent, "No need to do reflash\n"); + else + LOGD(tcm_hcd->pdev->dev.parent, + "Updating %s\n", + update_area == FIRMWARE_CONFIG ? + "firmware and config" : + "config only"); + + return update_area; +} + +static int reflash_read_flash(unsigned int address, unsigned char *data, + unsigned int datalen) +{ + int retval; + unsigned int length_words; + unsigned int flash_addr_words; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + LOCK_BUFFER(reflash_hcd->out); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &reflash_hcd->out, + 6); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for reflash_hcd->out.buf\n"); + UNLOCK_BUFFER(reflash_hcd->out); + return retval; + } + + length_words = datalen / 2; + flash_addr_words = address / 2; + + reflash_hcd->out.buf[0] = (unsigned char)flash_addr_words; + reflash_hcd->out.buf[1] = (unsigned char)(flash_addr_words >> 8); + reflash_hcd->out.buf[2] = (unsigned char)(flash_addr_words >> 16); + reflash_hcd->out.buf[3] = (unsigned char)(flash_addr_words >> 24); + reflash_hcd->out.buf[4] = (unsigned char)length_words; + reflash_hcd->out.buf[5] = (unsigned char)(length_words >> 8); + + LOCK_BUFFER(reflash_hcd->resp); + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_READ_FLASH, + reflash_hcd->out.buf, + 6, + &reflash_hcd->resp.buf, + &reflash_hcd->resp.buf_size, + &reflash_hcd->resp.data_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_READ_FLASH)); + UNLOCK_BUFFER(reflash_hcd->resp); + UNLOCK_BUFFER(reflash_hcd->out); + return retval; + } + + UNLOCK_BUFFER(reflash_hcd->out); + + if (reflash_hcd->resp.data_length != datalen) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read requested length\n"); + UNLOCK_BUFFER(reflash_hcd->resp); + return -EIO; + } + + retval = secure_memcpy(data, + datalen, + reflash_hcd->resp.buf, + reflash_hcd->resp.buf_size, + datalen); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy read data\n"); + UNLOCK_BUFFER(reflash_hcd->resp); + return retval; + } + + UNLOCK_BUFFER(reflash_hcd->resp); + + return 0; +} + +static int reflash_read_data(enum flash_area area, bool run_app_firmware, + struct syna_tcm_buffer *output) +{ + int retval; + unsigned int temp; + unsigned int addr; + unsigned int length; + struct syna_tcm_app_info *app_info; + struct syna_tcm_boot_info *boot_info; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + switch (area) { + case CUSTOM_LCM: + case CUSTOM_OEM: + case PPDT: + retval = tcm_hcd->get_data_location(tcm_hcd, + area, + &addr, + &length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get data location\n"); + return retval; + } + break; + default: + break; + } + + retval = reflash_set_up_flash_access(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set up flash access\n"); + return retval; + } + + app_info = &tcm_hcd->app_info; + boot_info = &tcm_hcd->boot_info; + + switch (area) { + case BOOT_CONFIG: + temp = le2_to_uint(boot_info->boot_config_start_block); + addr = temp * reflash_hcd->write_block_size; + length = BOOT_CONFIG_SIZE * BOOT_CONFIG_SLOTS; + break; + case APP_CONFIG: + temp = le2_to_uint(app_info->app_config_start_write_block); + addr = temp * reflash_hcd->write_block_size; + length = le2_to_uint(app_info->app_config_size); + break; + case DISP_CONFIG: + temp = le4_to_uint(boot_info->display_config_start_block); + addr = temp * reflash_hcd->write_block_size; + temp = le2_to_uint(boot_info->display_config_length_blocks); + length = temp * reflash_hcd->write_block_size; + break; + case CUSTOM_OTP: + temp = le2_to_uint(boot_info->custom_otp_start_block); + addr = temp * reflash_hcd->write_block_size; + temp = le2_to_uint(boot_info->custom_otp_length_blocks); + length = temp * reflash_hcd->write_block_size; + break; + case CUSTOM_LCM: + case CUSTOM_OEM: + case PPDT: + addr *= reflash_hcd->write_block_size; + length *= reflash_hcd->write_block_size; + break; + default: + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid data area\n"); + retval = -EINVAL; + goto run_app_firmware; + } + + if (addr == 0 || length == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Data area unavailable\n"); + retval = -EINVAL; + goto run_app_firmware; + } + + LOCK_BUFFER(reflash_hcd->read); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &reflash_hcd->read, + length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for read.buf\n"); + UNLOCK_BUFFER(reflash_hcd->read); + goto run_app_firmware; + } + + retval = reflash_read_flash(addr, reflash_hcd->read.buf, length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read from flash\n"); + UNLOCK_BUFFER(reflash_hcd->read); + goto run_app_firmware; + } + + reflash_hcd->read.data_length = length; + + if (output != NULL) { + retval = syna_tcm_alloc_mem(tcm_hcd, + output, + length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for output->buf\n"); + UNLOCK_BUFFER(reflash_hcd->read); + goto run_app_firmware; + } + + retval = secure_memcpy(output->buf, + output->buf_size, + reflash_hcd->read.buf, + reflash_hcd->read.buf_size, + length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy read data\n"); + UNLOCK_BUFFER(reflash_hcd->read); + goto run_app_firmware; + } + + output->data_length = length; + } + + UNLOCK_BUFFER(reflash_hcd->read); + + retval = 0; + +run_app_firmware: + if (!run_app_firmware) + goto exit; + + if (tcm_hcd->switch_mode(tcm_hcd, FW_MODE_APPLICATION) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run application firmware\n"); + } + +exit: + return retval; +} + +static int reflash_check_boot_config(void) +{ + unsigned int temp; + unsigned int image_addr; + unsigned int device_addr; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + if (reflash_hcd->image_info.boot_config.size < BOOT_CONFIG_SIZE) { + LOGE(tcm_hcd->pdev->dev.parent, + "No valid boot config in image file\n"); + return -EINVAL; + } + + image_addr = reflash_hcd->image_info.boot_config.flash_addr; + + temp = le2_to_uint(tcm_hcd->boot_info.boot_config_start_block); + device_addr = temp * reflash_hcd->write_block_size; + + if (image_addr != device_addr) { + LOGE(tcm_hcd->pdev->dev.parent, + "Flash address mismatch\n"); + return -EINVAL; + } + + return 0; +} + +static int reflash_check_app_config(void) +{ + unsigned int temp; + unsigned int image_addr; + unsigned int image_size; + unsigned int device_addr; + unsigned int device_size; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + if (reflash_hcd->image_info.app_config.size == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "No application config in image file\n"); + return -EINVAL; + } + + image_addr = reflash_hcd->image_info.app_config.flash_addr; + image_size = reflash_hcd->image_info.app_config.size; + + temp = le2_to_uint(tcm_hcd->app_info.app_config_start_write_block); + device_addr = temp * reflash_hcd->write_block_size; + device_size = le2_to_uint(tcm_hcd->app_info.app_config_size); + + if (device_addr == 0 && device_size == 0) + return 0; + + if (image_addr != device_addr) { + LOGE(tcm_hcd->pdev->dev.parent, + "Flash address mismatch\n"); + return -EINVAL; + } + + if (image_size != device_size) { + LOGE(tcm_hcd->pdev->dev.parent, + "Config size mismatch\n"); + return -EINVAL; + } + + return 0; +} + +static int reflash_check_disp_config(void) +{ + unsigned int temp; + unsigned int image_addr; + unsigned int image_size; + unsigned int device_addr; + unsigned int device_size; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + if (reflash_hcd->image_info.disp_config.size == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "No display config in image file\n"); + return -EINVAL; + } + + image_addr = reflash_hcd->image_info.disp_config.flash_addr; + image_size = reflash_hcd->image_info.disp_config.size; + + temp = le4_to_uint(tcm_hcd->boot_info.display_config_start_block); + device_addr = temp * reflash_hcd->write_block_size; + + temp = le2_to_uint(tcm_hcd->boot_info.display_config_length_blocks); + device_size = temp * reflash_hcd->write_block_size; + + if (image_addr != device_addr) { + LOGE(tcm_hcd->pdev->dev.parent, + "Flash address mismatch\n"); + return -EINVAL; + } + + if (image_size != device_size) { + LOGE(tcm_hcd->pdev->dev.parent, + "Config size mismatch\n"); + return -EINVAL; + } + + return 0; +} + +static int reflash_check_prod_test_firmware(void) +{ + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + if (reflash_hcd->image_info.prod_test_firmware.size == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "No production test firmware in image file\n"); + return -EINVAL; + } + + return 0; +} + +static int reflash_check_app_firmware(void) +{ + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + if (reflash_hcd->image_info.app_firmware.size == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "No application firmware in image file\n"); + return -EINVAL; + } + + return 0; +} + +static int reflash_write_flash(unsigned int address, const unsigned char *data, + unsigned int datalen) +{ + int retval; + unsigned int offset; + unsigned int w_length; + unsigned int xfer_length; + unsigned int remaining_length; + unsigned int flash_address; + unsigned int block_address; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + w_length = tcm_hcd->wr_chunk_size - 5; + + w_length = w_length - (w_length % reflash_hcd->write_block_size); + + w_length = MIN(w_length, reflash_hcd->max_write_payload_size); + + offset = 0; + + remaining_length = datalen; + + LOCK_BUFFER(reflash_hcd->out); + LOCK_BUFFER(reflash_hcd->resp); + + while (remaining_length) { + if (remaining_length > w_length) + xfer_length = w_length; + else + xfer_length = remaining_length; + + retval = syna_tcm_alloc_mem(tcm_hcd, + &reflash_hcd->out, + xfer_length + 2); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for out.buf\n"); + UNLOCK_BUFFER(reflash_hcd->resp); + UNLOCK_BUFFER(reflash_hcd->out); + return retval; + } + + flash_address = address + offset; + block_address = flash_address / reflash_hcd->write_block_size; + reflash_hcd->out.buf[0] = (unsigned char)block_address; + reflash_hcd->out.buf[1] = (unsigned char)(block_address >> 8); + + retval = secure_memcpy(&reflash_hcd->out.buf[2], + reflash_hcd->out.buf_size - 2, + &data[offset], + datalen - offset, + xfer_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy write data\n"); + UNLOCK_BUFFER(reflash_hcd->resp); + UNLOCK_BUFFER(reflash_hcd->out); + return retval; + } + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_WRITE_FLASH, + reflash_hcd->out.buf, + xfer_length + 2, + &reflash_hcd->resp.buf, + &reflash_hcd->resp.buf_size, + &reflash_hcd->resp.data_length, + NULL, + WRITE_FLASH_DELAY_MS); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_WRITE_FLASH)); + LOGE(tcm_hcd->pdev->dev.parent, + "Flash address = 0x%08x\n", + flash_address); + LOGE(tcm_hcd->pdev->dev.parent, + "Data length = %d\n", + xfer_length); + UNLOCK_BUFFER(reflash_hcd->resp); + UNLOCK_BUFFER(reflash_hcd->out); + return retval; + } + + offset += xfer_length; + remaining_length -= xfer_length; + } + + UNLOCK_BUFFER(reflash_hcd->resp); + UNLOCK_BUFFER(reflash_hcd->out); + + return 0; +} + +reflash_write(app_config) + +reflash_write(disp_config) + +reflash_write(prod_test_firmware) + +reflash_write(app_firmware) + +static int reflash_erase_flash(unsigned int page_start, unsigned int page_count) +{ + int retval; + unsigned char out_buf[2]; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + out_buf[0] = (unsigned char)page_start; + out_buf[1] = (unsigned char)page_count; + + LOCK_BUFFER(reflash_hcd->resp); + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_ERASE_FLASH, + out_buf, + sizeof(out_buf), + &reflash_hcd->resp.buf, + &reflash_hcd->resp.buf_size, + &reflash_hcd->resp.data_length, + NULL, + ERASE_FLASH_DELAY_MS); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_ERASE_FLASH)); + UNLOCK_BUFFER(reflash_hcd->resp); + return retval; + } + + UNLOCK_BUFFER(reflash_hcd->resp); + + return 0; +} + +reflash_erase(app_config) + +reflash_erase(disp_config) + +reflash_erase(prod_test_firmware) + +reflash_erase(app_firmware) + +static int reflash_update_custom_otp(const unsigned char *data, + unsigned int offset, unsigned int datalen) +{ + int retval; + unsigned int temp; + unsigned int addr; + unsigned int length; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + retval = reflash_set_up_flash_access(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set up flash access\n"); + return retval; + } + + tcm_hcd->update_watchdog(tcm_hcd, false); + + temp = le2_to_uint(tcm_hcd->boot_info.custom_otp_start_block); + addr = temp * reflash_hcd->write_block_size; + + temp = le2_to_uint(tcm_hcd->boot_info.custom_otp_length_blocks); + length = temp * reflash_hcd->write_block_size; + + if (addr == 0 || length == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Data area unavailable\n"); + retval = -EINVAL; + goto run_app_firmware; + } + + if (datalen + offset > length) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid data length\n"); + retval = -EINVAL; + goto run_app_firmware; + } + + retval = reflash_write_flash(addr + offset, + data, + datalen); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write to flash\n"); + goto run_app_firmware; + } + + retval = 0; + +run_app_firmware: + if (tcm_hcd->switch_mode(tcm_hcd, FW_MODE_APPLICATION) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run application firmware\n"); + } + + tcm_hcd->update_watchdog(tcm_hcd, true); + + return retval; +} + +static int reflash_update_custom_lcm(const unsigned char *data, + unsigned int offset, unsigned int datalen) +{ + int retval; + unsigned int addr; + unsigned int length; + unsigned int page_start; + unsigned int page_count; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + retval = tcm_hcd->get_data_location(tcm_hcd, + CUSTOM_LCM, + &addr, + &length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get data location\n"); + return retval; + } + + retval = reflash_set_up_flash_access(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set up flash access\n"); + return retval; + } + + tcm_hcd->update_watchdog(tcm_hcd, false); + + addr *= reflash_hcd->write_block_size; + length *= reflash_hcd->write_block_size; + + if (addr == 0 || length == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Data area unavailable\n"); + retval = -EINVAL; + goto run_app_firmware; + } + + if (datalen + offset > length) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid data length\n"); + retval = -EINVAL; + goto run_app_firmware; + } + + if (offset == 0) { + page_start = addr / reflash_hcd->page_size; + + page_count = ceil_div(length, reflash_hcd->page_size); + + retval = reflash_erase_flash(page_start, page_count); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to erase flash pages\n"); + goto run_app_firmware; + } + } + + retval = reflash_write_flash(addr + offset, + data, + datalen); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write to flash\n"); + goto run_app_firmware; + } + + retval = 0; + +run_app_firmware: + if (tcm_hcd->switch_mode(tcm_hcd, FW_MODE_APPLICATION) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run application firmware\n"); + } + + tcm_hcd->update_watchdog(tcm_hcd, true); + + return retval; +} + +static int reflash_update_custom_oem(const unsigned char *data, + unsigned int offset, unsigned int datalen) +{ + int retval; + unsigned int addr; + unsigned int length; + unsigned int page_start; + unsigned int page_count; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + retval = tcm_hcd->get_data_location(tcm_hcd, + CUSTOM_OEM, + &addr, + &length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get data location\n"); + return retval; + } + + retval = reflash_set_up_flash_access(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set up flash access\n"); + return retval; + } + + tcm_hcd->update_watchdog(tcm_hcd, false); + + addr *= reflash_hcd->write_block_size; + length *= reflash_hcd->write_block_size; + + if (addr == 0 || length == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Data area unavailable\n"); + retval = -EINVAL; + goto run_app_firmware; + } + + if (datalen + offset > length) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid data length\n"); + retval = -EINVAL; + goto run_app_firmware; + } + + if (offset == 0) { + page_start = addr / reflash_hcd->page_size; + + page_count = ceil_div(length, reflash_hcd->page_size); + + retval = reflash_erase_flash(page_start, page_count); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to erase flash pages\n"); + goto run_app_firmware; + } + } + + retval = reflash_write_flash(addr + offset, + data, + datalen); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write to flash\n"); + goto run_app_firmware; + } + + retval = 0; + +run_app_firmware: + if (tcm_hcd->switch_mode(tcm_hcd, FW_MODE_APPLICATION) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run application firmware\n"); + } + + tcm_hcd->update_watchdog(tcm_hcd, true); + + return retval; +} + +static int reflash_update_boot_config(bool lock) +{ + int retval; + unsigned char slot_used; + unsigned int idx; + unsigned int addr = 0; + struct boot_config *data; + struct boot_config *last_slot; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + retval = reflash_set_up_flash_access(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set up flash access\n"); + return retval; + } + + tcm_hcd->update_watchdog(tcm_hcd, false); + + retval = reflash_check_boot_config(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed boot_config partition check\n"); + goto reset; + } + + retval = reflash_read_data(BOOT_CONFIG, false, NULL); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read boot config\n"); + goto reset; + } + + LOCK_BUFFER(reflash_hcd->read); + + data = (struct boot_config *)reflash_hcd->read.buf; + last_slot = data + (BOOT_CONFIG_SLOTS - 1); + slot_used = tcm_hcd->id_info.mode == MODE_TDDI_BOOTLOADER ? 0 : 1; + + if (last_slot->used == slot_used) { + LOGE(tcm_hcd->pdev->dev.parent, + "Boot config already locked down\n"); + UNLOCK_BUFFER(reflash_hcd->read); + goto reset; + } + + if (lock) { + idx = BOOT_CONFIG_SLOTS - 1; + } else { + for (idx = 0; idx < BOOT_CONFIG_SLOTS; idx++) { + if (data->used == slot_used) { + data++; + continue; + } else { + break; + } + } + } + + UNLOCK_BUFFER(reflash_hcd->read); + + if (idx == BOOT_CONFIG_SLOTS) { + LOGE(tcm_hcd->pdev->dev.parent, + "No free boot config slot available\n"); + goto reset; + } + + addr += idx * BOOT_CONFIG_SIZE; + + retval = reflash_write_flash(addr, + reflash_hcd->image_info.boot_config.data, + BOOT_CONFIG_SIZE); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write to flash\n"); + goto reset; + } + + LOGN(tcm_hcd->pdev->dev.parent, + "Slot %d updated with new boot config\n", + idx); + + retval = 0; + +reset: + if (tcm_hcd->reset(tcm_hcd, false, true) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + } + + tcm_hcd->update_watchdog(tcm_hcd, true); + + return retval; +} + +reflash_update(app_config) + +reflash_update(disp_config) + +reflash_update(prod_test_firmware) + +reflash_update(app_firmware) + +static int reflash_do_reflash(void) +{ + int retval; + enum update_area update_area; + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + + retval = reflash_get_fw_image(); + if (retval < 0) { + LOGD(tcm_hcd->pdev->dev.parent, + "Failed to get firmware image\n"); + goto exit; + } + + LOGD(tcm_hcd->pdev->dev.parent, + "Start of reflash\n"); + + atomic_set(&tcm_hcd->firmware_flashing, 1); + + update_area = reflash_compare_id_info(); + + switch (update_area) { + case FIRMWARE_CONFIG: + retval = reflash_update_app_firmware(false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to reflash application firmware\n"); + goto exit; + } + memset(&tcm_hcd->app_info, 0x00, sizeof(tcm_hcd->app_info)); + if (tcm_hcd->features.dual_firmware) { + retval = reflash_update_prod_test_firmware(false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to reflash production test\n"); + goto exit; + } + } + case CONFIG_ONLY: + if (reflash_hcd->disp_cfg_update) { + retval = reflash_update_disp_config(false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to reflash display config\n"); + goto exit; + } + } + retval = reflash_update_app_config(true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to reflash application config\n"); + goto exit; + } + break; + case NONE: + default: + break; + } + + LOGD(tcm_hcd->pdev->dev.parent, + "End of reflash\n"); + + retval = 0; + +exit: + if (reflash_hcd->fw_entry) { + release_firmware(reflash_hcd->fw_entry); + reflash_hcd->fw_entry = NULL; + reflash_hcd->image = NULL; + reflash_hcd->image_size = 0; + } + + atomic_set(&tcm_hcd->firmware_flashing, 0); + wake_up_interruptible(&tcm_hcd->reflash_wq); + return retval; +} + +#ifdef STARTUP_REFLASH +static void reflash_startup_work(struct work_struct *work) +{ + int retval; +#if defined(CONFIG_DRM) || defined(CONFIG_FB) + unsigned int timeout; +#endif + struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; + +#if defined(CONFIG_DRM) || defined(CONFIG_FB) + timeout = FB_READY_TIMEOUT_S * 1000 / FB_READY_WAIT_MS; + + while (tcm_hcd->fb_ready != FB_READY_COUNT - 1) { + if (timeout == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Timed out waiting for FB ready\n"); + return; + } + msleep(FB_READY_WAIT_MS); + timeout--; + } +#endif + + pm_stay_awake(&tcm_hcd->pdev->dev); + + mutex_lock(&reflash_hcd->reflash_mutex); + + retval = reflash_do_reflash(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reflash\n"); + } + + mutex_unlock(&reflash_hcd->reflash_mutex); + + pm_relax(&tcm_hcd->pdev->dev); +} +#endif + +static int reflash_init(struct syna_tcm_hcd *tcm_hcd) +{ + int retval = 0; + int idx; + + reflash_hcd = kzalloc(sizeof(*reflash_hcd), GFP_KERNEL); + if (!reflash_hcd) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for reflash_hcd\n"); + return -ENOMEM; + } + + reflash_hcd->image_buf = kzalloc(IMAGE_BUF_SIZE, GFP_KERNEL); + if (!reflash_hcd->image_buf) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for image_buf\n"); + goto err_allocate_memory; + } + + reflash_hcd->tcm_hcd = tcm_hcd; + + reflash_hcd->force_update = FORCE_REFLASH; + + mutex_init(&reflash_hcd->reflash_mutex); + + INIT_BUFFER(reflash_hcd->out, false); + INIT_BUFFER(reflash_hcd->resp, false); + INIT_BUFFER(reflash_hcd->read, false); + +#ifdef STARTUP_REFLASH + reflash_hcd->workqueue = + create_singlethread_workqueue("syna_tcm_reflash"); + INIT_WORK(&reflash_hcd->work, reflash_startup_work); + queue_work(reflash_hcd->workqueue, &reflash_hcd->work); +#endif + + if (!ENABLE_SYSFS_INTERFACE) + return 0; + + reflash_hcd->sysfs_dir = kobject_create_and_add(SYSFS_DIR_NAME, + tcm_hcd->sysfs_dir); + if (!reflash_hcd->sysfs_dir) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs directory\n"); + retval = -EINVAL; + goto err_sysfs_create_dir; + } + + for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) { + retval = sysfs_create_file(reflash_hcd->sysfs_dir, + &(*attrs[idx]).attr); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs file\n"); + goto err_sysfs_create_file; + } + } + + retval = sysfs_create_bin_file(reflash_hcd->sysfs_dir, &bin_attrs[0]); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs bin file\n"); + goto err_sysfs_create_bin_file; + } + + reflash_hcd->custom_dir = kobject_create_and_add(CUSTOM_DIR_NAME, + reflash_hcd->sysfs_dir); + if (!reflash_hcd->custom_dir) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create custom sysfs directory\n"); + retval = -EINVAL; + goto err_custom_sysfs_create_dir; + } + + for (idx = 1; idx < ARRAY_SIZE(bin_attrs); idx++) { + retval = sysfs_create_bin_file(reflash_hcd->custom_dir, + &bin_attrs[idx]); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs bin file\n"); + goto err_custom_sysfs_create_bin_file; + } + } + + tcm_hcd->read_flash_data = reflash_read_data; + + return 0; + +err_custom_sysfs_create_bin_file: + for (idx--; idx > 0; idx--) + sysfs_remove_bin_file(reflash_hcd->custom_dir, &bin_attrs[idx]); + + kobject_put(reflash_hcd->custom_dir); + + idx = ARRAY_SIZE(attrs); + +err_custom_sysfs_create_dir: + sysfs_remove_bin_file(reflash_hcd->sysfs_dir, &bin_attrs[0]); + +err_sysfs_create_bin_file: +err_sysfs_create_file: + for (idx--; idx >= 0; idx--) + sysfs_remove_file(reflash_hcd->sysfs_dir, &(*attrs[idx]).attr); + + kobject_put(reflash_hcd->sysfs_dir); + +err_sysfs_create_dir: +err_allocate_memory: + kfree(reflash_hcd->image_buf); + + RELEASE_BUFFER(reflash_hcd->read); + RELEASE_BUFFER(reflash_hcd->resp); + RELEASE_BUFFER(reflash_hcd->out); + + kfree(reflash_hcd); + reflash_hcd = NULL; + + return retval; +} + +static int reflash_remove(struct syna_tcm_hcd *tcm_hcd) +{ + int idx; + + if (!reflash_hcd) + goto exit; + + tcm_hcd->read_flash_data = NULL; + + if (ENABLE_SYSFS_INTERFACE) { + for (idx = 1; idx < ARRAY_SIZE(bin_attrs); idx++) { + sysfs_remove_bin_file(reflash_hcd->custom_dir, + &bin_attrs[idx]); + } + + kobject_put(reflash_hcd->custom_dir); + + sysfs_remove_bin_file(reflash_hcd->sysfs_dir, &bin_attrs[0]); + + for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) { + sysfs_remove_file(reflash_hcd->sysfs_dir, + &(*attrs[idx]).attr); + } + + kobject_put(reflash_hcd->sysfs_dir); + } + +#ifdef STARTUP_REFLASH + cancel_work_sync(&reflash_hcd->work); + flush_workqueue(reflash_hcd->workqueue); + destroy_workqueue(reflash_hcd->workqueue); +#endif + + kfree(reflash_hcd->image_buf); + + RELEASE_BUFFER(reflash_hcd->read); + RELEASE_BUFFER(reflash_hcd->resp); + RELEASE_BUFFER(reflash_hcd->out); + + kfree(reflash_hcd); + reflash_hcd = NULL; + +exit: + complete(&reflash_remove_complete); + + return 0; +} + +static int reflash_reset(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + + if (!reflash_hcd) { + retval = reflash_init(tcm_hcd); + return retval; + } + + return 0; +} + +static struct syna_tcm_module_cb reflash_module = { + .type = TCM_REFLASH, + .init = reflash_init, + .remove = reflash_remove, + .syncbox = NULL, + .asyncbox = NULL, + .reset = reflash_reset, + .suspend = NULL, + .resume = NULL, + .early_suspend = NULL, +}; + +static int __init reflash_module_init(void) +{ + return syna_tcm_add_module(&reflash_module, true); +} + +static void __exit reflash_module_exit(void) +{ + syna_tcm_add_module(&reflash_module, false); + + wait_for_completion(&reflash_remove_complete); +} + +module_init(reflash_module_init); +module_exit(reflash_module_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics TCM Reflash Module"); +MODULE_LICENSE("GPL v2"); diff --git a/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm_spi.c b/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm_spi.c new file mode 100644 index 0000000000..ee8751ac52 --- /dev/null +++ b/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm_spi.c @@ -0,0 +1,670 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2019 Scott Lin + * + * 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" + +static unsigned char *buf; + +static unsigned int buf_size; + +static struct spi_transfer *xfer; + +static struct syna_tcm_bus_io bus_io; + +static struct syna_tcm_hw_interface hw_if; + +static struct platform_device *syna_tcm_spi_device; + +#ifdef CONFIG_OF +static int parse_dt(struct device *dev, struct syna_tcm_board_data *bdata) +{ + int retval; + u32 value; + struct property *prop; + struct device_node *np = dev->of_node; + const char *name; + + prop = of_find_property(np, "synaptics,irq-gpio", NULL); + if (prop && prop->length) { + bdata->irq_gpio = of_get_named_gpio_flags(np, + "synaptics,irq-gpio", 0, + (enum of_gpio_flags *)&bdata->irq_flags); + } else { + bdata->irq_gpio = -1; + } + + retval = of_property_read_u32(np, "synaptics,irq-on-state", &value); + if (retval < 0) + bdata->irq_on_state = 0; + else + bdata->irq_on_state = value; + + retval = of_property_read_string(np, "synaptics,pwr-reg-name", &name); + if (retval < 0) + bdata->pwr_reg_name = NULL; + else + bdata->pwr_reg_name = name; + + retval = of_property_read_string(np, "synaptics,bus-reg-name", &name); + if (retval < 0) + bdata->bus_reg_name = NULL; + else + bdata->bus_reg_name = name; + + prop = of_find_property(np, "synaptics,power-gpio", NULL); + if (prop && prop->length) { + bdata->power_gpio = of_get_named_gpio_flags(np, + "synaptics,power-gpio", 0, NULL); + } else { + bdata->power_gpio = -1; + } + + prop = of_find_property(np, "synaptics,power-on-state", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,power-on-state", + &value); + if (retval < 0) { + LOGE(dev, + "Failed to read synaptics,power-on-state\n"); + return retval; + } + bdata->power_on_state = value; + } else { + bdata->power_on_state = 0; + } + + prop = of_find_property(np, "synaptics,power-delay-ms", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,power-delay-ms", + &value); + if (retval < 0) { + LOGE(dev, "Failed to read synaptics,power-delay-ms\n"); + return retval; + } + bdata->power_delay_ms = value; + } else { + bdata->power_delay_ms = 0; + } + + prop = of_find_property(np, "synaptics,reset-gpio", NULL); + if (prop && prop->length) { + bdata->reset_gpio = of_get_named_gpio_flags(np, + "synaptics,reset-gpio", 0, NULL); + } else { + bdata->reset_gpio = -1; + } + + prop = of_find_property(np, "synaptics,reset-on-state", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,reset-on-state", + &value); + if (retval < 0) { + LOGE(dev, "Failed to read synaptics,reset-on-state\n"); + return retval; + } + bdata->reset_on_state = value; + } else { + bdata->reset_on_state = 0; + } + + prop = of_find_property(np, "synaptics,reset-active-ms", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,reset-active-ms", + &value); + if (retval < 0) { + LOGE(dev, "Failed to read synaptics,reset-active-ms\n"); + return retval; + } + bdata->reset_active_ms = value; + } else { + bdata->reset_active_ms = 0; + } + + prop = of_find_property(np, "synaptics,reset-delay-ms", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,reset-delay-ms", + &value); + if (retval < 0) { + LOGE(dev, "Unable to read synaptics,reset-delay-ms\n"); + return retval; + } + bdata->reset_delay_ms = value; + } else { + bdata->reset_delay_ms = 0; + } + + prop = of_find_property(np, "synaptics,x-flip", NULL); + bdata->x_flip = prop > 0 ? true : false; + + prop = of_find_property(np, "synaptics,y-flip", NULL); + bdata->y_flip = prop > 0 ? true : false; + + prop = of_find_property(np, "synaptics,swap-axes", NULL); + bdata->swap_axes = prop > 0 ? true : false; + + prop = of_find_property(np, "synaptics,byte-delay-us", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,byte-delay-us", + &value); + if (retval < 0) { + LOGE(dev, "Unable to read synaptics,byte-delay-us\n"); + return retval; + } + bdata->byte_delay_us = value; + } else { + bdata->byte_delay_us = 0; + } + + prop = of_find_property(np, "synaptics,block-delay-us", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,block-delay-us", + &value); + if (retval < 0) { + LOGE(dev, "Unable to read synaptics,block-delay-us\n"); + return retval; + } + bdata->block_delay_us = value; + } else { + bdata->block_delay_us = 0; + } + + prop = of_find_property(np, "synaptics,spi-mode", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,spi-mode", + &value); + if (retval < 0) { + LOGE(dev, "Unable to read synaptics,spi-mode\n"); + return retval; + } + bdata->spi_mode = value; + + } else { + bdata->spi_mode = 0; + } + + prop = of_find_property(np, "synaptics,ubl-max-freq", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,ubl-max-freq", + &value); + if (retval < 0) { + LOGE(dev, "Unable to read synaptics,ubl-max-freq\n"); + return retval; + } + bdata->ubl_max_freq = value; + } else { + bdata->ubl_max_freq = 0; + } + + prop = of_find_property(np, "synaptics,ubl-byte-delay-us", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,ubl-byte-delay-us", + &value); + if (retval < 0) { + LOGE(dev, + "Unable to read synaptics,ubl-byte-delay-us\n"); + return retval; + } + bdata->ubl_byte_delay_us = value; + } else { + bdata->ubl_byte_delay_us = 0; + } + + return 0; +} +#endif + +static int syna_tcm_spi_alloc_mem(struct syna_tcm_hcd *tcm_hcd, + unsigned int count, unsigned int size) +{ + static unsigned int xfer_count; + struct spi_device *spi = to_spi_device(tcm_hcd->pdev->dev.parent); + + if (count > xfer_count) { + kfree(xfer); + xfer = kcalloc(count, sizeof(*xfer), GFP_KERNEL); + if (!xfer) { + LOGE(&spi->dev, + "Failed to allocate memory for xfer\n"); + xfer_count = 0; + return -ENOMEM; + } + xfer_count = count; + } else { + memset(xfer, 0, count * sizeof(*xfer)); + } + + if (size > buf_size) { + if (buf_size) + kfree(buf); + buf = kmalloc(size, GFP_KERNEL); + if (!buf) { + LOGE(&spi->dev, + "Failed to allocate memory for buf\n"); + buf_size = 0; + return -ENOMEM; + } + buf_size = size; + } + + return 0; +} + +static int syna_tcm_spi_rmi_read(struct syna_tcm_hcd *tcm_hcd, + unsigned short addr, unsigned char *data, unsigned int length) +{ + int retval; + unsigned int idx; + unsigned int mode; + unsigned int byte_count; + struct spi_message msg; + struct spi_device *spi = to_spi_device(tcm_hcd->pdev->dev.parent); + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + mutex_lock(&tcm_hcd->io_ctrl_mutex); + + spi_message_init(&msg); + + byte_count = length + 2; + + if (bdata->ubl_byte_delay_us == 0) + retval = syna_tcm_spi_alloc_mem(tcm_hcd, 2, byte_count); + else + retval = syna_tcm_spi_alloc_mem(tcm_hcd, byte_count, 3); + if (retval < 0) { + LOGE(&spi->dev, + "Failed to allocate memory\n"); + goto exit; + } + + buf[0] = (unsigned char)(addr >> 8) | 0x80; + buf[1] = (unsigned char)addr; + + if (bdata->ubl_byte_delay_us == 0) { + xfer[0].len = 2; + xfer[0].tx_buf = buf; + xfer[0].speed_hz = bdata->ubl_max_freq; + spi_message_add_tail(&xfer[0], &msg); + memset(&buf[2], 0xff, length); + xfer[1].len = length; + xfer[1].tx_buf = &buf[2]; + xfer[1].rx_buf = data; + if (bdata->block_delay_us) + xfer[1].delay_usecs = bdata->block_delay_us; + xfer[1].speed_hz = bdata->ubl_max_freq; + spi_message_add_tail(&xfer[1], &msg); + } else { + buf[2] = 0xff; + for (idx = 0; idx < byte_count; idx++) { + xfer[idx].len = 1; + if (idx < 2) { + xfer[idx].tx_buf = &buf[idx]; + } else { + xfer[idx].tx_buf = &buf[2]; + xfer[idx].rx_buf = &data[idx - 2]; + } + xfer[idx].delay_usecs = bdata->ubl_byte_delay_us; + if (bdata->block_delay_us && (idx == byte_count - 1)) + xfer[idx].delay_usecs = bdata->block_delay_us; + xfer[idx].speed_hz = bdata->ubl_max_freq; + spi_message_add_tail(&xfer[idx], &msg); + } + } + + mode = spi->mode; + spi->mode = SPI_MODE_3; + + retval = spi_sync(spi, &msg); + if (retval == 0) { + retval = length; + } else { + LOGE(&spi->dev, + "Failed to complete SPI transfer, error = %d\n", + retval); + } + + spi->mode = mode; + +exit: + mutex_unlock(&tcm_hcd->io_ctrl_mutex); + + return retval; +} + +static int syna_tcm_spi_rmi_write(struct syna_tcm_hcd *tcm_hcd, + unsigned short addr, unsigned char *data, unsigned int length) +{ + int retval; + unsigned int mode; + unsigned int byte_count; + struct spi_message msg; + struct spi_device *spi = to_spi_device(tcm_hcd->pdev->dev.parent); + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + mutex_lock(&tcm_hcd->io_ctrl_mutex); + + spi_message_init(&msg); + + byte_count = length + 2; + + retval = syna_tcm_spi_alloc_mem(tcm_hcd, 1, byte_count); + if (retval < 0) { + LOGE(&spi->dev, + "Failed to allocate memory\n"); + goto exit; + } + + buf[0] = (unsigned char)(addr >> 8) & ~0x80; + buf[1] = (unsigned char)addr; + retval = secure_memcpy(&buf[2], + buf_size - 2, + data, + length, + length); + if (retval < 0) { + LOGE(&spi->dev, + "Failed to copy write data\n"); + goto exit; + } + + xfer[0].len = byte_count; + xfer[0].tx_buf = buf; + if (bdata->block_delay_us) + xfer[0].delay_usecs = bdata->block_delay_us; + spi_message_add_tail(&xfer[0], &msg); + + mode = spi->mode; + spi->mode = SPI_MODE_3; + + retval = spi_sync(spi, &msg); + if (retval == 0) { + retval = length; + } else { + LOGE(&spi->dev, + "Failed to complete SPI transfer, error = %d\n", + retval); + } + + spi->mode = mode; + +exit: + mutex_unlock(&tcm_hcd->io_ctrl_mutex); + + return retval; +} + +static int syna_tcm_spi_read(struct syna_tcm_hcd *tcm_hcd, unsigned char *data, + unsigned int length) +{ + int retval; + unsigned int idx; + struct spi_message msg; + struct spi_device *spi = to_spi_device(tcm_hcd->pdev->dev.parent); + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + mutex_lock(&tcm_hcd->io_ctrl_mutex); + + spi_message_init(&msg); + + if (bdata->byte_delay_us == 0) + retval = syna_tcm_spi_alloc_mem(tcm_hcd, 1, length); + else + retval = syna_tcm_spi_alloc_mem(tcm_hcd, length, 1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory\n"); + goto exit; + } + + if (bdata->byte_delay_us == 0) { + memset(buf, 0xff, length); + xfer[0].len = length; + xfer[0].tx_buf = buf; + xfer[0].rx_buf = data; + if (bdata->block_delay_us) + xfer[0].delay_usecs = bdata->block_delay_us; + spi_message_add_tail(&xfer[0], &msg); + } else { + buf[0] = 0xff; + for (idx = 0; idx < length; idx++) { + xfer[idx].len = 1; + xfer[idx].tx_buf = buf; + xfer[idx].rx_buf = &data[idx]; + xfer[idx].delay_usecs = bdata->byte_delay_us; + if (bdata->block_delay_us && (idx == length - 1)) + xfer[idx].delay_usecs = bdata->block_delay_us; + spi_message_add_tail(&xfer[idx], &msg); + } + } + + retval = spi_sync(spi, &msg); + if (retval == 0) { + retval = length; + } else { + LOGE(&spi->dev, + "Failed to complete SPI transfer, error = %d\n", + retval); + } + +exit: + mutex_unlock(&tcm_hcd->io_ctrl_mutex); + + return retval; +} + +static int syna_tcm_spi_write(struct syna_tcm_hcd *tcm_hcd, unsigned char *data, + unsigned int length) +{ + int retval; + unsigned int idx; + struct spi_message msg; + struct spi_device *spi = to_spi_device(tcm_hcd->pdev->dev.parent); + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + mutex_lock(&tcm_hcd->io_ctrl_mutex); + + spi_message_init(&msg); + + if (bdata->byte_delay_us == 0) + retval = syna_tcm_spi_alloc_mem(tcm_hcd, 1, 0); + else + retval = syna_tcm_spi_alloc_mem(tcm_hcd, length, 0); + if (retval < 0) { + LOGE(&spi->dev, + "Failed to allocate memory\n"); + goto exit; + } + + if (bdata->byte_delay_us == 0) { + xfer[0].len = length; + xfer[0].tx_buf = data; + if (bdata->block_delay_us) + xfer[0].delay_usecs = bdata->block_delay_us; + spi_message_add_tail(&xfer[0], &msg); + } else { + for (idx = 0; idx < length; idx++) { + xfer[idx].len = 1; + xfer[idx].tx_buf = &data[idx]; + xfer[idx].delay_usecs = bdata->byte_delay_us; + if (bdata->block_delay_us && (idx == length - 1)) + xfer[idx].delay_usecs = bdata->block_delay_us; + spi_message_add_tail(&xfer[idx], &msg); + } + } + + retval = spi_sync(spi, &msg); + if (retval == 0) { + retval = length; + } else { + LOGE(&spi->dev, + "Failed to complete SPI transfer, error = %d\n", + retval); + } + +exit: + mutex_unlock(&tcm_hcd->io_ctrl_mutex); + + return retval; +} + +static int syna_tcm_spi_probe(struct spi_device *spi) +{ + int retval; + + if (spi->master->flags & SPI_MASTER_HALF_DUPLEX) { + LOGE(&spi->dev, + "Full duplex not supported by host\n"); + return -EIO; + } + + syna_tcm_spi_device = platform_device_alloc(PLATFORM_DRIVER_NAME, 0); + if (!syna_tcm_spi_device) { + LOGE(&spi->dev, + "Failed to allocate platform device\n"); + return -ENOMEM; + } + +#ifdef CONFIG_OF + hw_if.bdata = devm_kzalloc(&spi->dev, sizeof(*hw_if.bdata), GFP_KERNEL); + if (!hw_if.bdata) { + LOGE(&spi->dev, + "Failed to allocate memory for board data\n"); + return -ENOMEM; + } + parse_dt(&spi->dev, hw_if.bdata); +#else + hw_if.bdata = spi->dev.platform_data; +#endif + + switch (hw_if.bdata->spi_mode) { + case 0: + spi->mode = SPI_MODE_0; + break; + case 1: + spi->mode = SPI_MODE_1; + break; + case 2: + spi->mode = SPI_MODE_2; + break; + case 3: + spi->mode = SPI_MODE_3; + break; + } + + bus_io.type = BUS_SPI; + bus_io.read = syna_tcm_spi_read; + bus_io.write = syna_tcm_spi_write; + bus_io.rmi_read = syna_tcm_spi_rmi_read; + bus_io.rmi_write = syna_tcm_spi_rmi_write; + + hw_if.bus_io = &bus_io; + + spi->bits_per_word = 8; + + retval = spi_setup(spi); + if (retval < 0) { + LOGE(&spi->dev, + "Failed to set up SPI protocol driver\n"); + return retval; + } + + syna_tcm_spi_device->dev.parent = &spi->dev; + syna_tcm_spi_device->dev.platform_data = &hw_if; + + retval = platform_device_add(syna_tcm_spi_device); + if (retval < 0) { + LOGE(&spi->dev, + "Failed to add platform device\n"); + return retval; + } + + return 0; +} + +static int syna_tcm_spi_remove(struct spi_device *spi) +{ + syna_tcm_spi_device->dev.platform_data = NULL; + + platform_device_unregister(syna_tcm_spi_device); + + return 0; +} + +static const struct spi_device_id syna_tcm_id_table[] = { + {SPI_MODULE_NAME, 0}, + {}, +}; +MODULE_DEVICE_TABLE(spi, syna_tcm_id_table); + +#ifdef CONFIG_OF +static const struct of_device_id syna_tcm_of_match_table[] = { + { + .compatible = "synaptics,tcm-spi", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, syna_tcm_of_match_table); +#else +#define syna_tcm_of_match_table NULL +#endif + +static struct spi_driver syna_tcm_spi_driver = { + .driver = { + .name = SPI_MODULE_NAME, + .owner = THIS_MODULE, + .of_match_table = syna_tcm_of_match_table, + }, + .probe = syna_tcm_spi_probe, + .remove = syna_tcm_spi_remove, + .id_table = syna_tcm_id_table, +}; + +int syna_tcm_bus_init_spi(void) +{ + return spi_register_driver(&syna_tcm_spi_driver); +} +EXPORT_SYMBOL(syna_tcm_bus_init_spi); + +void syna_tcm_bus_exit_spi(void) +{ + kfree(buf); + + kfree(xfer); + + spi_unregister_driver(&syna_tcm_spi_driver); +} +EXPORT_SYMBOL(syna_tcm_bus_exit_spi); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics TCM SPI Bus Module"); +MODULE_LICENSE("GPL v2"); diff --git a/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm_testing.c b/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm_testing.c new file mode 100644 index 0000000000..b1921f779c --- /dev/null +++ b/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm_testing.c @@ -0,0 +1,1938 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2019 Scott Lin + * + * 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 "synaptics_tcm_core.h" +#include "synaptics_tcm_testing.h" + +#define SYSFS_DIR_NAME "testing" + +#define REPORT_TIMEOUT_MS 500 + +#define testing_sysfs_show(t_name) \ +static ssize_t testing_sysfs_##t_name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + int retval; \ + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; \ +\ + mutex_lock(&tcm_hcd->extif_mutex); \ +\ + retval = testing_##t_name(); \ + if (retval < 0) { \ + LOGE(tcm_hcd->pdev->dev.parent, \ + "Failed to do "#t_name" test\n"); \ + goto exit; \ + } \ +\ + retval = snprintf(buf, PAGE_SIZE, \ + "%s\n", \ + testing_hcd->result ? "Passed" : "Failed"); \ +\ +exit: \ + mutex_unlock(&tcm_hcd->extif_mutex); \ +\ + return retval; \ +} + +enum test_code { + TEST_TRX_TRX_SHORTS = 0, + TEST_TRX_SENSOR_OPENS = 1, + TEST_TRX_GROUND_SHORTS = 2, + TEST_DYNAMIC_RANGE = 7, + TEST_OPEN_SHORT_DETECTOR = 8, + TEST_NOISE = 10, + TEST_PT11 = 11, + TEST_PT12 = 12, + TEST_PT13 = 13, + TEST_DYNAMIC_RANGE_DOZE = 14, + TEST_NOISE_DOZE = 15, +}; + +struct testing_hcd { + bool result; + unsigned char report_type; + unsigned int report_index; + unsigned int num_of_reports; + struct kobject *sysfs_dir; + struct syna_tcm_buffer out; + struct syna_tcm_buffer resp; + struct syna_tcm_buffer report; + struct syna_tcm_buffer process; + struct syna_tcm_buffer output; + struct syna_tcm_hcd *tcm_hcd; + int (*collect_reports)(enum report_type report_type, + unsigned int num_of_reports); +}; + +DECLARE_COMPLETION(report_complete); + +DECLARE_COMPLETION(testing_remove_complete); + +static struct testing_hcd *testing_hcd; + +static int testing_dynamic_range(void); + +static int testing_dynamic_range_lpwg(void); + +static int testing_dynamic_range_doze(void); + +static int testing_noise(void); + +static int testing_noise_lpwg(void); + +static int testing_noise_doze(void); + +static int testing_open_short_detector(void); + +static int testing_pt11(void); + +static int testing_pt12(void); + +static int testing_pt13(void); + +static int testing_reset_open(void); + +static int testing_lockdown(void); + +static int testing_trx(enum test_code test_code); + +SHOW_PROTOTYPE(testing, dynamic_range); +SHOW_PROTOTYPE(testing, dynamic_range_lpwg); +SHOW_PROTOTYPE(testing, dynamic_range_doze); +SHOW_PROTOTYPE(testing, noise); +SHOW_PROTOTYPE(testing, noise_lpwg); +SHOW_PROTOTYPE(testing, noise_doze); +SHOW_PROTOTYPE(testing, open_short_detector); +SHOW_PROTOTYPE(testing, pt11); +SHOW_PROTOTYPE(testing, pt12); +SHOW_PROTOTYPE(testing, pt13); +SHOW_PROTOTYPE(testing, reset_open); +SHOW_PROTOTYPE(testing, lockdown); +SHOW_PROTOTYPE(testing, trx_trx_shorts); +SHOW_PROTOTYPE(testing, trx_sensor_opens); +SHOW_PROTOTYPE(testing, trx_ground_shorts); +SHOW_PROTOTYPE(testing, size); + +static struct device_attribute *attrs[] = { + ATTRIFY(dynamic_range), + ATTRIFY(dynamic_range_lpwg), + ATTRIFY(dynamic_range_doze), + ATTRIFY(noise), + ATTRIFY(noise_lpwg), + ATTRIFY(noise_doze), + ATTRIFY(open_short_detector), + ATTRIFY(pt11), + ATTRIFY(pt12), + ATTRIFY(pt13), + ATTRIFY(reset_open), + ATTRIFY(lockdown), + ATTRIFY(trx_trx_shorts), + ATTRIFY(trx_sensor_opens), + ATTRIFY(trx_ground_shorts), + ATTRIFY(size), +}; + +static ssize_t testing_sysfs_data_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static struct bin_attribute bin_attr = { + .attr = { + .name = "data", + .mode = 0444, + }, + .size = 0, + .read = testing_sysfs_data_show, +}; + +testing_sysfs_show(dynamic_range) + +testing_sysfs_show(dynamic_range_lpwg) + +testing_sysfs_show(dynamic_range_doze) + +testing_sysfs_show(noise) + +testing_sysfs_show(noise_lpwg) + +testing_sysfs_show(noise_doze) + +testing_sysfs_show(open_short_detector) + +testing_sysfs_show(pt11) + +testing_sysfs_show(pt12) + +testing_sysfs_show(pt13) + +testing_sysfs_show(reset_open) + +testing_sysfs_show(lockdown) + +static ssize_t testing_sysfs_trx_trx_shorts_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + retval = testing_trx(TEST_TRX_TRX_SHORTS); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do TRX-TRX shorts test\n"); + goto exit; + } + + retval = snprintf(buf, PAGE_SIZE, + "%s\n", + testing_hcd->result ? "Passed" : "Failed"); + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t testing_sysfs_trx_sensor_opens_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + retval = testing_trx(TEST_TRX_SENSOR_OPENS); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do TRX-sensor opens test\n"); + goto exit; + } + + retval = snprintf(buf, PAGE_SIZE, + "%s\n", + testing_hcd->result ? "Passed" : "Failed"); + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t testing_sysfs_trx_ground_shorts_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + retval = testing_trx(TEST_TRX_GROUND_SHORTS); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do TRX-ground shorts test\n"); + goto exit; + } + + retval = snprintf(buf, PAGE_SIZE, + "%s\n", + testing_hcd->result ? "Passed" : "Failed"); + +exit: + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t testing_sysfs_size_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + LOCK_BUFFER(testing_hcd->output); + + retval = snprintf(buf, PAGE_SIZE, + "%u\n", + testing_hcd->output.data_length); + + UNLOCK_BUFFER(testing_hcd->output); + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static ssize_t testing_sysfs_data_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + unsigned int readlen; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + mutex_lock(&tcm_hcd->extif_mutex); + + LOCK_BUFFER(testing_hcd->output); + + readlen = MIN(count, testing_hcd->output.data_length - pos); + + retval = secure_memcpy(buf, + count, + &testing_hcd->output.buf[pos], + testing_hcd->output.buf_size - pos, + readlen); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy report data\n"); + } else { + retval = readlen; + } + + UNLOCK_BUFFER(testing_hcd->output); + + mutex_unlock(&tcm_hcd->extif_mutex); + + return retval; +} + +static int testing_run_prod_test_item(enum test_code test_code) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + if (tcm_hcd->features.dual_firmware && + tcm_hcd->id_info.mode != MODE_PRODUCTION_TEST) { + retval = tcm_hcd->switch_mode(tcm_hcd, FW_MODE_PRODUCTION_TEST); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run production test firmware\n"); + return retval; + } + } else if (tcm_hcd->id_info.mode != MODE_APPLICATION || + tcm_hcd->app_status != APP_STATUS_OK) { + LOGE(tcm_hcd->pdev->dev.parent, + "Application firmware not running\n"); + return -ENODEV; + } + + LOCK_BUFFER(testing_hcd->out); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &testing_hcd->out, + 1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for testing_hcd->out.buf\n"); + UNLOCK_BUFFER(testing_hcd->out); + return retval; + } + + testing_hcd->out.buf[0] = test_code; + + LOCK_BUFFER(testing_hcd->resp); + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_PRODUCTION_TEST, + testing_hcd->out.buf, + 1, + &testing_hcd->resp.buf, + &testing_hcd->resp.buf_size, + &testing_hcd->resp.data_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_PRODUCTION_TEST)); + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->out); + return retval; + } + + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->out); + + return 0; +} + +static int testing_collect_reports(enum report_type report_type, + unsigned int num_of_reports) +{ + int retval; + bool completed; + unsigned int timeout; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + testing_hcd->report_index = 0; + testing_hcd->report_type = report_type; + testing_hcd->num_of_reports = num_of_reports; + + reinit_completion(&report_complete); + + LOCK_BUFFER(testing_hcd->out); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &testing_hcd->out, + 1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for testing_hcd->out.buf\n"); + UNLOCK_BUFFER(testing_hcd->out); + goto exit; + } + + testing_hcd->out.buf[0] = testing_hcd->report_type; + + LOCK_BUFFER(testing_hcd->resp); + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_ENABLE_REPORT, + testing_hcd->out.buf, + 1, + &testing_hcd->resp.buf, + &testing_hcd->resp.buf_size, + &testing_hcd->resp.data_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_ENABLE_REPORT)); + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->out); + goto exit; + } + + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->out); + + completed = false; + timeout = REPORT_TIMEOUT_MS * num_of_reports; + + retval = wait_for_completion_timeout(&report_complete, + msecs_to_jiffies(timeout)); + if (retval == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Timed out waiting for report collection\n"); + } else { + completed = true; + } + + LOCK_BUFFER(testing_hcd->out); + + testing_hcd->out.buf[0] = testing_hcd->report_type; + + LOCK_BUFFER(testing_hcd->resp); + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_DISABLE_REPORT, + testing_hcd->out.buf, + 1, + &testing_hcd->resp.buf, + &testing_hcd->resp.buf_size, + &testing_hcd->resp.data_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_DISABLE_REPORT)); + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->out); + goto exit; + } + + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->out); + + if (completed) + retval = 0; + else + retval = -EIO; + +exit: + testing_hcd->report_type = 0; + + return retval; +} + +static void testing_get_frame_size_words(unsigned int *size, bool image_only) +{ + unsigned int rows; + unsigned int cols; + unsigned int hybrid; + unsigned int buttons; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + app_info = &tcm_hcd->app_info; + + rows = le2_to_uint(app_info->num_of_image_rows); + cols = le2_to_uint(app_info->num_of_image_cols); + hybrid = le2_to_uint(app_info->has_hybrid_data); + buttons = le2_to_uint(app_info->num_of_buttons); + + *size = rows * cols; + + if (!image_only) { + if (hybrid) + *size += rows + cols; + *size += buttons; + } +} + +static void testing_doze_frame_output(unsigned int rows, unsigned int cols) +{ + int retval; + unsigned int data_size; + unsigned int header_size; + unsigned int output_size; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + app_info = &tcm_hcd->app_info; + + header_size = 2; + + data_size = rows * cols; + + if (le2_to_uint(app_info->num_of_buttons)) + data_size++; + + output_size = header_size + data_size * 2; + + LOCK_BUFFER(testing_hcd->output); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &testing_hcd->output, + output_size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for testing_hcd->output.buf\n"); + UNLOCK_BUFFER(testing_hcd->output); + return; + } + + testing_hcd->output.buf[0] = rows; + testing_hcd->output.buf[1] = cols; + + output_size = header_size; + + LOCK_BUFFER(testing_hcd->resp); + + retval = secure_memcpy(testing_hcd->output.buf + header_size, + testing_hcd->output.buf_size - header_size, + testing_hcd->resp.buf, + testing_hcd->resp.buf_size, + testing_hcd->resp.data_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy test data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->output); + return; + } + + output_size += testing_hcd->resp.data_length; + + UNLOCK_BUFFER(testing_hcd->resp); + + testing_hcd->output.data_length = output_size; + + UNLOCK_BUFFER(testing_hcd->output); +} + +static void testing_standard_frame_output(bool image_only) +{ + int retval; + unsigned int data_size; + unsigned int header_size; + unsigned int output_size; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + app_info = &tcm_hcd->app_info; + + testing_get_frame_size_words(&data_size, image_only); + + header_size = sizeof(app_info->num_of_buttons) + + sizeof(app_info->num_of_image_rows) + + sizeof(app_info->num_of_image_cols) + + sizeof(app_info->has_hybrid_data); + + output_size = header_size + data_size * 2; + + LOCK_BUFFER(testing_hcd->output); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &testing_hcd->output, + output_size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for testing_hcd->output.buf\n"); + UNLOCK_BUFFER(testing_hcd->output); + return; + } + + retval = secure_memcpy(testing_hcd->output.buf, + testing_hcd->output.buf_size, + &app_info->num_of_buttons[0], + header_size, + header_size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy header data\n"); + UNLOCK_BUFFER(testing_hcd->output); + return; + } + + output_size = header_size; + + LOCK_BUFFER(testing_hcd->resp); + + retval = secure_memcpy(testing_hcd->output.buf + header_size, + testing_hcd->output.buf_size - header_size, + testing_hcd->resp.buf, + testing_hcd->resp.buf_size, + testing_hcd->resp.data_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy test data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->output); + return; + } + + output_size += testing_hcd->resp.data_length; + + UNLOCK_BUFFER(testing_hcd->resp); + + testing_hcd->output.data_length = output_size; + + UNLOCK_BUFFER(testing_hcd->output); +} + +static int testing_dynamic_range_doze(void) +{ + int retval; + unsigned char *buf; + unsigned int idx; + unsigned int row; + unsigned int col; + unsigned int data; + unsigned int rows; + unsigned int cols; + unsigned int data_size; + unsigned int limits_rows; + unsigned int limits_cols; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + app_info = &tcm_hcd->app_info; + + cols = le2_to_uint(app_info->num_of_image_cols); + + retval = testing_run_prod_test_item(TEST_DYNAMIC_RANGE_DOZE); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run test\n"); + goto exit; + } + + LOCK_BUFFER(testing_hcd->resp); + + data_size = testing_hcd->resp.data_length / 2; + + if (le2_to_uint(app_info->num_of_buttons)) + data_size--; + + if (data_size % cols) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid max number of rows per burst\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + rows = data_size / cols; + + limits_rows = ARRAY_SIZE(drt_hi_limits); + limits_cols = ARRAY_SIZE(drt_hi_limits[0]); + + if (rows > limits_rows || cols > limits_cols) { + LOGE(tcm_hcd->pdev->dev.parent, + "Mismatching limits data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + limits_rows = ARRAY_SIZE(drt_lo_limits); + limits_cols = ARRAY_SIZE(drt_lo_limits[0]); + + if (rows > limits_rows || cols > limits_cols) { + LOGE(tcm_hcd->pdev->dev.parent, + "Mismatching limits data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + idx = 0; + buf = testing_hcd->resp.buf; + testing_hcd->result = true; + + for (row = 0; row < rows; row++) { + for (col = 0; col < cols; col++) { + data = le2_to_uint(&buf[idx * 2]); + if (data > drt_hi_limits[row][col] || + data < drt_lo_limits[row][col]) { + testing_hcd->result = false; + break; + } + idx++; + } + } + + UNLOCK_BUFFER(testing_hcd->resp); + + testing_doze_frame_output(rows, cols); + + retval = 0; + +exit: + if (tcm_hcd->features.dual_firmware) { + if (tcm_hcd->reset(tcm_hcd, false, true) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + } + } + + return retval; +} + +static int testing_dynamic_range_lpwg(void) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + retval = tcm_hcd->set_dynamic_config(tcm_hcd, + DC_IN_WAKEUP_GESTURE_MODE, + 1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enable wakeup gesture mode\n"); + return retval; + } + + retval = testing_dynamic_range(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do dynamic range test\n"); + return retval; + } + + retval = tcm_hcd->set_dynamic_config(tcm_hcd, + DC_IN_WAKEUP_GESTURE_MODE, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to disable wakeup gesture mode\n"); + return retval; + } + + return 0; +} + +static int testing_dynamic_range(void) +{ + int retval; + unsigned char *buf; + unsigned int idx; + unsigned int row; + unsigned int col; + unsigned int data; + unsigned int rows; + unsigned int cols; + unsigned int limits_rows; + unsigned int limits_cols; + unsigned int frame_size_words; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + app_info = &tcm_hcd->app_info; + + rows = le2_to_uint(app_info->num_of_image_rows); + cols = le2_to_uint(app_info->num_of_image_cols); + + testing_get_frame_size_words(&frame_size_words, false); + + retval = testing_run_prod_test_item(TEST_DYNAMIC_RANGE); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run test\n"); + goto exit; + } + + LOCK_BUFFER(testing_hcd->resp); + + if (frame_size_words != testing_hcd->resp.data_length / 2) { + LOGE(tcm_hcd->pdev->dev.parent, + "Frame size mismatch\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + limits_rows = ARRAY_SIZE(drt_hi_limits); + limits_cols = ARRAY_SIZE(drt_hi_limits[0]); + + if (rows > limits_rows || cols > limits_cols) { + LOGE(tcm_hcd->pdev->dev.parent, + "Mismatching limits data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + limits_rows = ARRAY_SIZE(drt_lo_limits); + limits_cols = ARRAY_SIZE(drt_lo_limits[0]); + + if (rows > limits_rows || cols > limits_cols) { + LOGE(tcm_hcd->pdev->dev.parent, + "Mismatching limits data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + idx = 0; + buf = testing_hcd->resp.buf; + testing_hcd->result = true; + + for (row = 0; row < rows; row++) { + for (col = 0; col < cols; col++) { + data = le2_to_uint(&buf[idx * 2]); + if (data > drt_hi_limits[row][col] || + data < drt_lo_limits[row][col]) { + testing_hcd->result = false; + break; + } + idx++; + } + } + + UNLOCK_BUFFER(testing_hcd->resp); + + testing_standard_frame_output(false); + + retval = 0; + +exit: + if (tcm_hcd->features.dual_firmware) { + if (tcm_hcd->reset(tcm_hcd, false, true) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + } + } + + return retval; +} + +static int testing_noise_doze(void) +{ + int retval; + short data; + unsigned char *buf; + unsigned int idx; + unsigned int row; + unsigned int col; + unsigned int rows; + unsigned int cols; + unsigned int data_size; + unsigned int limits_rows; + unsigned int limits_cols; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + app_info = &tcm_hcd->app_info; + + cols = le2_to_uint(app_info->num_of_image_cols); + + retval = testing_run_prod_test_item(TEST_NOISE_DOZE); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run test\n"); + goto exit; + } + + LOCK_BUFFER(testing_hcd->resp); + + data_size = testing_hcd->resp.data_length / 2; + + if (le2_to_uint(app_info->num_of_buttons)) + data_size--; + + if (data_size % cols) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid max number of rows per burst\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + rows = data_size / cols; + + limits_rows = ARRAY_SIZE(noise_limits); + limits_cols = ARRAY_SIZE(noise_limits[0]); + + if (rows > limits_rows || cols > limits_cols) { + LOGE(tcm_hcd->pdev->dev.parent, + "Mismatching limits data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + idx = 0; + buf = testing_hcd->resp.buf; + testing_hcd->result = true; + + for (row = 0; row < rows; row++) { + for (col = 0; col < cols; col++) { + data = (short)le2_to_uint(&buf[idx * 2]); + if (data > noise_limits[row][col]) { + testing_hcd->result = false; + break; + } + idx++; + } + } + + UNLOCK_BUFFER(testing_hcd->resp); + + testing_doze_frame_output(rows, cols); + + retval = 0; + +exit: + if (tcm_hcd->features.dual_firmware) { + if (tcm_hcd->reset(tcm_hcd, false, true) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + } + } + + return retval; +} + +static int testing_noise_lpwg(void) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + retval = tcm_hcd->set_dynamic_config(tcm_hcd, + DC_IN_WAKEUP_GESTURE_MODE, + 1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enable wakeup gesture mode\n"); + return retval; + } + + retval = testing_noise(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do noise test\n"); + return retval; + } + + retval = tcm_hcd->set_dynamic_config(tcm_hcd, + DC_IN_WAKEUP_GESTURE_MODE, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to disable wakeup gesture mode\n"); + return retval; + } + + return 0; +} + +static int testing_noise(void) +{ + int retval; + short data; + unsigned char *buf; + unsigned int idx; + unsigned int row; + unsigned int col; + unsigned int rows; + unsigned int cols; + unsigned int limits_rows; + unsigned int limits_cols; + unsigned int frame_size_words; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + app_info = &tcm_hcd->app_info; + + rows = le2_to_uint(app_info->num_of_image_rows); + cols = le2_to_uint(app_info->num_of_image_cols); + + testing_get_frame_size_words(&frame_size_words, false); + + retval = testing_run_prod_test_item(TEST_NOISE); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run test\n"); + goto exit; + } + + LOCK_BUFFER(testing_hcd->resp); + + if (frame_size_words != testing_hcd->resp.data_length / 2) { + LOGE(tcm_hcd->pdev->dev.parent, + "Frame size mismatch\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + limits_rows = ARRAY_SIZE(noise_limits); + limits_cols = ARRAY_SIZE(noise_limits[0]); + + if (rows > limits_rows || cols > limits_cols) { + LOGE(tcm_hcd->pdev->dev.parent, + "Mismatching limits data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + idx = 0; + buf = testing_hcd->resp.buf; + testing_hcd->result = true; + + for (row = 0; row < rows; row++) { + for (col = 0; col < cols; col++) { + data = (short)le2_to_uint(&buf[idx * 2]); + if (data > noise_limits[row][col]) { + testing_hcd->result = false; + break; + } + idx++; + } + } + + UNLOCK_BUFFER(testing_hcd->resp); + + testing_standard_frame_output(false); + + retval = 0; + +exit: + if (tcm_hcd->features.dual_firmware) { + if (tcm_hcd->reset(tcm_hcd, false, true) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + } + } + + return retval; +} + +static void testing_open_short_detector_output(void) +{ + int retval; + unsigned int rows; + unsigned int cols; + unsigned int data_size; + unsigned int header_size; + unsigned int output_size; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + app_info = &tcm_hcd->app_info; + + rows = le2_to_uint(app_info->num_of_image_rows); + cols = le2_to_uint(app_info->num_of_image_cols); + data_size = (rows * cols + 7) / 8; + + header_size = sizeof(app_info->num_of_buttons) + + sizeof(app_info->num_of_image_rows) + + sizeof(app_info->num_of_image_cols) + + sizeof(app_info->has_hybrid_data); + + output_size = header_size + data_size * 2; + + LOCK_BUFFER(testing_hcd->output); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &testing_hcd->output, + output_size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for output.buf\n"); + UNLOCK_BUFFER(testing_hcd->output); + return; + } + + retval = secure_memcpy(testing_hcd->output.buf, + testing_hcd->output.buf_size, + &app_info->num_of_buttons[0], + header_size, + header_size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy header data\n"); + UNLOCK_BUFFER(testing_hcd->output); + return; + } + + output_size = header_size; + + LOCK_BUFFER(testing_hcd->resp); + + retval = secure_memcpy(testing_hcd->output.buf + header_size, + testing_hcd->output.buf_size - header_size, + testing_hcd->resp.buf, + testing_hcd->resp.buf_size, + testing_hcd->resp.data_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy test data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->output); + return; + } + + output_size += testing_hcd->resp.data_length; + + UNLOCK_BUFFER(testing_hcd->resp); + + testing_hcd->output.data_length = output_size; + + UNLOCK_BUFFER(testing_hcd->output); +} + +static int testing_open_short_detector(void) +{ + int retval; + unsigned int bit; + unsigned int byte; + unsigned int row; + unsigned int col; + unsigned int rows; + unsigned int cols; + unsigned int data_size; + unsigned char *data; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + app_info = &tcm_hcd->app_info; + + rows = le2_to_uint(app_info->num_of_image_rows); + cols = le2_to_uint(app_info->num_of_image_cols); + data_size = (rows * cols + 7) / 8; + + retval = testing_run_prod_test_item(TEST_OPEN_SHORT_DETECTOR); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run test\n"); + goto exit; + } + + LOCK_BUFFER(testing_hcd->resp); + + if (data_size * 2 != testing_hcd->resp.data_length) { + LOGE(tcm_hcd->pdev->dev.parent, + "Data size mismatch\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + testing_hcd->result = true; + + bit = 0; + byte = 0; + data = &testing_hcd->resp.buf[0]; + for (row = 0; row < rows; row++) { + for (col = 0; col < cols; col++) { + if (data[byte] & (1 << bit)) { + testing_hcd->result = false; + break; + } + if (bit++ > 7) { + bit = 0; + byte++; + } + } + } + + if (testing_hcd->result == true) { + bit = 0; + byte = 0; + data = &testing_hcd->resp.buf[data_size]; + for (row = 0; row < rows; row++) { + for (col = 0; col < cols; col++) { + if (data[byte] & (1 << bit)) { + testing_hcd->result = false; + break; + } + if (bit++ > 7) { + bit = 0; + byte++; + } + } + } + } + + UNLOCK_BUFFER(testing_hcd->resp); + + testing_open_short_detector_output(); + + retval = 0; + +exit: + if (tcm_hcd->reset(tcm_hcd, false, true) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + } + + return retval; +} + +static int testing_pt11(void) +{ + int retval; + short data; + unsigned char *buf; + unsigned int idx; + unsigned int row; + unsigned int col; + unsigned int rows; + unsigned int cols; + unsigned int limits_rows; + unsigned int limits_cols; + unsigned int image_size_words; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + app_info = &tcm_hcd->app_info; + + rows = le2_to_uint(app_info->num_of_image_rows); + cols = le2_to_uint(app_info->num_of_image_cols); + + testing_get_frame_size_words(&image_size_words, true); + + retval = testing_run_prod_test_item(TEST_PT11); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run test\n"); + goto exit; + } + + LOCK_BUFFER(testing_hcd->resp); + + if (image_size_words != testing_hcd->resp.data_length / 2) { + LOGE(tcm_hcd->pdev->dev.parent, + "Image size mismatch\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + limits_rows = ARRAY_SIZE(pt11_hi_limits); + limits_cols = ARRAY_SIZE(pt11_hi_limits[0]); + + if (rows > limits_rows || cols > limits_cols) { + LOGE(tcm_hcd->pdev->dev.parent, + "Mismatching limits data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + limits_rows = ARRAY_SIZE(pt11_lo_limits); + limits_cols = ARRAY_SIZE(pt11_lo_limits[0]); + + if (rows > limits_rows || cols > limits_cols) { + LOGE(tcm_hcd->pdev->dev.parent, + "Mismatching limits data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + idx = 0; + buf = testing_hcd->resp.buf; + testing_hcd->result = true; + + for (row = 0; row < rows; row++) { + for (col = 0; col < cols; col++) { + data = (short)le2_to_uint(&buf[idx * 2]); + if (data > pt11_hi_limits[row][col] || + data < pt11_lo_limits[row][col]) { + testing_hcd->result = false; + break; + } + idx++; + } + } + + UNLOCK_BUFFER(testing_hcd->resp); + + testing_standard_frame_output(true); + + retval = 0; + +exit: + if (tcm_hcd->features.dual_firmware) { + if (tcm_hcd->reset(tcm_hcd, false, true) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + } + } + + return retval; +} + +static int testing_pt12(void) +{ + int retval; + short data; + unsigned char *buf; + unsigned int idx; + unsigned int row; + unsigned int col; + unsigned int rows; + unsigned int cols; + unsigned int limits_rows; + unsigned int limits_cols; + unsigned int image_size_words; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + app_info = &tcm_hcd->app_info; + + rows = le2_to_uint(app_info->num_of_image_rows); + cols = le2_to_uint(app_info->num_of_image_cols); + + testing_get_frame_size_words(&image_size_words, true); + + retval = testing_run_prod_test_item(TEST_PT12); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run test\n"); + goto exit; + } + + LOCK_BUFFER(testing_hcd->resp); + + if (image_size_words != testing_hcd->resp.data_length / 2) { + LOGE(tcm_hcd->pdev->dev.parent, + "Image size mismatch\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + limits_rows = ARRAY_SIZE(pt12_limits); + limits_cols = ARRAY_SIZE(pt12_limits[0]); + + if (rows > limits_rows || cols > limits_cols) { + LOGE(tcm_hcd->pdev->dev.parent, + "Mismatching limits data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + idx = 0; + buf = testing_hcd->resp.buf; + testing_hcd->result = true; + + for (row = 0; row < rows; row++) { + for (col = 0; col < cols; col++) { + data = (short)le2_to_uint(&buf[idx * 2]); + if (data < pt12_limits[row][col]) { + testing_hcd->result = false; + break; + } + idx++; + } + } + + UNLOCK_BUFFER(testing_hcd->resp); + + testing_standard_frame_output(true); + + retval = 0; + +exit: + if (tcm_hcd->features.dual_firmware) { + if (tcm_hcd->reset(tcm_hcd, false, true) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + } + } + + return retval; +} + +static int testing_pt13(void) +{ + int retval; + short data; + unsigned char *buf; + unsigned int idx; + unsigned int row; + unsigned int col; + unsigned int rows; + unsigned int cols; + unsigned int limits_rows; + unsigned int limits_cols; + unsigned int image_size_words; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + app_info = &tcm_hcd->app_info; + + rows = le2_to_uint(app_info->num_of_image_rows); + cols = le2_to_uint(app_info->num_of_image_cols); + + testing_get_frame_size_words(&image_size_words, true); + + retval = testing_run_prod_test_item(TEST_PT13); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run test\n"); + goto exit; + } + + LOCK_BUFFER(testing_hcd->resp); + + if (image_size_words != testing_hcd->resp.data_length / 2) { + LOGE(tcm_hcd->pdev->dev.parent, + "Image size mismatch\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + limits_rows = ARRAY_SIZE(pt13_limits); + limits_cols = ARRAY_SIZE(pt13_limits[0]); + + if (rows > limits_rows || cols > limits_cols) { + LOGE(tcm_hcd->pdev->dev.parent, + "Mismatching limits data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + retval = -EINVAL; + goto exit; + } + + idx = 0; + buf = testing_hcd->resp.buf; + testing_hcd->result = true; + + for (row = 0; row < rows; row++) { + for (col = 0; col < cols; col++) { + data = (short)le2_to_uint(&buf[idx * 2]); + if (data < pt13_limits[row][col]) { + testing_hcd->result = false; + break; + } + idx++; + } + } + + UNLOCK_BUFFER(testing_hcd->resp); + + testing_standard_frame_output(true); + + retval = 0; + +exit: + if (tcm_hcd->features.dual_firmware) { + if (tcm_hcd->reset(tcm_hcd, false, true) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + } + } + + return retval; +} + +static int testing_reset_open(void) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + if (bdata->reset_gpio < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Hardware reset unavailable\n"); + return -EINVAL; + } + + mutex_lock(&tcm_hcd->reset_mutex); + + tcm_hcd->update_watchdog(tcm_hcd, false); + + gpio_set_value(bdata->reset_gpio, bdata->reset_on_state); + msleep(bdata->reset_active_ms); + gpio_set_value(bdata->reset_gpio, !bdata->reset_on_state); + msleep(bdata->reset_delay_ms); + + tcm_hcd->update_watchdog(tcm_hcd, true); + + mutex_unlock(&tcm_hcd->reset_mutex); + + if (tcm_hcd->id_info.mode == MODE_APPLICATION) { + retval = tcm_hcd->switch_mode(tcm_hcd, FW_MODE_BOOTLOADER); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enter bootloader mode\n"); + return retval; + } + } else { + retval = tcm_hcd->identify(tcm_hcd, false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do identification\n"); + goto run_app_firmware; + } + } + + if (tcm_hcd->boot_info.last_reset_reason == reset_open_limit) + testing_hcd->result = true; + else + testing_hcd->result = false; + + retval = 0; + +run_app_firmware: + if (tcm_hcd->switch_mode(tcm_hcd, FW_MODE_APPLICATION) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run application firmware\n"); + } + + return retval; +} + +static void testing_lockdown_output(void) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + LOCK_BUFFER(testing_hcd->output); + LOCK_BUFFER(testing_hcd->resp); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &testing_hcd->output, + testing_hcd->resp.data_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for output.buf\n"); + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->output); + return; + } + + retval = secure_memcpy(testing_hcd->output.buf, + testing_hcd->output.buf_size, + testing_hcd->resp.buf, + testing_hcd->resp.buf_size, + testing_hcd->resp.data_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy test data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->output); + return; + } + + testing_hcd->output.data_length = testing_hcd->resp.data_length; + + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->output); +} + +static int testing_lockdown(void) +{ + int retval; + unsigned int idx; + unsigned int lockdown_size; + unsigned int limits_size; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + if (tcm_hcd->read_flash_data == NULL) { + LOGE(tcm_hcd->pdev->dev.parent, + "Unable to read from flash\n"); + return -EINVAL; + } + + LOCK_BUFFER(testing_hcd->resp); + + retval = tcm_hcd->read_flash_data(CUSTOM_OTP, true, &testing_hcd->resp); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read lockdown data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + return retval; + } + + lockdown_size = testing_hcd->resp.data_length; + + limits_size = sizeof(lockdown_limits) / sizeof(*lockdown_limits); + + if (lockdown_size != limits_size) { + LOGE(tcm_hcd->pdev->dev.parent, + "Mismatching limits data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + return -EINVAL; + } + + testing_hcd->result = true; + + for (idx = 0; idx < lockdown_size; idx++) { + if (testing_hcd->resp.buf[idx] != lockdown_limits[idx]) { + testing_hcd->result = false; + break; + } + } + + UNLOCK_BUFFER(testing_hcd->resp); + + testing_lockdown_output(); + + return 0; +} + +static void testing_trx_output(void) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + LOCK_BUFFER(testing_hcd->output); + LOCK_BUFFER(testing_hcd->resp); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &testing_hcd->output, + testing_hcd->resp.data_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for output.buf\n"); + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->output); + return; + } + + retval = secure_memcpy(testing_hcd->output.buf, + testing_hcd->output.buf_size, + testing_hcd->resp.buf, + testing_hcd->resp.buf_size, + testing_hcd->resp.data_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy test data\n"); + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->output); + return; + } + + testing_hcd->output.data_length = testing_hcd->resp.data_length; + + UNLOCK_BUFFER(testing_hcd->resp); + UNLOCK_BUFFER(testing_hcd->output); +} + +static int testing_trx(enum test_code test_code) +{ + int retval; + unsigned char pass_vector; + unsigned int idx; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + switch (test_code) { + case TEST_TRX_TRX_SHORTS: + case TEST_TRX_GROUND_SHORTS: + pass_vector = 0xff; + break; + case TEST_TRX_SENSOR_OPENS: + pass_vector = 0x00; + break; + default: + return -EINVAL; + } + + retval = testing_run_prod_test_item(test_code); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to run test\n"); + goto exit; + } + + LOCK_BUFFER(testing_hcd->resp); + + testing_hcd->result = true; + + for (idx = 0; idx < testing_hcd->resp.data_length; idx++) { + if (testing_hcd->resp.buf[idx] != pass_vector) { + testing_hcd->result = false; + break; + } + } + + UNLOCK_BUFFER(testing_hcd->resp); + + testing_trx_output(); + + retval = 0; + +exit: + if (tcm_hcd->features.dual_firmware) { + if (tcm_hcd->reset(tcm_hcd, false, true) < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do reset\n"); + } + } + + return retval; +} + +static void testing_report(void) +{ + int retval; + unsigned int offset; + unsigned int report_size; + struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; + + report_size = tcm_hcd->report.buffer.data_length; + + LOCK_BUFFER(testing_hcd->report); + + if (testing_hcd->report_index == 0) { + retval = syna_tcm_alloc_mem(tcm_hcd, + &testing_hcd->report, + report_size * testing_hcd->num_of_reports); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for report.buf\n"); + UNLOCK_BUFFER(testing_hcd->report); + return; + } + } + + if (testing_hcd->report_index < testing_hcd->num_of_reports) { + offset = report_size * testing_hcd->report_index; + + retval = secure_memcpy(testing_hcd->report.buf + offset, + testing_hcd->report.buf_size - offset, + tcm_hcd->report.buffer.buf, + tcm_hcd->report.buffer.buf_size, + tcm_hcd->report.buffer.data_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy report data\n"); + UNLOCK_BUFFER(testing_hcd->report); + return; + } + + testing_hcd->report_index++; + testing_hcd->report.data_length += report_size; + } + + UNLOCK_BUFFER(testing_hcd->report); + + if (testing_hcd->report_index == testing_hcd->num_of_reports) + complete(&report_complete); +} + +static int testing_init(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + int idx; + + testing_hcd = kzalloc(sizeof(*testing_hcd), GFP_KERNEL); + if (!testing_hcd) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for testing_hcd\n"); + return -ENOMEM; + } + + testing_hcd->tcm_hcd = tcm_hcd; + + testing_hcd->collect_reports = testing_collect_reports; + + INIT_BUFFER(testing_hcd->out, false); + INIT_BUFFER(testing_hcd->resp, false); + INIT_BUFFER(testing_hcd->report, false); + INIT_BUFFER(testing_hcd->process, false); + INIT_BUFFER(testing_hcd->output, false); + + testing_hcd->sysfs_dir = kobject_create_and_add(SYSFS_DIR_NAME, + tcm_hcd->sysfs_dir); + if (!testing_hcd->sysfs_dir) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs directory\n"); + retval = -EINVAL; + goto err_sysfs_create_dir; + } + + for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) { + retval = sysfs_create_file(testing_hcd->sysfs_dir, + &(*attrs[idx]).attr); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs file\n"); + goto err_sysfs_create_file; + } + } + + retval = sysfs_create_bin_file(testing_hcd->sysfs_dir, &bin_attr); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to create sysfs bin file\n"); + goto err_sysfs_create_bin_file; + } + + return 0; + +err_sysfs_create_bin_file: +err_sysfs_create_file: + for (idx--; idx >= 0; idx--) + sysfs_remove_file(testing_hcd->sysfs_dir, &(*attrs[idx]).attr); + + kobject_put(testing_hcd->sysfs_dir); + +err_sysfs_create_dir: + RELEASE_BUFFER(testing_hcd->output); + RELEASE_BUFFER(testing_hcd->process); + RELEASE_BUFFER(testing_hcd->report); + RELEASE_BUFFER(testing_hcd->resp); + RELEASE_BUFFER(testing_hcd->out); + + kfree(testing_hcd); + testing_hcd = NULL; + + return retval; +} + +static int testing_remove(struct syna_tcm_hcd *tcm_hcd) +{ + int idx; + + if (!testing_hcd) + goto exit; + + sysfs_remove_bin_file(testing_hcd->sysfs_dir, &bin_attr); + + for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) + sysfs_remove_file(testing_hcd->sysfs_dir, &(*attrs[idx]).attr); + + kobject_put(testing_hcd->sysfs_dir); + + RELEASE_BUFFER(testing_hcd->output); + RELEASE_BUFFER(testing_hcd->process); + RELEASE_BUFFER(testing_hcd->report); + RELEASE_BUFFER(testing_hcd->resp); + RELEASE_BUFFER(testing_hcd->out); + + kfree(testing_hcd); + testing_hcd = NULL; + +exit: + complete(&testing_remove_complete); + + return 0; +} + +static int testing_reset(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + + if (!testing_hcd) { + retval = testing_init(tcm_hcd); + return retval; + } + + return 0; +} + +static int testing_syncbox(struct syna_tcm_hcd *tcm_hcd) +{ + if (!testing_hcd) + return 0; + + if (tcm_hcd->report.id == testing_hcd->report_type) + testing_report(); + + return 0; +} + +static struct syna_tcm_module_cb testing_module = { + .type = TCM_TESTING, + .init = testing_init, + .remove = testing_remove, + .syncbox = testing_syncbox, + .asyncbox = NULL, + .reset = testing_reset, + .suspend = NULL, + .resume = NULL, + .early_suspend = NULL, +}; + +static int __init testing_module_init(void) +{ + return syna_tcm_add_module(&testing_module, true); +} + +static void __exit testing_module_exit(void) +{ + syna_tcm_add_module(&testing_module, false); + + wait_for_completion(&testing_remove_complete); +} + +module_init(testing_module_init); +module_exit(testing_module_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics TCM Testing Module"); +MODULE_LICENSE("GPL v2"); diff --git a/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm_testing.h b/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm_testing.h new file mode 100644 index 0000000000..c5a39a5896 --- /dev/null +++ b/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm_testing.h @@ -0,0 +1,85 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2019 Scott Lin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#ifndef _SYNAPTICS_TCM_TESTING_H_ +#define _SYNAPTICS_TCM_TESTING_H_ + +static const unsigned short drt_hi_limits[32][32] = { + {0}, +}; + +static const unsigned short drt_lo_limits[32][32] = { + {0,}, +}; + +static const unsigned short noise_limits[32][32] = { + {0,}, +}; + +static const short pt11_hi_limits[32][32] = { + {0,}, +}; + +static const short pt11_lo_limits[32][32] = { + {0,}, +}; + +static const short pt12_limits[32][32] = { + {0,}, +}; + +static const short pt13_limits[32][32] = { + {0,}, +}; + +static const unsigned char lockdown_limits[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +}; + +static const unsigned char reset_open_limit = 0x13; + +#endif diff --git a/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm_touch.c b/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm_touch.c new file mode 100644 index 0000000000..b4b6b75965 --- /dev/null +++ b/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm_touch.c @@ -0,0 +1,1267 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2019 Scott Lin + * + * 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, + NOP = -1, +}; + +enum touch_report_code { + TOUCH_END = 0, + TOUCH_FOREACH_ACTIVE_OBJECT, + TOUCH_FOREACH_OBJECT, + TOUCH_FOREACH_END, + TOUCH_PAD_TO_NEXT_BYTE, + TOUCH_TIMESTAMP, + TOUCH_OBJECT_N_INDEX, + TOUCH_OBJECT_N_CLASSIFICATION, + TOUCH_OBJECT_N_X_POSITION, + TOUCH_OBJECT_N_Y_POSITION, + TOUCH_OBJECT_N_Z, + TOUCH_OBJECT_N_X_WIDTH, + TOUCH_OBJECT_N_Y_WIDTH, + TOUCH_OBJECT_N_TX_POSITION_TIXELS, + TOUCH_OBJECT_N_RX_POSITION_TIXELS, + TOUCH_0D_BUTTONS_STATE, + TOUCH_GESTURE_DOUBLE_TAP, + TOUCH_FRAME_RATE, + TOUCH_POWER_IM, + TOUCH_CID_IM, + TOUCH_RAIL_IM, + TOUCH_CID_VARIANCE_IM, + TOUCH_NSM_FREQUENCY, + TOUCH_NSM_STATE, + TOUCH_NUM_OF_ACTIVE_OBJECTS, + TOUCH_NUM_OF_CPU_CYCLES_USED_SINCE_LAST_FRAME, + TOUCH_TUNING_GAUSSIAN_WIDTHS = 0x80, + TOUCH_TUNING_SMALL_OBJECT_PARAMS, + TOUCH_TUNING_0D_BUTTONS_VARIANCE, +}; + +struct object_data { + unsigned char status; + unsigned int x_pos; + unsigned int y_pos; + unsigned int x_width; + unsigned int y_width; + unsigned int z; + unsigned int tx_pos; + unsigned int rx_pos; +}; + +struct input_params { + unsigned int max_x; + unsigned int max_y; + unsigned int max_objects; +}; + +struct touch_data { + struct object_data *object_data; + unsigned int timestamp; + unsigned int buttons_state; + unsigned int gesture_double_tap; + unsigned int frame_rate; + unsigned int power_im; + unsigned int cid_im; + unsigned int rail_im; + unsigned int cid_variance_im; + unsigned int nsm_frequency; + unsigned int nsm_state; + unsigned int num_of_active_objects; + unsigned int num_of_cpu_cycles; +}; + +struct touch_hcd { + bool irq_wake; + bool report_touch; + bool suspend_touch; + unsigned char *prev_status; + unsigned int max_x; + unsigned int max_y; + unsigned int max_objects; + struct mutex report_mutex; + struct input_dev *input_dev; + struct touch_data touch_data; + struct input_params input_params; + struct syna_tcm_buffer out; + struct syna_tcm_buffer resp; + struct syna_tcm_hcd *tcm_hcd; +}; + +DECLARE_COMPLETION(touch_remove_complete); + +static struct touch_hcd *touch_hcd; + +/** + * touch_free_objects() - Free all touch objects + * + * Report finger lift events to the input subsystem for all touch objects. + */ +static void touch_free_objects(void) +{ +#ifdef TYPE_B_PROTOCOL + unsigned int idx; +#endif + + if (touch_hcd->input_dev == NULL) + return; + + mutex_lock(&touch_hcd->report_mutex); + +#ifdef TYPE_B_PROTOCOL + for (idx = 0; idx < touch_hcd->max_objects; idx++) { + input_mt_slot(touch_hcd->input_dev, idx); + input_mt_report_slot_state(touch_hcd->input_dev, + MT_TOOL_FINGER, 0); + } +#endif + input_report_key(touch_hcd->input_dev, + BTN_TOUCH, 0); + input_report_key(touch_hcd->input_dev, + BTN_TOOL_FINGER, 0); +#ifndef TYPE_B_PROTOCOL + input_mt_sync(touch_hcd->input_dev); +#endif + input_sync(touch_hcd->input_dev); + + mutex_unlock(&touch_hcd->report_mutex); +} + +/** + * touch_get_report_data() - Retrieve data from touch report + * + * Retrieve data from the touch report based on the bit offset and bit length + * information from the touch report configuration. + */ +static int touch_get_report_data(unsigned int offset, + unsigned int bits, unsigned int *data) +{ + unsigned char mask; + unsigned char byte_data; + unsigned int output_data; + unsigned int bit_offset; + unsigned int byte_offset; + unsigned int data_bits; + unsigned int available_bits; + unsigned int remaining_bits; + unsigned char *touch_report; + struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; + + if (bits == 0 || bits > 32) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid number of bits\n"); + return -EINVAL; + } + + if (offset + bits > tcm_hcd->report.buffer.data_length * 8) { + *data = 0; + return 0; + } + + touch_report = tcm_hcd->report.buffer.buf; + + output_data = 0; + remaining_bits = bits; + + bit_offset = offset % 8; + byte_offset = offset / 8; + + while (remaining_bits) { + byte_data = touch_report[byte_offset]; + byte_data >>= bit_offset; + + available_bits = 8 - bit_offset; + data_bits = MIN(available_bits, remaining_bits); + mask = 0xff >> (8 - data_bits); + + byte_data &= mask; + + output_data |= byte_data << (bits - remaining_bits); + + bit_offset = 0; + byte_offset += 1; + remaining_bits -= data_bits; + } + + *data = output_data; + + return 0; +} + +/** + * touch_parse_report() - Parse touch report + * + * Traverse through the touch report configuration and parse the touch report + * generated by the device accordingly to retrieve the touch data. + */ +static int touch_parse_report(void) +{ + int retval; + bool active_only; + bool num_of_active_objects; + unsigned char code; + unsigned int size; + unsigned int idx; + unsigned int obj; + unsigned int next; + unsigned int data; + unsigned int bits; + unsigned int offset; + unsigned int objects; + unsigned int active_objects; + unsigned int report_size; + unsigned int config_size; + unsigned char *config_data; + struct touch_data *touch_data; + struct object_data *object_data; + struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; + static unsigned int end_of_foreach; + + touch_data = &touch_hcd->touch_data; + object_data = touch_hcd->touch_data.object_data; + + config_data = tcm_hcd->config.buf; + config_size = tcm_hcd->config.data_length; + + report_size = tcm_hcd->report.buffer.data_length; + + size = sizeof(*object_data) * touch_hcd->max_objects; + memset(touch_hcd->touch_data.object_data, 0x00, size); + + num_of_active_objects = false; + + idx = 0; + offset = 0; + objects = 0; + while (idx < config_size) { + code = config_data[idx++]; + switch (code) { + case TOUCH_END: + goto exit; + case TOUCH_FOREACH_ACTIVE_OBJECT: + obj = 0; + next = idx; + active_only = true; + break; + case TOUCH_FOREACH_OBJECT: + obj = 0; + next = idx; + active_only = false; + break; + case TOUCH_FOREACH_END: + end_of_foreach = idx; + if (active_only) { + if (num_of_active_objects) { + objects++; + if (objects < active_objects) + idx = next; + } else if (offset < report_size * 8) { + idx = next; + } + } else { + obj++; + if (obj < touch_hcd->max_objects) + idx = next; + } + break; + case TOUCH_PAD_TO_NEXT_BYTE: + offset = ceil_div(offset, 8) * 8; + break; + case TOUCH_TIMESTAMP: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get timestamp\n"); + return retval; + } + touch_data->timestamp = data; + offset += bits; + break; + case TOUCH_OBJECT_N_INDEX: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &obj); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get object index\n"); + return retval; + } + offset += bits; + break; + case TOUCH_OBJECT_N_CLASSIFICATION: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get classification data\n"); + return retval; + } + object_data[obj].status = data; + offset += bits; + break; + case TOUCH_OBJECT_N_X_POSITION: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get object x position\n"); + return retval; + } + object_data[obj].x_pos = data; + offset += bits; + break; + case TOUCH_OBJECT_N_Y_POSITION: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get object y position\n"); + return retval; + } + object_data[obj].y_pos = data; + offset += bits; + break; + case TOUCH_OBJECT_N_Z: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get object z\n"); + return retval; + } + object_data[obj].z = data; + offset += bits; + break; + case TOUCH_OBJECT_N_X_WIDTH: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get object x width\n"); + return retval; + } + object_data[obj].x_width = data; + offset += bits; + break; + case TOUCH_OBJECT_N_Y_WIDTH: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get object y width\n"); + return retval; + } + object_data[obj].y_width = data; + offset += bits; + break; + case TOUCH_OBJECT_N_TX_POSITION_TIXELS: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get object tx position\n"); + return retval; + } + object_data[obj].tx_pos = data; + offset += bits; + break; + case TOUCH_OBJECT_N_RX_POSITION_TIXELS: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get object rx position\n"); + return retval; + } + object_data[obj].rx_pos = data; + offset += bits; + break; + case TOUCH_0D_BUTTONS_STATE: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get 0D buttons state\n"); + return retval; + } + touch_data->buttons_state = data; + offset += bits; + break; + case TOUCH_GESTURE_DOUBLE_TAP: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get gesture double tap\n"); + return retval; + } + touch_data->gesture_double_tap = data; + offset += bits; + break; + case TOUCH_FRAME_RATE: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get frame rate\n"); + return retval; + } + touch_data->frame_rate = data; + offset += bits; + break; + case TOUCH_POWER_IM: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get power IM\n"); + return retval; + } + touch_data->power_im = data; + offset += bits; + break; + case TOUCH_CID_IM: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get CID IM\n"); + return retval; + } + touch_data->cid_im = data; + offset += bits; + break; + case TOUCH_RAIL_IM: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get rail IM\n"); + return retval; + } + touch_data->rail_im = data; + offset += bits; + break; + case TOUCH_CID_VARIANCE_IM: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get CID variance IM\n"); + return retval; + } + touch_data->cid_variance_im = data; + offset += bits; + break; + case TOUCH_NSM_FREQUENCY: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get NSM frequency\n"); + return retval; + } + touch_data->nsm_frequency = data; + offset += bits; + break; + case TOUCH_NSM_STATE: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get NSM state\n"); + return retval; + } + touch_data->nsm_state = data; + offset += bits; + break; + case TOUCH_NUM_OF_ACTIVE_OBJECTS: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get number of objects\n"); + return retval; + } + active_objects = data; + num_of_active_objects = true; + touch_data->num_of_active_objects = data; + offset += bits; + if (touch_data->num_of_active_objects == 0) + idx = end_of_foreach; + break; + case TOUCH_NUM_OF_CPU_CYCLES_USED_SINCE_LAST_FRAME: + bits = config_data[idx++]; + retval = touch_get_report_data(offset, bits, &data); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get number of CPU cycles\n"); + return retval; + } + touch_data->num_of_cpu_cycles = data; + offset += bits; + break; + case TOUCH_TUNING_GAUSSIAN_WIDTHS: + bits = config_data[idx++]; + offset += bits; + break; + case TOUCH_TUNING_SMALL_OBJECT_PARAMS: + bits = config_data[idx++]; + offset += bits; + break; + case TOUCH_TUNING_0D_BUTTONS_VARIANCE: + bits = config_data[idx++]; + offset += bits; + break; + } + } + +exit: + return 0; +} + +/** + * touch_report() - Report touch events + * + * Retrieve data from the touch report generated by the device and report touch + * events to the input subsystem. + */ +static void touch_report(void) +{ + int retval; + unsigned int idx; + unsigned int x; + unsigned int y; + unsigned int temp; + unsigned int status; + unsigned int touch_count; + struct touch_data *touch_data; + struct object_data *object_data; + struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + if (!touch_hcd->report_touch) + return; + + if (touch_hcd->input_dev == NULL) + return; + + mutex_lock(&touch_hcd->report_mutex); + + retval = touch_parse_report(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to parse touch report\n"); + goto exit; + } + + touch_data = &touch_hcd->touch_data; + object_data = touch_hcd->touch_data.object_data; + +#ifdef WAKEUP_GESTURE + if (touch_data->gesture_double_tap && tcm_hcd->in_suspend) { + input_report_key(touch_hcd->input_dev, KEY_WAKEUP, 1); + input_sync(touch_hcd->input_dev); + input_report_key(touch_hcd->input_dev, KEY_WAKEUP, 0); + input_sync(touch_hcd->input_dev); + } +#endif + + if (tcm_hcd->in_suspend) + goto exit; + + touch_count = 0; + + for (idx = 0; idx < touch_hcd->max_objects; idx++) { + if (touch_hcd->prev_status[idx] == LIFT && + object_data[idx].status == LIFT) + status = NOP; + else + status = object_data[idx].status; + + switch (status) { + case LIFT: +#ifdef TYPE_B_PROTOCOL + input_mt_slot(touch_hcd->input_dev, idx); + input_mt_report_slot_state(touch_hcd->input_dev, + MT_TOOL_FINGER, 0); +#endif + break; + case FINGER: + case GLOVED_FINGER: + x = object_data[idx].x_pos; + y = object_data[idx].y_pos; + if (bdata->swap_axes) { + temp = x; + x = y; + y = temp; + } + if (bdata->x_flip) + x = touch_hcd->input_params.max_x - x; + if (bdata->y_flip) + y = touch_hcd->input_params.max_y - y; +#ifdef TYPE_B_PROTOCOL + input_mt_slot(touch_hcd->input_dev, idx); + input_mt_report_slot_state(touch_hcd->input_dev, + MT_TOOL_FINGER, 1); +#endif + input_report_key(touch_hcd->input_dev, + BTN_TOUCH, 1); + input_report_key(touch_hcd->input_dev, + BTN_TOOL_FINGER, 1); + input_report_abs(touch_hcd->input_dev, + ABS_MT_POSITION_X, x); + input_report_abs(touch_hcd->input_dev, + ABS_MT_POSITION_Y, y); +#ifndef TYPE_B_PROTOCOL + input_mt_sync(touch_hcd->input_dev); +#endif + LOGD(tcm_hcd->pdev->dev.parent, + "Finger %d: x = %d, y = %d\n", + idx, x, y); + touch_count++; + break; + default: + break; + } + + touch_hcd->prev_status[idx] = object_data[idx].status; + } + + if (touch_count == 0) { + input_report_key(touch_hcd->input_dev, + BTN_TOUCH, 0); + input_report_key(touch_hcd->input_dev, + BTN_TOOL_FINGER, 0); +#ifndef TYPE_B_PROTOCOL + input_mt_sync(touch_hcd->input_dev); +#endif + } + + input_sync(touch_hcd->input_dev); + +exit: + mutex_unlock(&touch_hcd->report_mutex); +} + +/** + * touch_set_input_params() - Set input parameters + * + * Set the input parameters of the input device based on the information + * retrieved from the application information packet. In addition, set up an + * array for tracking the status of touch objects. + */ +static int touch_set_input_params(void) +{ + struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; + + input_set_abs_params(touch_hcd->input_dev, + ABS_MT_POSITION_X, 0, touch_hcd->max_x, 0, 0); + input_set_abs_params(touch_hcd->input_dev, + ABS_MT_POSITION_Y, 0, touch_hcd->max_y, 0, 0); + + input_mt_init_slots(touch_hcd->input_dev, touch_hcd->max_objects, + INPUT_MT_DIRECT); + + touch_hcd->input_params.max_x = touch_hcd->max_x; + touch_hcd->input_params.max_y = touch_hcd->max_y; + touch_hcd->input_params.max_objects = touch_hcd->max_objects; + + if (touch_hcd->max_objects == 0) + return 0; + + kfree(touch_hcd->prev_status); + touch_hcd->prev_status = kzalloc(touch_hcd->max_objects, GFP_KERNEL); + if (!touch_hcd->prev_status) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for prev_status\n"); + return -ENOMEM; + } + + return 0; +} + +/** + * touch_get_input_params() - Get input parameters + * + * Retrieve the input parameters to register with the input subsystem for + * the input device from the application information packet. In addition, + * the touch report configuration is retrieved and stored. + */ +static int touch_get_input_params(void) +{ + int retval; + unsigned int temp; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + app_info = &tcm_hcd->app_info; + touch_hcd->max_x = le2_to_uint(app_info->max_x); + touch_hcd->max_y = le2_to_uint(app_info->max_y); + touch_hcd->max_objects = le2_to_uint(app_info->max_objects); + + if (bdata->swap_axes) { + temp = touch_hcd->max_x; + touch_hcd->max_x = touch_hcd->max_y; + touch_hcd->max_y = temp; + } + + LOCK_BUFFER(tcm_hcd->config); + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_GET_TOUCH_REPORT_CONFIG, + NULL, + 0, + &tcm_hcd->config.buf, + &tcm_hcd->config.buf_size, + &tcm_hcd->config.data_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_GET_TOUCH_REPORT_CONFIG)); + UNLOCK_BUFFER(tcm_hcd->config); + return retval; + } + + UNLOCK_BUFFER(tcm_hcd->config); + + return 0; +} + +/** + * touch_set_input_dev() - Set up input device + * + * Allocate an input device, configure the input device based on the particular + * input events to be reported, and register the input device with the input + * subsystem. + */ +static int touch_set_input_dev(void) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; + + touch_hcd->input_dev = input_allocate_device(); + if (touch_hcd->input_dev == NULL) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate input device\n"); + return -ENODEV; + } + + touch_hcd->input_dev->name = TOUCH_INPUT_NAME; + touch_hcd->input_dev->phys = TOUCH_INPUT_PHYS_PATH; + touch_hcd->input_dev->id.product = SYNAPTICS_TCM_ID_PRODUCT; + touch_hcd->input_dev->id.version = SYNAPTICS_TCM_ID_VERSION; + touch_hcd->input_dev->dev.parent = tcm_hcd->pdev->dev.parent; + input_set_drvdata(touch_hcd->input_dev, tcm_hcd); + + set_bit(EV_SYN, touch_hcd->input_dev->evbit); + set_bit(EV_KEY, touch_hcd->input_dev->evbit); + set_bit(EV_ABS, touch_hcd->input_dev->evbit); + set_bit(BTN_TOUCH, touch_hcd->input_dev->keybit); + set_bit(BTN_TOOL_FINGER, touch_hcd->input_dev->keybit); +#ifdef INPUT_PROP_DIRECT + set_bit(INPUT_PROP_DIRECT, touch_hcd->input_dev->propbit); +#endif + +#ifdef WAKEUP_GESTURE + set_bit(KEY_WAKEUP, touch_hcd->input_dev->keybit); + input_set_capability(touch_hcd->input_dev, EV_KEY, KEY_WAKEUP); +#endif + + retval = touch_set_input_params(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set input parameters\n"); + input_free_device(touch_hcd->input_dev); + touch_hcd->input_dev = NULL; + return retval; + } + + retval = input_register_device(touch_hcd->input_dev); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to register input device\n"); + input_free_device(touch_hcd->input_dev); + touch_hcd->input_dev = NULL; + return retval; + } + + return 0; +} + +/** + * touch_set_report_config() - Set touch report configuration + * + * Send the SET_TOUCH_REPORT_CONFIG command to configure the format and content + * of the touch report. + */ +static int touch_set_report_config(void) +{ + int retval; + unsigned int idx; + unsigned int length; + struct syna_tcm_app_info *app_info; + struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; + + if (!bdata->extend_report) + return 0; + + app_info = &tcm_hcd->app_info; + length = le2_to_uint(app_info->max_touch_report_config_size); + + if (length < TOUCH_REPORT_CONFIG_SIZE) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid maximum touch report config size\n"); + return -EINVAL; + } + + LOCK_BUFFER(touch_hcd->out); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &touch_hcd->out, + length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for touch_hcd->out.buf\n"); + UNLOCK_BUFFER(touch_hcd->out); + return retval; + } + + idx = 0; +#ifdef WAKEUP_GESTURE + touch_hcd->out.buf[idx++] = TOUCH_GESTURE_DOUBLE_TAP; + touch_hcd->out.buf[idx++] = 8; +#endif + touch_hcd->out.buf[idx++] = TOUCH_FOREACH_ACTIVE_OBJECT; + touch_hcd->out.buf[idx++] = TOUCH_OBJECT_N_INDEX; + touch_hcd->out.buf[idx++] = 4; + touch_hcd->out.buf[idx++] = TOUCH_OBJECT_N_CLASSIFICATION; + touch_hcd->out.buf[idx++] = 4; + touch_hcd->out.buf[idx++] = TOUCH_OBJECT_N_X_POSITION; + touch_hcd->out.buf[idx++] = 12; + touch_hcd->out.buf[idx++] = TOUCH_OBJECT_N_Y_POSITION; + touch_hcd->out.buf[idx++] = 12; + touch_hcd->out.buf[idx++] = TOUCH_FOREACH_END; + touch_hcd->out.buf[idx++] = TOUCH_END; + + LOCK_BUFFER(touch_hcd->resp); + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_SET_TOUCH_REPORT_CONFIG, + touch_hcd->out.buf, + length, + &touch_hcd->resp.buf, + &touch_hcd->resp.buf_size, + &touch_hcd->resp.data_length, + NULL, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_SET_TOUCH_REPORT_CONFIG)); + UNLOCK_BUFFER(touch_hcd->resp); + UNLOCK_BUFFER(touch_hcd->out); + return retval; + } + + UNLOCK_BUFFER(touch_hcd->resp); + UNLOCK_BUFFER(touch_hcd->out); + + return 0; +} + +/** + * touch_check_input_params() - Check input parameters + * + * Check if any of the input parameters registered with the input subsystem for + * the input device has changed. + */ +static int touch_check_input_params(void) +{ + unsigned int size; + struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; + + if (touch_hcd->max_x == 0 && touch_hcd->max_y == 0) + return 0; + + if (touch_hcd->input_params.max_objects != touch_hcd->max_objects) { + kfree(touch_hcd->touch_data.object_data); + size = sizeof(*touch_hcd->touch_data.object_data); + size *= touch_hcd->max_objects; + touch_hcd->touch_data.object_data = kzalloc(size, GFP_KERNEL); + if (!touch_hcd->touch_data.object_data) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for object_data\n"); + return -ENOMEM; + } + return 1; + } + + if (touch_hcd->input_params.max_x != touch_hcd->max_x) + return 1; + + if (touch_hcd->input_params.max_y != touch_hcd->max_y) + return 1; + + return 0; +} + +/** + * touch_set_input_reporting() - Configure touch report and set up new input + * device if necessary + * + * After a device reset event, configure the touch report and set up a new input + * device if any of the input parameters has changed after the device reset. + */ +static int touch_set_input_reporting(void) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = touch_hcd->tcm_hcd; + + if (tcm_hcd->id_info.mode != MODE_APPLICATION || + tcm_hcd->app_status != APP_STATUS_OK) { + LOGN(tcm_hcd->pdev->dev.parent, + "Application firmware not running\n"); + return 0; + } + + touch_hcd->report_touch = false; + + touch_free_objects(); + + mutex_lock(&touch_hcd->report_mutex); + + retval = touch_set_report_config(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set report config\n"); + goto exit; + } + + retval = touch_get_input_params(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get input parameters\n"); + goto exit; + } + + retval = touch_check_input_params(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to check input parameters\n"); + goto exit; + } else if (retval == 0) { + LOGD(tcm_hcd->pdev->dev.parent, + "Input parameters unchanged\n"); + goto exit; + } + + if (touch_hcd->input_dev != NULL) { + input_unregister_device(touch_hcd->input_dev); + touch_hcd->input_dev = NULL; + } + + retval = touch_set_input_dev(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set up input device\n"); + goto exit; + } + +exit: + mutex_unlock(&touch_hcd->report_mutex); + + touch_hcd->report_touch = retval < 0 ? false : true; + + return retval; +} + +static int touch_init(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + + touch_hcd = kzalloc(sizeof(*touch_hcd), GFP_KERNEL); + if (!touch_hcd) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for touch_hcd\n"); + return -ENOMEM; + } + + touch_hcd->tcm_hcd = tcm_hcd; + + mutex_init(&touch_hcd->report_mutex); + + INIT_BUFFER(touch_hcd->out, false); + INIT_BUFFER(touch_hcd->resp, false); + + retval = touch_set_input_reporting(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set up input reporting\n"); + goto err_set_input_reporting; + } + + tcm_hcd->report_touch = touch_report; + + return 0; + +err_set_input_reporting: + kfree(touch_hcd->touch_data.object_data); + kfree(touch_hcd->prev_status); + + RELEASE_BUFFER(touch_hcd->resp); + RELEASE_BUFFER(touch_hcd->out); + + kfree(touch_hcd); + touch_hcd = NULL; + + return retval; +} + +static int touch_remove(struct syna_tcm_hcd *tcm_hcd) +{ + if (!touch_hcd) + goto exit; + + tcm_hcd->report_touch = NULL; + + if (touch_hcd->input_dev) + input_unregister_device(touch_hcd->input_dev); + + kfree(touch_hcd->touch_data.object_data); + kfree(touch_hcd->prev_status); + + RELEASE_BUFFER(touch_hcd->resp); + RELEASE_BUFFER(touch_hcd->out); + + kfree(touch_hcd); + touch_hcd = NULL; + +exit: + complete(&touch_remove_complete); + + return 0; +} + +static int touch_syncbox(struct syna_tcm_hcd *tcm_hcd) +{ + if (!touch_hcd) + return 0; + + switch (tcm_hcd->report.id) { + case REPORT_IDENTIFY: + touch_free_objects(); + break; + case REPORT_TOUCH: + if (!touch_hcd->suspend_touch) + touch_report(); + break; + default: + break; + } + + return 0; +} + +static int touch_asyncbox(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + + if (!touch_hcd) + return 0; + + switch (tcm_hcd->async_report_id) { + case REPORT_IDENTIFY: + if (tcm_hcd->id_info.mode != MODE_APPLICATION) + break; + retval = tcm_hcd->identify(tcm_hcd, false); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to do identification\n"); + return retval; + } + retval = touch_set_input_reporting(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set up input reporting\n"); + return retval; + } + break; + default: + break; + } + + return 0; +} + +static int touch_reset(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + + if (!touch_hcd) { + retval = touch_init(tcm_hcd); + return retval; + } + + if (tcm_hcd->id_info.mode == MODE_APPLICATION) { + retval = touch_set_input_reporting(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to set up input reporting\n"); + return retval; + } + } + + return 0; +} + +static int touch_early_suspend(struct syna_tcm_hcd *tcm_hcd) +{ + if (!touch_hcd) + return 0; + +#ifdef WAKEUP_GESTURE + touch_hcd->suspend_touch = false; +#else + touch_hcd->suspend_touch = true; +#endif + + touch_free_objects(); + + return 0; +} + +static int touch_suspend(struct syna_tcm_hcd *tcm_hcd) +{ +#ifdef WAKEUP_GESTURE + int retval; +#endif + + if (!touch_hcd) + return 0; + + touch_hcd->suspend_touch = true; + + touch_free_objects(); + +#ifdef WAKEUP_GESTURE + if (!touch_hcd->irq_wake) { + enable_irq_wake(tcm_hcd->irq); + touch_hcd->irq_wake = true; + } + + touch_hcd->suspend_touch = false; + + retval = tcm_hcd->set_dynamic_config(tcm_hcd, + DC_IN_WAKEUP_GESTURE_MODE, + 1); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enable wakeup gesture mode\n"); + return retval; + } +#endif + + return 0; +} + +static int touch_resume(struct syna_tcm_hcd *tcm_hcd) +{ +#ifdef WAKEUP_GESTURE + int retval; +#endif + + if (!touch_hcd) + return 0; + + touch_hcd->suspend_touch = false; + +#ifdef WAKEUP_GESTURE + if (touch_hcd->irq_wake) { + disable_irq_wake(tcm_hcd->irq); + touch_hcd->irq_wake = false; + } + + retval = tcm_hcd->set_dynamic_config(tcm_hcd, + DC_IN_WAKEUP_GESTURE_MODE, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to disable wakeup gesture mode\n"); + return retval; + } +#endif + + return 0; +} + +static struct syna_tcm_module_cb touch_module = { + .type = TCM_TOUCH, + .init = touch_init, + .remove = touch_remove, + .syncbox = touch_syncbox, + .asyncbox = touch_asyncbox, + .reset = touch_reset, + .suspend = touch_suspend, + .resume = touch_resume, + .early_suspend = touch_early_suspend, +}; + + int touch_module_init(void) +{ + return syna_tcm_add_module(&touch_module, true); +} +EXPORT_SYMBOL(touch_module_init); + void touch_module_exit(void) +{ + syna_tcm_add_module(&touch_module, false); + + wait_for_completion(&touch_remove_complete); +} +EXPORT_SYMBOL(touch_module_exit); + diff --git a/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm_zeroflash.c b/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm_zeroflash.c new file mode 100644 index 0000000000..5e8295477c --- /dev/null +++ b/qcom/opensource/touch-drivers/synaptics_tcm/synaptics_tcm_zeroflash.c @@ -0,0 +1,1012 @@ +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved. + * + * Copyright (C) 2017-2019 Scott Lin + * + * 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 FW_IMAGE_NAME "synaptics/hdl_firmware.img" + +#define BOOT_CONFIG_ID "BOOT_CONFIG" + +#define F35_APP_CODE_ID "F35_APP_CODE" + +#define APP_CONFIG_ID "APP_CONFIG" + +#define DISP_CONFIG_ID "DISPLAY" + +#define IMAGE_FILE_MAGIC_VALUE 0x4818472b + +#define FLASH_AREA_MAGIC_VALUE 0x7c05e516 + +#define PDT_START_ADDR 0x00e9 + +#define PDT_END_ADDR 0x00ee + +#define UBL_FN_NUMBER 0x35 + +#define F35_CTRL3_OFFSET 18 + +#define F35_CTRL7_OFFSET 22 + +#define F35_WRITE_FW_TO_PMEM_COMMAND 4 + +#define RESET_TO_HDL_DELAY_MS 12 + +#define DOWNLOAD_RETRY_COUNT 10 + +enum f35_error_code { + SUCCESS = 0, + UNKNOWN_FLASH_PRESENT, + MAGIC_NUMBER_NOT_PRESENT, + INVALID_BLOCK_NUMBER, + BLOCK_NOT_ERASED, + NO_FLASH_PRESENT, + CHECKSUM_FAILURE, + WRITE_FAILURE, + INVALID_COMMAND, + IN_DEBUG_MODE, + INVALID_HEADER, + REQUESTING_FIRMWARE, + INVALID_CONFIGURATION, + DISABLE_BLOCK_PROTECT_FAILURE, +}; + +enum config_download { + HDL_INVALID = 0, + HDL_TOUCH_CONFIG, + HDL_DISPLAY_CONFIG, + HDL_DISPLAY_CONFIG_TO_RAM, +}; + +struct area_descriptor { + unsigned char magic_value[4]; + unsigned char id_string[16]; + unsigned char flags[4]; + unsigned char flash_addr_words[4]; + unsigned char length[4]; + unsigned char checksum[4]; +}; + +struct block_data { + const unsigned char *data; + unsigned int size; + unsigned int flash_addr; +}; + +struct image_info { + unsigned int packrat_number; + struct block_data boot_config; + struct block_data app_firmware; + struct block_data app_config; + struct block_data disp_config; +}; + +struct image_header { + unsigned char magic_value[4]; + unsigned char num_of_areas[4]; +}; + +struct rmi_f35_query { + unsigned char version:4; + unsigned char has_debug_mode:1; + unsigned char has_data5:1; + unsigned char has_query1:1; + unsigned char has_query2:1; + unsigned char chunk_size; + unsigned char has_ctrl7:1; + unsigned char has_host_download:1; + unsigned char has_spi_master:1; + unsigned char advanced_recovery_mode:1; + unsigned char reserved:4; +} __packed; + +struct rmi_f35_data { + unsigned char error_code:5; + unsigned char recovery_mode_forced:1; + unsigned char nvm_programmed:1; + unsigned char in_recovery:1; +} __packed; + +struct rmi_pdt_entry { + unsigned char query_base_addr; + unsigned char command_base_addr; + unsigned char control_base_addr; + unsigned char data_base_addr; + unsigned char intr_src_count:3; + unsigned char reserved_1:2; + unsigned char fn_version:2; + unsigned char reserved_2:1; + unsigned char fn_number; +} __packed; + +struct rmi_addr { + unsigned short query_base; + unsigned short command_base; + unsigned short control_base; + unsigned short data_base; +}; + +struct firmware_status { + unsigned short invalid_static_config:1; + unsigned short need_disp_config:1; + unsigned short need_app_config:1; + unsigned short hdl_version:4; + unsigned short reserved:9; +} __packed; + +struct zeroflash_hcd { + bool has_hdl; + bool f35_ready; + const unsigned char *image; + unsigned char *buf; + const struct firmware *fw_entry; + struct work_struct config_work; + struct work_struct firmware_work; + struct workqueue_struct *workqueue; + struct rmi_addr f35_addr; + struct image_info image_info; + struct firmware_status fw_status; + struct syna_tcm_buffer out; + struct syna_tcm_buffer resp; + struct syna_tcm_hcd *tcm_hcd; +}; + +DECLARE_COMPLETION(zeroflash_remove_complete); + +static struct zeroflash_hcd *zeroflash_hcd; + +static int zeroflash_check_uboot(void) +{ + int retval; + unsigned char fn_number; + struct rmi_f35_query query; + struct rmi_pdt_entry p_entry; + struct syna_tcm_hcd *tcm_hcd = zeroflash_hcd->tcm_hcd; + + retval = syna_tcm_rmi_read(tcm_hcd, + PDT_END_ADDR, + &fn_number, + sizeof(fn_number)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read RMI function number\n"); + return retval; + } + + LOGD(tcm_hcd->pdev->dev.parent, + "Found F$%02x\n", + fn_number); + + if (fn_number != UBL_FN_NUMBER) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to find F$35\n"); + return -ENODEV; + } + + if (zeroflash_hcd->f35_ready) + return 0; + + retval = syna_tcm_rmi_read(tcm_hcd, + PDT_START_ADDR, + (unsigned char *)&p_entry, + sizeof(p_entry)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read PDT entry\n"); + return retval; + } + + zeroflash_hcd->f35_addr.query_base = p_entry.query_base_addr; + zeroflash_hcd->f35_addr.command_base = p_entry.command_base_addr; + zeroflash_hcd->f35_addr.control_base = p_entry.control_base_addr; + zeroflash_hcd->f35_addr.data_base = p_entry.data_base_addr; + + retval = syna_tcm_rmi_read(tcm_hcd, + zeroflash_hcd->f35_addr.query_base, + (unsigned char *)&query, + sizeof(query)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read F$35 query\n"); + return retval; + } + + zeroflash_hcd->f35_ready = true; + + if (query.has_query2 && query.has_ctrl7 && query.has_host_download) { + zeroflash_hcd->has_hdl = true; + } else { + LOGE(tcm_hcd->pdev->dev.parent, + "Host download not supported\n"); + zeroflash_hcd->has_hdl = false; + return -ENODEV; + } + + return 0; +} + +static int zeroflash_parse_fw_image(void) +{ + unsigned int idx; + unsigned int addr; + unsigned int offset; + unsigned int length; + unsigned int checksum; + unsigned int flash_addr; + unsigned int magic_value; + unsigned int num_of_areas; + struct image_header *header; + struct image_info *image_info; + struct area_descriptor *descriptor; + struct syna_tcm_hcd *tcm_hcd = zeroflash_hcd->tcm_hcd; + const unsigned char *image; + const unsigned char *content; + + image = zeroflash_hcd->image; + image_info = &zeroflash_hcd->image_info; + header = (struct image_header *)image; + + magic_value = le4_to_uint(header->magic_value); + if (magic_value != IMAGE_FILE_MAGIC_VALUE) { + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid image file magic value\n"); + return -EINVAL; + } + + memset(image_info, 0x00, sizeof(*image_info)); + + offset = sizeof(*header); + num_of_areas = le4_to_uint(header->num_of_areas); + + for (idx = 0; idx < num_of_areas; idx++) { + addr = le4_to_uint(image + offset); + descriptor = (struct area_descriptor *)(image + addr); + offset += 4; + + magic_value = le4_to_uint(descriptor->magic_value); + if (magic_value != FLASH_AREA_MAGIC_VALUE) + continue; + + length = le4_to_uint(descriptor->length); + content = (unsigned char *)descriptor + sizeof(*descriptor); + flash_addr = le4_to_uint(descriptor->flash_addr_words) * 2; + checksum = le4_to_uint(descriptor->checksum); + + if (!memcmp((char *)descriptor->id_string, + BOOT_CONFIG_ID, + strlen(BOOT_CONFIG_ID))) { + if (checksum != (crc32(~0, content, length) ^ ~0)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Boot config checksum error\n"); + return -EINVAL; + } + image_info->boot_config.size = length; + image_info->boot_config.data = content; + image_info->boot_config.flash_addr = flash_addr; + LOGD(tcm_hcd->pdev->dev.parent, + "Boot config size = %d\n", + length); + LOGD(tcm_hcd->pdev->dev.parent, + "Boot config flash address = 0x%08x\n", + flash_addr); + } else if (!memcmp((char *)descriptor->id_string, + F35_APP_CODE_ID, + strlen(F35_APP_CODE_ID))) { + if (checksum != (crc32(~0, content, length) ^ ~0)) { + LOGE(tcm_hcd->pdev->dev.parent, + "APP firmware checksum error\n"); + return -EINVAL; + } + image_info->app_firmware.size = length; + image_info->app_firmware.data = content; + image_info->app_firmware.flash_addr = flash_addr; + LOGD(tcm_hcd->pdev->dev.parent, + "Application firmware size = %d\n", + length); + LOGD(tcm_hcd->pdev->dev.parent, + "Application firmware flash address = 0x%08x\n", + flash_addr); + } else if (!memcmp((char *)descriptor->id_string, + APP_CONFIG_ID, + strlen(APP_CONFIG_ID))) { + if (checksum != (crc32(~0, content, length) ^ ~0)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Application config checksum error\n"); + return -EINVAL; + } + image_info->app_config.size = length; + image_info->app_config.data = content; + image_info->app_config.flash_addr = flash_addr; + image_info->packrat_number = le4_to_uint(&content[14]); + LOGD(tcm_hcd->pdev->dev.parent, + "Application config size = %d\n", + length); + LOGD(tcm_hcd->pdev->dev.parent, + "Application config flash address = 0x%08x\n", + flash_addr); + } else if (!memcmp((char *)descriptor->id_string, + DISP_CONFIG_ID, + strlen(DISP_CONFIG_ID))) { + if (checksum != (crc32(~0, content, length) ^ ~0)) { + LOGE(tcm_hcd->pdev->dev.parent, + "Display config checksum error\n"); + return -EINVAL; + } + image_info->disp_config.size = length; + image_info->disp_config.data = content; + image_info->disp_config.flash_addr = flash_addr; + LOGD(tcm_hcd->pdev->dev.parent, + "Display config size = %d\n", + length); + LOGD(tcm_hcd->pdev->dev.parent, + "Display config flash address = 0x%08x\n", + flash_addr); + } + } + + return 0; +} + +static int zeroflash_get_fw_image(void) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = zeroflash_hcd->tcm_hcd; + + if (zeroflash_hcd->fw_entry != NULL) + return 0; + + do { + retval = request_firmware(&zeroflash_hcd->fw_entry, + FW_IMAGE_NAME, + tcm_hcd->pdev->dev.parent); + if (retval < 0) { + LOGD(tcm_hcd->pdev->dev.parent, + "Failed to request %s\n", + FW_IMAGE_NAME); + msleep(100); + } else { + break; + } + } while (1); + + LOGD(tcm_hcd->pdev->dev.parent, + "Firmware image size = %d\n", + (unsigned int)zeroflash_hcd->fw_entry->size); + + zeroflash_hcd->image = zeroflash_hcd->fw_entry->data; + + retval = zeroflash_parse_fw_image(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to parse firmware image\n"); + release_firmware(zeroflash_hcd->fw_entry); + zeroflash_hcd->fw_entry = NULL; + zeroflash_hcd->image = NULL; + return retval; + } + + return 0; +} + +static void zeroflash_download_config(void) +{ + struct firmware_status *fw_status; + struct syna_tcm_hcd *tcm_hcd = zeroflash_hcd->tcm_hcd; + + fw_status = &zeroflash_hcd->fw_status; + + if (!fw_status->need_app_config && !fw_status->need_disp_config) { + if (atomic_read(&tcm_hcd->helper.task) == HELP_NONE) { + atomic_set(&tcm_hcd->helper.task, + HELP_SEND_RESET_NOTIFICATION); + queue_work(tcm_hcd->helper.workqueue, + &tcm_hcd->helper.work); + } + atomic_set(&tcm_hcd->host_downloading, 0); + return; + } + + queue_work(zeroflash_hcd->workqueue, &zeroflash_hcd->config_work); +} + +static void zeroflash_download_firmware(void) +{ + queue_work(zeroflash_hcd->workqueue, &zeroflash_hcd->firmware_work); +} + +static int zeroflash_download_disp_config(void) +{ + int retval; + unsigned char response_code; + struct image_info *image_info; + struct syna_tcm_hcd *tcm_hcd = zeroflash_hcd->tcm_hcd; + static unsigned int retry_count; + + LOGN(tcm_hcd->pdev->dev.parent, + "Downloading display config\n"); + + image_info = &zeroflash_hcd->image_info; + + if (image_info->disp_config.size == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "No display config in image file\n"); + return -EINVAL; + } + + LOCK_BUFFER(zeroflash_hcd->out); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &zeroflash_hcd->out, + image_info->disp_config.size + 2); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for out.buf\n"); + goto unlock_out; + } + + switch (zeroflash_hcd->fw_status.hdl_version) { + case 0: + zeroflash_hcd->out.buf[0] = 1; + break; + case 1: + zeroflash_hcd->out.buf[0] = 2; + break; + default: + retval = -EINVAL; + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid HDL version (%d)\n", + zeroflash_hcd->fw_status.hdl_version); + goto unlock_out; + } + + zeroflash_hcd->out.buf[1] = HDL_DISPLAY_CONFIG; + + retval = secure_memcpy(&zeroflash_hcd->out.buf[2], + zeroflash_hcd->out.buf_size - 2, + image_info->disp_config.data, + image_info->disp_config.size, + image_info->disp_config.size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy display config data\n"); + goto unlock_out; + } + + zeroflash_hcd->out.data_length = image_info->disp_config.size + 2; + + LOCK_BUFFER(zeroflash_hcd->resp); + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_DOWNLOAD_CONFIG, + zeroflash_hcd->out.buf, + zeroflash_hcd->out.data_length, + &zeroflash_hcd->resp.buf, + &zeroflash_hcd->resp.buf_size, + &zeroflash_hcd->resp.data_length, + &response_code, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_DOWNLOAD_CONFIG)); + if (response_code != STATUS_ERROR) + goto unlock_resp; + retry_count++; + if (DOWNLOAD_RETRY_COUNT && retry_count > DOWNLOAD_RETRY_COUNT) + goto unlock_resp; + } else { + retry_count = 0; + } + + retval = secure_memcpy((unsigned char *)&zeroflash_hcd->fw_status, + sizeof(zeroflash_hcd->fw_status), + zeroflash_hcd->resp.buf, + zeroflash_hcd->resp.buf_size, + sizeof(zeroflash_hcd->fw_status)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy firmware status\n"); + goto unlock_resp; + } + + LOGN(tcm_hcd->pdev->dev.parent, + "Display config downloaded\n"); + + retval = 0; + +unlock_resp: + UNLOCK_BUFFER(zeroflash_hcd->resp); + +unlock_out: + UNLOCK_BUFFER(zeroflash_hcd->out); + + return retval; +} + +static int zeroflash_download_app_config(void) +{ + int retval; + unsigned char padding; + unsigned char response_code; + struct image_info *image_info; + struct syna_tcm_hcd *tcm_hcd = zeroflash_hcd->tcm_hcd; + static unsigned int retry_count; + + LOGN(tcm_hcd->pdev->dev.parent, + "Downloading application config\n"); + + image_info = &zeroflash_hcd->image_info; + + if (image_info->app_config.size == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "No application config in image file\n"); + return -EINVAL; + } + + padding = image_info->app_config.size % 8; + if (padding) + padding = 8 - padding; + + LOCK_BUFFER(zeroflash_hcd->out); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &zeroflash_hcd->out, + image_info->app_config.size + 2 + padding); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for out.buf\n"); + goto unlock_out; + } + + switch (zeroflash_hcd->fw_status.hdl_version) { + case 0: + zeroflash_hcd->out.buf[0] = 1; + break; + case 1: + zeroflash_hcd->out.buf[0] = 2; + break; + default: + retval = -EINVAL; + LOGE(tcm_hcd->pdev->dev.parent, + "Invalid HDL version (%d)\n", + zeroflash_hcd->fw_status.hdl_version); + goto unlock_out; + } + + zeroflash_hcd->out.buf[1] = HDL_TOUCH_CONFIG; + + retval = secure_memcpy(&zeroflash_hcd->out.buf[2], + zeroflash_hcd->out.buf_size - 2, + image_info->app_config.data, + image_info->app_config.size, + image_info->app_config.size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy application config data\n"); + goto unlock_out; + } + + zeroflash_hcd->out.data_length = image_info->app_config.size + 2; + zeroflash_hcd->out.data_length += padding; + + LOCK_BUFFER(zeroflash_hcd->resp); + + retval = tcm_hcd->write_message(tcm_hcd, + CMD_DOWNLOAD_CONFIG, + zeroflash_hcd->out.buf, + zeroflash_hcd->out.data_length, + &zeroflash_hcd->resp.buf, + &zeroflash_hcd->resp.buf_size, + &zeroflash_hcd->resp.data_length, + &response_code, + 0); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write command %s\n", + STR(CMD_DOWNLOAD_CONFIG)); + if (response_code != STATUS_ERROR) + goto unlock_resp; + retry_count++; + if (DOWNLOAD_RETRY_COUNT && retry_count > DOWNLOAD_RETRY_COUNT) + goto unlock_resp; + } else { + retry_count = 0; + } + + retval = secure_memcpy((unsigned char *)&zeroflash_hcd->fw_status, + sizeof(zeroflash_hcd->fw_status), + zeroflash_hcd->resp.buf, + zeroflash_hcd->resp.buf_size, + sizeof(zeroflash_hcd->fw_status)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy firmware status\n"); + goto unlock_resp; + } + + LOGN(tcm_hcd->pdev->dev.parent, + "Application config downloaded\n"); + + retval = 0; + +unlock_resp: + UNLOCK_BUFFER(zeroflash_hcd->resp); + +unlock_out: + UNLOCK_BUFFER(zeroflash_hcd->out); + + return retval; +} + +static void zeroflash_download_config_work(struct work_struct *work) +{ + int retval; + struct syna_tcm_hcd *tcm_hcd = zeroflash_hcd->tcm_hcd; + + retval = zeroflash_get_fw_image(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get firmware image\n"); + return; + } + + LOGN(tcm_hcd->pdev->dev.parent, + "Start of config download\n"); + + if (zeroflash_hcd->fw_status.need_app_config) { + retval = zeroflash_download_app_config(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to download application config\n"); + return; + } + goto exit; + } + + if (zeroflash_hcd->fw_status.need_disp_config) { + retval = zeroflash_download_disp_config(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to download display config\n"); + return; + } + goto exit; + } + +exit: + LOGN(tcm_hcd->pdev->dev.parent, + "End of config download\n"); + + zeroflash_download_config(); +} + +static int zeroflash_download_app_fw(void) +{ + int retval; + unsigned char command; + struct image_info *image_info; + struct syna_tcm_hcd *tcm_hcd = zeroflash_hcd->tcm_hcd; +#if RESET_TO_HDL_DELAY_MS + const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; +#endif + + LOGN(tcm_hcd->pdev->dev.parent, + "Downloading application firmware\n"); + + image_info = &zeroflash_hcd->image_info; + + if (image_info->app_firmware.size == 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "No application firmware in image file\n"); + return -EINVAL; + } + + LOCK_BUFFER(zeroflash_hcd->out); + + retval = syna_tcm_alloc_mem(tcm_hcd, + &zeroflash_hcd->out, + image_info->app_firmware.size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for out.buf\n"); + UNLOCK_BUFFER(zeroflash_hcd->out); + return retval; + } + + retval = secure_memcpy(zeroflash_hcd->out.buf, + zeroflash_hcd->out.buf_size, + image_info->app_firmware.data, + image_info->app_firmware.size, + image_info->app_firmware.size); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy application firmware data\n"); + UNLOCK_BUFFER(zeroflash_hcd->out); + return retval; + } + + zeroflash_hcd->out.data_length = image_info->app_firmware.size; + + command = F35_WRITE_FW_TO_PMEM_COMMAND; + +#if RESET_TO_HDL_DELAY_MS + gpio_set_value(bdata->reset_gpio, bdata->reset_on_state); + msleep(bdata->reset_active_ms); + gpio_set_value(bdata->reset_gpio, !bdata->reset_on_state); + msleep(RESET_TO_HDL_DELAY_MS); +#endif + + retval = syna_tcm_rmi_write(tcm_hcd, + zeroflash_hcd->f35_addr.control_base + F35_CTRL3_OFFSET, + &command, + sizeof(command)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write F$35 command\n"); + UNLOCK_BUFFER(zeroflash_hcd->out); + return retval; + } + + retval = syna_tcm_rmi_write(tcm_hcd, + zeroflash_hcd->f35_addr.control_base + F35_CTRL7_OFFSET, + zeroflash_hcd->out.buf, + zeroflash_hcd->out.data_length); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to write application firmware data\n"); + UNLOCK_BUFFER(zeroflash_hcd->out); + return retval; + } + + UNLOCK_BUFFER(zeroflash_hcd->out); + + LOGN(tcm_hcd->pdev->dev.parent, + "Application firmware downloaded\n"); + + return 0; +} + +static void zeroflash_download_firmware_work(struct work_struct *work) +{ + int retval; + struct rmi_f35_data data; + struct syna_tcm_hcd *tcm_hcd = zeroflash_hcd->tcm_hcd; + static unsigned int retry_count; + + retval = zeroflash_check_uboot(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Microbootloader support unavailable\n"); + goto exit; + } + + atomic_set(&tcm_hcd->host_downloading, 1); + + retval = syna_tcm_rmi_read(tcm_hcd, + zeroflash_hcd->f35_addr.data_base, + (unsigned char *)&data, + sizeof(data)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to read F$35 data\n"); + goto exit; + } + + if (data.error_code != REQUESTING_FIRMWARE) { + LOGE(tcm_hcd->pdev->dev.parent, + "Microbootloader error code = 0x%02x\n", + data.error_code); + if (data.error_code != CHECKSUM_FAILURE) { + retval = -EIO; + goto exit; + } else { + retry_count++; + } + } else { + retry_count = 0; + } + + retval = zeroflash_get_fw_image(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to get firmware image\n"); + goto exit; + } + + LOGN(tcm_hcd->pdev->dev.parent, + "Start of firmware download\n"); + + retval = zeroflash_download_app_fw(); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to download application firmware\n"); + goto exit; + } + + LOGN(tcm_hcd->pdev->dev.parent, + "End of firmware download\n"); + +exit: + if (retval < 0) + retry_count++; + + if (DOWNLOAD_RETRY_COUNT && retry_count > DOWNLOAD_RETRY_COUNT) { + retval = tcm_hcd->enable_irq(tcm_hcd, false, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to disable interrupt\n"); + } + } else { + retval = tcm_hcd->enable_irq(tcm_hcd, true, NULL); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to enable interrupt\n"); + } + } +} + +static int zeroflash_init(struct syna_tcm_hcd *tcm_hcd) +{ + zeroflash_hcd = kzalloc(sizeof(*zeroflash_hcd), GFP_KERNEL); + if (!zeroflash_hcd) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to allocate memory for zeroflash_hcd\n"); + return -ENOMEM; + } + + zeroflash_hcd->tcm_hcd = tcm_hcd; + + INIT_BUFFER(zeroflash_hcd->out, false); + INIT_BUFFER(zeroflash_hcd->resp, false); + + zeroflash_hcd->workqueue = + create_singlethread_workqueue("syna_tcm_zeroflash"); + INIT_WORK(&zeroflash_hcd->config_work, + zeroflash_download_config_work); + INIT_WORK(&zeroflash_hcd->firmware_work, + zeroflash_download_firmware_work); + + if (tcm_hcd->init_okay == false && + tcm_hcd->hw_if->bus_io->type == BUS_SPI) + zeroflash_download_firmware(); + + return 0; +} + +static int zeroflash_remove(struct syna_tcm_hcd *tcm_hcd) +{ + if (!zeroflash_hcd) + goto exit; + + if (zeroflash_hcd->fw_entry) + release_firmware(zeroflash_hcd->fw_entry); + + cancel_work_sync(&zeroflash_hcd->config_work); + cancel_work_sync(&zeroflash_hcd->firmware_work); + flush_workqueue(zeroflash_hcd->workqueue); + destroy_workqueue(zeroflash_hcd->workqueue); + + RELEASE_BUFFER(zeroflash_hcd->resp); + RELEASE_BUFFER(zeroflash_hcd->out); + + kfree(zeroflash_hcd); + zeroflash_hcd = NULL; + +exit: + complete(&zeroflash_remove_complete); + + return 0; +} + +static int zeroflash_syncbox(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + unsigned char *fw_status; + + if (!zeroflash_hcd) + return 0; + + switch (tcm_hcd->report.id) { + case REPORT_STATUS: + fw_status = (unsigned char *)&zeroflash_hcd->fw_status; + retval = secure_memcpy(fw_status, + sizeof(zeroflash_hcd->fw_status), + tcm_hcd->report.buffer.buf, + tcm_hcd->report.buffer.buf_size, + sizeof(zeroflash_hcd->fw_status)); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to copy firmware status\n"); + return retval; + } + zeroflash_download_config(); + break; + case REPORT_HDL: + retval = tcm_hcd->enable_irq(tcm_hcd, false, true); + if (retval < 0) { + LOGE(tcm_hcd->pdev->dev.parent, + "Failed to disable interrupt\n"); + return retval; + } + zeroflash_download_firmware(); + break; + default: + break; + } + + return 0; +} + +static int zeroflash_reset(struct syna_tcm_hcd *tcm_hcd) +{ + int retval; + + if (!zeroflash_hcd) { + retval = zeroflash_init(tcm_hcd); + return retval; + } + + return 0; +} + +static struct syna_tcm_module_cb zeroflash_module = { + .type = TCM_ZEROFLASH, + .init = zeroflash_init, + .remove = zeroflash_remove, + .syncbox = zeroflash_syncbox, + .asyncbox = NULL, + .reset = zeroflash_reset, + .suspend = NULL, + .resume = NULL, + .early_suspend = NULL, +}; + +static int __init zeroflash_module_init(void) +{ + return syna_tcm_add_module(&zeroflash_module, true); +} + +static void __exit zeroflash_module_exit(void) +{ + syna_tcm_add_module(&zeroflash_module, false); + + wait_for_completion(&zeroflash_remove_complete); +} + +module_init(zeroflash_module_init); +module_exit(zeroflash_module_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics TCM Zeroflash Module"); +MODULE_LICENSE("GPL v2"); diff --git a/qcom/opensource/touch-drivers/target.bzl b/qcom/opensource/touch-drivers/target.bzl new file mode 100644 index 0000000000..a987074b3b --- /dev/null +++ b/qcom/opensource/touch-drivers/target.bzl @@ -0,0 +1,125 @@ +load(":touch_modules.bzl", "touch_driver_modules") +load(":touch_modules_build.bzl", "define_target_variant_modules") +load("//msm-kernel:target_variants.bzl", "get_all_la_variants", "get_all_le_variants", "get_all_lxc_variants") + +def define_pineapple(t,v): + define_target_variant_modules( + target = t, + variant = v, + registry = touch_driver_modules, + modules = [ + "nt36xxx-i2c", + "atmel_mxt_ts", + "dummy_ts", + "goodix_ts", + "focaltech_fts" + ], + config_options = [ + "TOUCH_DLKM_ENABLE", + "CONFIG_ARCH_PINEAPPLE", + "CONFIG_MSM_TOUCH", + "CONFIG_TOUCHSCREEN_GOODIX_BRL", + "CONFIG_TOUCHSCREEN_NT36XXX_I2C", + "CONFIG_TOUCHSCREEN_ATMEL_MXT", + "CONFIG_TOUCHSCREEN_DUMMY", + "CONFIG_TOUCH_FOCALTECH" + ], +) + +def define_blair(t,v): + define_target_variant_modules( + target = t, + variant = v, + registry = touch_driver_modules, + modules = [ + "nt36xxx-i2c", + "goodix_ts", + "focaltech_fts", + "synaptics_tcm_ts" + ], + config_options = [ + "TOUCH_DLKM_ENABLE", + "CONFIG_ARCH_BLAIR", + "CONFIG_MSM_TOUCH", + "CONFIG_TOUCHSCREEN_NT36XXX_I2C", + "CONFIG_TOUCHSCREEN_GOODIX_BRL", + "CONFIG_TOUCH_FOCALTECH", + "CONFIG_TOUCHSCREEN_SYNAPTICS_TCM" + ], +) + +def define_pitti(t,v): + define_target_variant_modules( + target = t, + variant = v, + registry = touch_driver_modules, + modules = [ + "focaltech_fts", + "goodix_ts" + ], + config_options = [ + "TOUCH_DLKM_ENABLE", + "CONFIG_ARCH_PITTI", + "CONFIG_MSM_TOUCH", + "CONFIG_TOUCH_FOCALTECH", + "CONFIG_TOUCHSCREEN_GOODIX_BRL" + ], +) + +def define_monaco(t,v): + define_target_variant_modules( + target = t, + variant = v, + registry = touch_driver_modules, + modules = [ + "glink_comm", + "pt_ts", + "pt_i2c", + "pt_device_access", + "pt_debug", + "raydium_ts", + ], + config_options = [ + "TOUCH_DLKM_ENABLE", + "CONFIG_ARCH_MONACO", + "CONFIG_MSM_TOUCH", + "CONFIG_TOUCHSCREEN_MSM_GLINK", + "CONFIG_TOUCHSCREEN_PARADE", + "CONFIG_TOUCHSCREEN_PARADE_DEVICETREE_SUPPORT", + "CONFIG_TOUCHSCREEN_PARADE_I2C", + "CONFIG_TOUCHSCREEN_PARADE_DEVICE_ACCESS", + "CONFIG_TOUCHSCREEN_PARADE_BUTTON", + "CONFIG_TOUCHSCREEN_PARADE_PROXIMITY", + "CONFIG_TOUCHSCREEN_PARADE_DEBUG_MDL", + "CONFIG_TOUCHSCREEN_RM_TS", + ], +) + +def define_volcano(t,v): + define_target_variant_modules( + target = t, + variant = v, + registry = touch_driver_modules, + modules = [ + "goodix_ts" + ], + config_options = [ + "TOUCH_DLKM_ENABLE", + "CONFIG_ARCH_VOLCANO", + "CONFIG_MSM_TOUCH", + "CONFIG_TOUCHSCREEN_GOODIX_BRL" + ], +) + +def define_touch_target(): + for (t, v) in get_all_la_variants() + get_all_le_variants() + get_all_lxc_variants(): + if t == "blair": + define_blair(t, v) + elif t == "pitti": + define_pitti(t, v) + elif t == "monaco": + define_monaco(t, v) + elif t == "volcano": + define_volcano(t, v) + else: + define_pineapple(t, v) diff --git a/qcom/opensource/touch-drivers/touch_driver_board.mk b/qcom/opensource/touch-drivers/touch_driver_board.mk new file mode 100644 index 0000000000..abbee8e91b --- /dev/null +++ b/qcom/opensource/touch-drivers/touch_driver_board.mk @@ -0,0 +1,54 @@ +TOUCH_DLKM_ENABLE := true +ifeq ($(TARGET_KERNEL_DLKM_DISABLE), true) + ifeq ($(TARGET_KERNEL_DLKM_TOUCH_OVERRIDE), false) + TOUCH_DLKM_ENABLE := false + endif +endif + +ifeq ($(TOUCH_DLKM_ENABLE), true) + ifneq ($(TARGET_BOARD_AUTO),true) + ifeq ($(call is-board-platform-in-list,$(TARGET_BOARD_PLATFORM)),true) + ifeq ($(TARGET_BOARD_PLATFORM), monaco) + BOARD_VENDOR_KERNEL_MODULES += $(KERNEL_MODULES_OUT)/pt_ts.ko \ + $(KERNEL_MODULES_OUT)/pt_i2c.ko \ + $(KERNEL_MODULES_OUT)/pt_device_access.ko \ + $(KERNEL_MODULES_OUT)/glink_comm.ko \ + $(KERNEL_MODULES_OUT)/raydium_ts.ko + else ifeq ($(TARGET_BOARD_PLATFORM), kona) + BOARD_VENDOR_KERNEL_MODULES += $(KERNEL_MODULES_OUT)/focaltech_fts.ko + else ifeq ($(TARGET_BOARD_PLATFORM), pineapple) + BOARD_VENDOR_KERNEL_MODULES += $(KERNEL_MODULES_OUT)/nt36xxx-i2c.ko \ + $(KERNEL_MODULES_OUT)/goodix_ts.ko \ + $(KERNEL_MODULES_OUT)/atmel_mxt_ts.ko + else ifeq ($(TARGET_BOARD_PLATFORM), kalama) + BOARD_VENDOR_KERNEL_MODULES += $(KERNEL_MODULES_OUT)/nt36xxx-i2c.ko \ + $(KERNEL_MODULES_OUT)/goodix_ts.ko \ + $(KERNEL_MODULES_OUT)/atmel_mxt_ts.ko + else ifeq ($(TARGET_BOARD_PLATFORM), blair) + BOARD_VENDOR_KERNEL_MODULES += $(KERNEL_MODULES_OUT)/focaltech_fts.ko \ + $(KERNEL_MODULES_OUT)/nt36xxx-i2c.ko \ + $(KERNEL_MODULES_OUT)/synaptics_tcm_ts.ko \ + $(KERNEL_MODULES_OUT)/goodix_ts.ko + else ifeq ($(TARGET_BOARD_PLATFORM), crow) + BOARD_VENDOR_KERNEL_MODULES += $(KERNEL_MODULES_OUT)/goodix_ts.ko + else ifeq ($(TARGET_BOARD_PLATFORM), bengal) + BOARD_VENDOR_KERNEL_MODULES += $(KERNEL_MODULES_OUT)/synaptics_tcm_ts.ko \ + $(KERNEL_MODULES_OUT)/nt36xxx-i2c.ko \ + $(KERNEL_MODULES_OUT)/focaltech_fts.ko + else ifeq ($(TARGET_BOARD_PLATFORM), trinket) + BOARD_VENDOR_KERNEL_MODULES += $(KERNEL_MODULES_OUT)/synaptics_tcm_ts.ko + else ifeq ($(TARGET_BOARD_PLATFORM), pitti) + BOARD_VENDOR_KERNEL_MODULES += $(KERNEL_MODULES_OUT)/focaltech_fts.ko \ + $(KERNEL_MODULES_OUT)/goodix_ts.ko + else ifeq ($(TARGET_BOARD_PLATFORM), volcano) + BOARD_VENDOR_KERNEL_MODULES += $(KERNEL_MODULES_OUT)/goodix_ts.ko \ + $(KERNEL_MODULES_OUT)/focaltech_fts.ko + else + BOARD_VENDOR_KERNEL_MODULES += $(KERNEL_MODULES_OUT)/nt36xxx-i2c.ko \ + $(KERNEL_MODULES_OUT)/goodix_ts.ko \ + $(KERNEL_MODULES_OUT)/atmel_mxt_ts.ko \ + $(KERNEL_MODULES_OUT)/synaptics_tcm_ts.ko + endif + endif + endif +endif diff --git a/qcom/opensource/touch-drivers/touch_driver_product.mk b/qcom/opensource/touch-drivers/touch_driver_product.mk new file mode 100644 index 0000000000..54f47cae76 --- /dev/null +++ b/qcom/opensource/touch-drivers/touch_driver_product.mk @@ -0,0 +1,50 @@ +TOUCH_DLKM_ENABLE := true +ifeq ($(TARGET_KERNEL_DLKM_DISABLE), true) + ifeq ($(TARGET_KERNEL_DLKM_TOUCH_OVERRIDE), false) + TOUCH_DLKM_ENABLE := false + endif +endif + +ifeq ($(TOUCH_DLKM_ENABLE), true) + ifeq ($(TARGET_BOARD_PLATFORM), monaco) + PRODUCT_PACKAGES += $(KERNEL_MODULES_OUT)/pt_ts.ko \ + $(KERNEL_MODULES_OUT)/pt_i2c.ko \ + $(KERNEL_MODULES_OUT)/pt_device_access.ko \ + $(KERNEL_MODULES_OUT)/glink_comm.ko \ + $(KERNEL_MODULES_OUT)/raydium_ts.ko + else ifeq ($(TARGET_BOARD_PLATFORM), kona) + PRODUCT_PACKAGES += $(KERNEL_MODULES_OUT)/focaltech_fts.ko + else ifeq ($(TARGET_BOARD_PLATFORM), pineapple) + PRODUCT_PACKAGES += $(KERNEL_MODULES_OUT)/nt36xxx-i2c.ko \ + $(KERNEL_MODULES_OUT)/goodix_ts.ko \ + $(KERNEL_MODULES_OUT)/atmel_mxt_ts.ko + else ifeq ($(TARGET_BOARD_PLATFORM), kalama) + PRODUCT_PACKAGES += $(KERNEL_MODULES_OUT)/nt36xxx-i2c.ko \ + $(KERNEL_MODULES_OUT)/goodix_ts.ko \ + $(KERNEL_MODULES_OUT)/atmel_mxt_ts.ko + else ifeq ($(TARGET_BOARD_PLATFORM), blair) + PRODUCT_PACKAGES += $(KERNEL_MODULES_OUT)/focaltech_fts.ko \ + $(KERNEL_MODULES_OUT)/nt36xxx-i2c.ko \ + $(KERNEL_MODULES_OUT)/synaptics_tcm_ts.ko \ + $(KERNEL_MODULES_OUT)/goodix_ts.ko + else ifeq ($(TARGET_BOARD_PLATFORM), crow) + PRODUCT_PACKAGES += $(KERNEL_MODULES_OUT)/goodix_ts.ko + else ifeq ($(TARGET_BOARD_PLATFORM), bengal) + PRODUCT_PACKAGES += $(KERNEL_MODULES_OUT)/synaptics_tcm_ts.ko \ + $(KERNEL_MODULES_OUT)/nt36xxx-i2c.ko \ + $(KERNEL_MODULES_OUT)/focaltech_fts.ko + else ifeq ($(TARGET_BOARD_PLATFORM), trinket) + PRODUCT_PACKAGES += $(KERNEL_MODULES_OUT)/synaptics_tcm_ts.ko + else ifeq ($(TARGET_BOARD_PLATFORM), pitti) + PRODUCT_PACKAGES += $(KERNEL_MODULES_OUT)/focaltech_fts.ko \ + $(KERNEL_MODULES_OUT)/goodix_ts.ko + else ifeq ($(TARGET_BOARD_PLATFORM), volcano) + PRODUCT_PACKAGES += $(KERNEL_MODULES_OUT)/goodix_ts.ko \ + $(KERNEL_MODULES_OUT)/focaltech_fts.ko + else + PRODUCT_PACKAGES += $(KERNEL_MODULES_OUT)/nt36xxx-i2c.ko \ + $(KERNEL_MODULES_OUT)/goodix_ts.ko \ + $(KERNEL_MODULES_OUT)/atmel_mxt_ts.ko \ + $(KERNEL_MODULES_OUT)/synaptics_tcm_ts.ko + endif +endif diff --git a/qcom/opensource/touch-drivers/touch_modules.bzl b/qcom/opensource/touch-drivers/touch_modules.bzl new file mode 100644 index 0000000000..d0dcabbad5 --- /dev/null +++ b/qcom/opensource/touch-drivers/touch_modules.bzl @@ -0,0 +1,155 @@ +# Importing to touch module entry api from touch_modules_build.bzl to define module entried for touch drivers +load(":touch_modules_build.bzl", "touch_module_entry") + +# Importing the touch driver headers defined in BUILD.bazel +touch_driver_modules = touch_module_entry([":touch_drivers_headers"]) + +#Including the headers in the modules to be declared +module_entry = touch_driver_modules.register + +#--------------- TOUCH-DRIVERS MODULES ------------------ + +#define ddk_module() for goodix_ts +module_entry( + name = "goodix_ts", + config_option = "CONFIG_TOUCHSCREEN_GOODIX_BRL", + srcs = [ + "goodix_berlin_driver/goodix_brl_fwupdate.c", + "goodix_berlin_driver/goodix_brl_hw.c", + "goodix_berlin_driver/goodix_brl_i2c.c", + "goodix_berlin_driver/goodix_brl_spi.c", + "goodix_berlin_driver/goodix_cfg_bin.c", + "goodix_berlin_driver/goodix_ts_core.c", + "goodix_berlin_driver/goodix_ts_gesture.c", + "goodix_berlin_driver/goodix_ts_inspect.c", + "goodix_berlin_driver/goodix_ts_tools.c", + "goodix_berlin_driver/goodix_ts_utils.c", + "qts/qts_core.c" + ] +) + +#define ddk_module() for nt36xxx +module_entry( + name = "nt36xxx-i2c", + config_option = "CONFIG_TOUCHSCREEN_NT36XXX_I2C", + srcs = [ + "nt36xxx/nt36xxx_ext_proc.c", + "nt36xxx/nt36xxx_fw_update.c", + "nt36xxx/nt36xxx_mp_ctrlram.c", + "nt36xxx/nt36xxx.c" + ] +) + +#define ddk_module() for focaltech_fts +module_entry( + name = "focaltech_fts", + config_option = "CONFIG_TOUCH_FOCALTECH", + srcs = [ + "focaltech_touch/focaltech_core.c", + "focaltech_touch/focaltech_esdcheck.c", + "focaltech_touch/focaltech_ex_fun.c", + "focaltech_touch/focaltech_ex_mode.c", + "focaltech_touch/focaltech_flash/focaltech_upgrade_ft3518.c", + "focaltech_touch/focaltech_flash.c", + "focaltech_touch/focaltech_gesture.c", + "focaltech_touch/focaltech_i2c.c", + "focaltech_touch/focaltech_point_report_check.c" + ] +) + +#define ddk_module() for synaptics_tcm_ts +module_entry( + name = "synaptics_tcm_ts", + config_option = "CONFIG_TOUCHSCREEN_SYNAPTICS_TCM", + srcs = [ + "synaptics_tcm/synaptics_tcm_core.c", + "synaptics_tcm/synaptics_tcm_i2c.c", + "synaptics_tcm/synaptics_tcm_touch.c" + ] +) + +#define ddk_module() for atmel_mxt_ts +module_entry( + name = "atmel_mxt_ts", + config_option = "CONFIG_TOUCHSCREEN_ATMEL_MXT", + srcs = [ + "atmel_mxt/atmel_mxt_ts.c", + ] +) + +#define ddk_module() for dummy_ts +module_entry( + name = "dummy_ts", + config_option = "CONFIG_TOUCHSCREEN_DUMMY", + srcs = [ + "dummy_touch/dummy_touch.c" + ] +) + +#define ddk_module() for glink_interface_ts +module_entry( + name = "glink_comm", + config_option = "CONFIG_TOUCHSCREEN_MSM_GLINK", + srcs = [ + "glink_interface_ts/glink_interface.c" + ] +) + +#define ddk_module() for pt_ts +module_entry( + name = "pt_ts", + config_option = "CONFIG_TOUCHSCREEN_PARADE", + srcs = [ + "pt/pt_core.c", + "pt/pt_devtree.c", + "pt/pt_mt_common.c", + "pt/pt_platform.c", + "pt/pt_btn.c", + "pt/pt_mtb.c", + "pt/pt_proximity.c" + ] +) + +#define ddk_module() for pt_i2c +module_entry( + name = "pt_i2c", + config_option = "CONFIG_TOUCHSCREEN_PARADE_I2C", + srcs = [ + "pt/pt_i2c.c" + ] +) + +#define ddk_module() for pt_device_access +module_entry( + name = "pt_device_access", + config_option = "CONFIG_TOUCHSCREEN_PARADE_DEVICE_ACCESS", + srcs = [ + "pt/pt_device_access.c" + ] +) + +#define ddk_module() for pt_debug +module_entry( + name = "pt_debug", + config_option = "CONFIG_TOUCHSCREEN_PARADE_DEBUG_MDL", + srcs = [ + "pt/pt_debug.c" + ] +) + +#define ddk_module() for raydium_ts +module_entry( + name = "raydium_ts", + config_option = "CONFIG_TOUCHSCREEN_RM_TS", + srcs = [ + "raydium/drv_interface.c", + "raydium/raydium_driver.c", + "raydium/raydium_fw_update.c", + "raydium/raydium_selftest.c", + "raydium/raydium_sysfs.c", + "raydium/chip_raydium/f303_ic_control.c", + "raydium/chip_raydium/f303_ic_test.c", + "raydium/chip_raydium/ic_drv_global.c", + "raydium/chip_raydium/ic_drv_interface.c" + ] +) diff --git a/qcom/opensource/touch-drivers/touch_modules_build.bzl b/qcom/opensource/touch-drivers/touch_modules_build.bzl new file mode 100644 index 0000000000..02ee79769d --- /dev/null +++ b/qcom/opensource/touch-drivers/touch_modules_build.bzl @@ -0,0 +1,86 @@ +load("//build/kernel/kleaf:kernel.bzl", "ddk_module","ddk_submodule") +load("//build/bazel_common_rules/dist:dist.bzl", "copy_to_dist_dir") + +def _register_module_to_map(module_map, name, path, config_option, srcs): + module = struct( + name = name, + path = path, + srcs = srcs, + config_option = config_option + ) + + module_map[name] = module + +def _get_config_choices(map, options): + choices = [] + for option in map: + choices.extend(map[option].get(option in options,[])) + return choices + +def _get_kernel_build_options(modules, config_options): + all_options = {option: True for option in config_options} + all_options = all_options | {module.config_option: True for module in modules if module.config_option} + return all_options + +def _get_kernel_build_module_srcs(module, options, formatter): + srcs = module.srcs + print("-",module.name,",",module.config_option,",srcs =",srcs) + module_path = "{}/".format(module.path) if module.path else "" + return ["{}{}".format(module_path, formatter(src)) for src in srcs] + +def touch_module_entry(hdrs = []): + module_map = {} + + def register(name, path = None, config_option = [], srcs = [], config_srcs = None, deps = None): + _register_module_to_map(module_map, name, path, config_option, srcs) + return struct( + register = register, + get = module_map.get, + hdrs = hdrs + ) + +def define_target_variant_modules(target, variant, registry, modules, config_options = []): + kernel_build = "{}_{}".format(target, variant) + kernel_build_label = "//msm-kernel:{}".format(kernel_build) + modules = [registry.get(module_name) for module_name in modules] + options = _get_kernel_build_options(modules, config_options) + build_print = lambda message : print("{}: {}".format(kernel_build, message)) + formatter = lambda s : s.replace("%b", kernel_build).replace("%t", target) + headers = ["//msm-kernel:all_headers"] + registry.hdrs + all_module_rules = [] + + for module in modules: + rule_name = "{}_{}".format(kernel_build, module.name) + module_srcs = _get_kernel_build_module_srcs(module, options, formatter) + + if not module_srcs: + continue + + ddk_submodule( + name = rule_name, + srcs = module_srcs, + out = "{}.ko".format(module.name), + deps = headers, + local_defines = options.keys(), + ) + all_module_rules.append(rule_name) + + ddk_module( + name = "{}_touch_drivers".format(kernel_build), + kernel_build = kernel_build_label, + deps = all_module_rules, + ) + copy_to_dist_dir( + name = "{}_touch_drivers_dist".format(kernel_build), + data = [":{}_touch_drivers".format(kernel_build)], + dist_dir = "out/target/product/{}/dlkm/lib/modules".format(target), + flat = True, + wipe_dist_dir = False, + allow_duplicate_filenames = False, + mode_overrides = {"**/*": "644"}, + log = "info", + ) + +def define_consolidate_gki_modules(target, registry, modules, config_options = []): + define_target_variant_modules(target, "gki", registry, modules, config_options) + define_target_variant_modules(target, "consolidate", registry, modules, config_options)