From b709f4d97bb33d74ff5a14f8ff8c617b5f605c67 Mon Sep 17 00:00:00 2001 From: davidycchen Date: Tue, 17 Aug 2021 14:00:30 +0800 Subject: synaptics: add initial driver Initial driver provided by Synaptics. Bug: 198228556 Signed-off-by: davidycchen Change-Id: I471eda97a2b2e06a4dd9328e69c71d5f8e8fdc93 --- Kbuild | 14 + Kconfig | 60 + Makefile | 26 + syna_tcm2.c | 1745 +++++++++++++++++++++++ syna_tcm2.h | 416 ++++++ syna_tcm2_platform.h | 306 +++++ syna_tcm2_platform_i2c.c | 898 ++++++++++++ syna_tcm2_platform_spi.c | 1058 ++++++++++++++ syna_tcm2_runtime.h | 753 ++++++++++ syna_tcm2_sysfs.c | 2207 ++++++++++++++++++++++++++++++ syna_tcm2_testing.c | 660 +++++++++ syna_tcm2_testing.h | 66 + syna_tcm2_testing_limits.h | 234 ++++ tcm/synaptics_touchcom_core_dev.h | 1096 +++++++++++++++ tcm/synaptics_touchcom_core_v1.c | 1078 +++++++++++++++ tcm/synaptics_touchcom_core_v2.c | 1445 +++++++++++++++++++ tcm/synaptics_touchcom_func_base.c | 1678 +++++++++++++++++++++++ tcm/synaptics_touchcom_func_base.h | 418 ++++++ tcm/synaptics_touchcom_func_base_flash.h | 569 ++++++++ tcm/synaptics_touchcom_func_reflash.c | 2074 ++++++++++++++++++++++++++++ tcm/synaptics_touchcom_func_reflash.h | 145 ++ tcm/synaptics_touchcom_func_romboot.c | 1586 +++++++++++++++++++++ tcm/synaptics_touchcom_func_romboot.h | 155 +++ tcm/synaptics_touchcom_func_touch.c | 877 ++++++++++++ tcm/synaptics_touchcom_func_touch.h | 235 ++++ 25 files changed, 19799 insertions(+) create mode 100644 Kbuild create mode 100644 Kconfig create mode 100644 Makefile create mode 100644 syna_tcm2.c create mode 100644 syna_tcm2.h create mode 100644 syna_tcm2_platform.h create mode 100644 syna_tcm2_platform_i2c.c create mode 100644 syna_tcm2_platform_spi.c create mode 100644 syna_tcm2_runtime.h create mode 100644 syna_tcm2_sysfs.c create mode 100644 syna_tcm2_testing.c create mode 100644 syna_tcm2_testing.h create mode 100644 syna_tcm2_testing_limits.h create mode 100644 tcm/synaptics_touchcom_core_dev.h create mode 100644 tcm/synaptics_touchcom_core_v1.c create mode 100644 tcm/synaptics_touchcom_core_v2.c create mode 100644 tcm/synaptics_touchcom_func_base.c create mode 100644 tcm/synaptics_touchcom_func_base.h create mode 100644 tcm/synaptics_touchcom_func_base_flash.h create mode 100644 tcm/synaptics_touchcom_func_reflash.c create mode 100644 tcm/synaptics_touchcom_func_reflash.h create mode 100644 tcm/synaptics_touchcom_func_romboot.c create mode 100644 tcm/synaptics_touchcom_func_romboot.h create mode 100644 tcm/synaptics_touchcom_func_touch.c create mode 100644 tcm/synaptics_touchcom_func_touch.h diff --git a/Kbuild b/Kbuild new file mode 100644 index 0000000..93b07c0 --- /dev/null +++ b/Kbuild @@ -0,0 +1,14 @@ +TCM_CORE=tcm/ + +obj-$(CONFIG_TOUCHSCREEN_SYNA_TCM2) = syna_touch.o +syna_touch-objs += \ + syna_tcm2.o \ + $(TCM_CORE)synaptics_touchcom_core_v1.o \ + $(TCM_CORE)synaptics_touchcom_core_v2.o \ + $(TCM_CORE)synaptics_touchcom_func_base.o \ + $(TCM_CORE)synaptics_touchcom_func_touch.o \ + $(TCM_CORE)synaptics_touchcom_func_reflash.o \ + $(TCM_CORE)synaptics_touchcom_func_romboot.o \ + syna_tcm2_platform_spi.o \ + syna_tcm2_sysfs.o \ + syna_tcm2_testing.o diff --git a/Kconfig b/Kconfig new file mode 100644 index 0000000..9eb1bd1 --- /dev/null +++ b/Kconfig @@ -0,0 +1,60 @@ +# +# Synaptics TCM touchscreen driver configuration +# +menuconfig TOUCHSCREEN_SYNA_TCM2 + tristate "Synaptics TCM touchscreen" + default y + help + Say Y here if you have a Synaptics TCM touchscreen connected + to your system. + + If unsure, say N. + + To compile this driver as a module, choose M here. + +if TOUCHSCREEN_SYNA_TCM2 + +choice + default TOUCHSCREEN_SYNA_TCM2_I2C + prompt "Synaptics TCM bus module" +config TOUCHSCREEN_SYNA_TCM2_I2C + bool "I2C" + depends on I2C +config TOUCHSCREEN_SYNA_TCM2_SPI + bool "SPI" + depends on SPI_MASTER +endchoice + +config TOUCHSCREEN_SYNA_TCM2_SYSFS + bool "Synaptics TCM device sysfs module" + depends on TOUCHSCREEN_SYNA_TCM2 + help + Say Y here to enable support for the sysfs interface. + + If unsure, say N. + +config TOUCHSCREEN_SYNA_TCM2_REFLASH + bool "Synaptics TCM reflash module" + depends on TOUCHSCREEN_SYNA_TCM2 + help + Say Y here to enable support for the reflashing. + + If unsure, say N. + +config TOUCHSCREEN_SYNA_TCM2_TESTING + bool "Synaptics TCM testing module" + depends on TOUCHSCREEN_SYNA_TCM2 + help + Say Y here to enable support for the production test. + + If unsure, say N. + +config TOUCHSCREEN_SYNA_TCM2_ROMBOOT + bool "Synaptics TCM rom-bootloader module" + depends on TOUCHSCREEN_SYNA_TCM2 + help + Say Y here to enable support for the rom-boot access. + + If unsure, say N. + +endif diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0ebd555 --- /dev/null +++ b/Makefile @@ -0,0 +1,26 @@ +KERNEL_SRC ?= /lib/modules/$(shell uname -r)/build +M ?= $(shell pwd) + +KBUILD_OPTIONS += CONFIG_TOUCHSCREEN_SYNA_TCM2=m +# CONFIG_TOUCHSCREEN_SYNA_TCM2_TESTING is not set +# CONFIG_TOUCHSCREEN_SYNA_TCM2_ROMBOOT is not set +EXTRA_CFLAGS += -DDYNAMIC_DEBUG_MODULE +EXTRA_CFLAGS += -DCONFIG_TOUCHSCREEN_TBN +EXTRA_CFLAGS += -DCONFIG_TOUCHSCREEN_HEATMAP +EXTRA_CFLAGS += -DCONFIG_TOUCHSCREEN_OFFLOAD +EXTRA_CFLAGS += -DCONFIG_TOUCHSCREEN_SYNA_TCM2_REFLASH +EXTRA_CFLAGS += -DCONFIG_TOUCHSCREEN_SYNA_TCM2_SYSFS +EXTRA_CFLAGS += -DCONFIG_TOUCHSCREEN_SYNA_TCM2_TESTING +EXTRA_CFLAGS += -I$(KERNEL_SRC)/../google-modules/display +EXTRA_CFLAGS += -I$(KERNEL_SRC)/../google-modules/touch/common +EXTRA_CFLAGS += -I$(KERNEL_SRC)/../google-modules/touch/common/include +EXTRA_CFLAGS += -I$(KERNEL_SRC)/../google-modules/touch/synaptics +EXTRA_CFLAGS += -I$(KERNEL_SRC)/../google-modules/touch/synaptics/tcm +EXTRA_SYMBOLS += $(OUT_DIR)/../google-modules/touch/common/Module.symvers + +modules modules_install clean: + $(MAKE) -C $(KERNEL_SRC) M=$(M) \ + $(KBUILD_OPTIONS) \ + EXTRA_CFLAGS="$(EXTRA_CFLAGS)" \ + KBUILD_EXTRA_SYMBOLS="$(EXTRA_SYMBOLS)" \ + $(@) diff --git a/syna_tcm2.c b/syna_tcm2.c new file mode 100644 index 0000000..4527a0a --- /dev/null +++ b/syna_tcm2.c @@ -0,0 +1,1745 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Synaptics TouchCom touchscreen driver + * + * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +/** + * @file syna_tcm2.c + * + * This file implements the Synaptics device driver running under Linux kernel + * input device subsystem, and also communicate with Synaptics touch controller + * through TouchComm command-response protocol. + */ + +#include "syna_tcm2.h" +#include "syna_tcm2_platform.h" +#include "synaptics_touchcom_core_dev.h" +#include "synaptics_touchcom_func_base.h" +#include "synaptics_touchcom_func_touch.h" +#ifdef STARTUP_REFLASH +#include "synaptics_touchcom_func_reflash.h" +#endif +#ifdef MULTICHIP_DUT_REFLASH +#include "synaptics_touchcom_func_romboot.h" +#endif + +/** + * @section: USE_CUSTOM_TOUCH_REPORT_CONFIG + * Open if willing to set up the format of touch report. + * The custom_touch_format[] array can be used to describe the + * customized report format. + */ +#ifdef USE_CUSTOM_TOUCH_REPORT_CONFIG +static unsigned char custom_touch_format[] = { + /* entity code */ /* bits */ +#ifdef ENABLE_WAKEUP_GESTURE + TOUCH_REPORT_GESTURE_ID, 8, +#endif + TOUCH_REPORT_NUM_OF_ACTIVE_OBJECTS, 8, + TOUCH_REPORT_FOREACH_ACTIVE_OBJECT, + TOUCH_REPORT_OBJECT_N_INDEX, 8, + TOUCH_REPORT_OBJECT_N_CLASSIFICATION, 8, + TOUCH_REPORT_OBJECT_N_X_POSITION, 16, + TOUCH_REPORT_OBJECT_N_Y_POSITION, 16, + TOUCH_REPORT_FOREACH_END, + TOUCH_REPORT_END +}; +#endif + +/** + * @section: STARTUP_REFLASH_DELAY_TIME_MS + * The delayed time to start fw update during the startup time. + * This configuration depends on STARTUP_REFLASH. + */ +#ifdef STARTUP_REFLASH +#define STARTUP_REFLASH_DELAY_TIME_MS (200) + +#define FW_IMAGE_NAME "synaptics/firmware.img" +#endif + +/** + * @section: RESET_ON_RESUME_DELAY_MS + * The delayed time to issue a reset on resume state. + * This configuration depends on RESET_ON_RESUME. + */ +#ifdef RESET_ON_RESUME +#define RESET_ON_RESUME_DELAY_MS (100) +#endif + + +/** + * @section: POWER_ALIVE_AT_SUSPEND + * indicate that the power is still alive even at + * system suspend. + * otherwise, there is no power supplied when system + * is going to suspend stage. + */ +#define POWER_ALIVE_AT_SUSPEND + +/** + * @section: global variables for an active drm panel + * in order to register display notifier + */ +#ifdef USE_DRM_PANEL_NOTIFIER + struct drm_panel *active_panel; +#endif + + + +/** + * syna_dev_enable_lowpwr_gesture() + * + * Enable or disable the low power gesture mode. + * Furthermore, set up the wake-up irq. + * + * @param + * [ in] tcm: tcm driver handle + * [ in] en: '1' to enable low power gesture mode; '0' to disable + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_dev_enable_lowpwr_gesture(struct syna_tcm *tcm, bool en) +{ + int retval = 0; + struct syna_hw_attn_data *attn = &tcm->hw_if->bdata_attn; + + if (!tcm->lpwg_enabled) + return 0; + + if (attn->irq_id == 0) + return 0; + + if (en) { + if (!tcm->irq_wake) { + enable_irq_wake(attn->irq_id); + tcm->irq_wake = true; + } + + /* enable wakeup gesture mode + * + * the wakeup gesture control may result from by finger event; + * therefore, it's better to use ATTN driven mode here + */ + retval = syna_tcm_set_dynamic_config(tcm->tcm_dev, + DC_ENABLE_WAKEUP_GESTURE_MODE, + 1, + RESP_IN_ATTN); + if (retval < 0) { + LOGE("Fail to enable wakeup gesture via DC command\n"); + return retval; + } + } else { + if (tcm->irq_wake) { + disable_irq_wake(attn->irq_id); + tcm->irq_wake = false; + } + + /* disable wakeup gesture mode + * + * the wakeup gesture control may result from by finger event; + * therefore, it's better to use ATTN driven mode here + */ + retval = syna_tcm_set_dynamic_config(tcm->tcm_dev, + DC_ENABLE_WAKEUP_GESTURE_MODE, + 0, + RESP_IN_ATTN); + if (retval < 0) { + LOGE("Fail to disable wakeup gesture via DC command\n"); + return retval; + } + } + + return retval; +} + +#ifdef ENABLE_CUSTOM_TOUCH_ENTITY +/** + * @section: Callback function used for custom entity parsing in touch report + * + * Allow to parse the custom "newly" entity in the touch report. + * Please note that this function will be invoked in ISR + * + * @param + * [ in] code: the code of current touch entity + * [ in] config: the report configuration stored + * [in/out] config_offset: offset of current position in report config, + * and then return the updated position. + * [ in] report: touch report given + * [in/out] report_offset: offset of current position in touch report, + * the updated position should be returned. + * [ in] report_size: size of given touch report + * [ in] callback_data: pointer to caller data passed to callback function + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_dev_parse_custom_touch_report(const unsigned char code, + const unsigned char *config, unsigned int *config_offset, + const unsigned char *report, unsigned int *report_offset, + unsigned int report_size, void *callback_data) +{ + /** + * sample code to demonstrate how to parse the custom entity + * from the report, additional modifications must be beeded. + * + * struct syna_tcm *tcm = (struct syna_tcm *)callback_data; + * unsigned int data; + * unsigned int bits; + * + * switch (code) { + * case CUSTOM_ENTITY_CODE: + * bits = config[(*config_offset)++]; + * syna_tcm_get_touch_data(report, report_size, + * *report_offset, bits, &data); + * + * *report_offset += bits; + * return bits; + * default: + * LOGW("Unknown touch config code (idx:%d 0x%02x)\n", + * *config_offset, code); + * return (-EINVAL); + * } + * + */ + + return (-EINVAL); +} +#endif + +/** + * syna_tcm_free_input_events() + * + * Clear all relevant touched events. + * + * @param + * [ in] tcm: the driver handle + * + * @return + * none. + */ +static void syna_dev_free_input_events(struct syna_tcm *tcm) +{ + struct input_dev *input_dev = tcm->input_dev; +#ifdef TYPE_B_PROTOCOL + unsigned int idx; +#endif + + if (input_dev == NULL) + return; + + syna_pal_mutex_lock(&tcm->tp_event_mutex); + +#ifdef TYPE_B_PROTOCOL + for (idx = 0; idx < MAX_NUM_OBJECTS; idx++) { + input_mt_slot(input_dev, idx); + input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, 0); + } +#endif + input_report_key(input_dev, BTN_TOUCH, 0); + input_report_key(input_dev, BTN_TOOL_FINGER, 0); +#ifndef TYPE_B_PROTOCOL + input_mt_sync(input_dev); +#endif + input_sync(input_dev); + + syna_pal_mutex_unlock(&tcm->tp_event_mutex); + +} + +/** + * syna_dev_report_input_events() + * + * Report touched events to the input subsystem. + * + * After syna_tcm_get_event_data() function and the touched data is ready, + * this function can be called to report an input event. + * + * @param + * [ in] tcm: the driver handle + * + * @return + * none. + */ +static void syna_dev_report_input_events(struct syna_tcm *tcm) +{ + unsigned int idx; + unsigned int x; + unsigned int y; + int wx; + int wy; + unsigned int status; + unsigned int touch_count; + struct input_dev *input_dev = tcm->input_dev; + unsigned int max_objects = tcm->tcm_dev->max_objects; + struct tcm_touch_data_blob *touch_data; + struct tcm_objects_data_blob *object_data; + + if (input_dev == NULL) + return; + + syna_pal_mutex_lock(&tcm->tp_event_mutex); + + touch_data = &tcm->tp_data; + object_data = &tcm->tp_data.object_data[0]; + +#ifdef ENABLE_WAKEUP_GESTURE + if ((tcm->pwr_state == LOW_PWR) && tcm->irq_wake) { + if (touch_data->gesture_id) { + LOGD("Gesture detected, id:%d\n", + touch_data->gesture_id); + + input_report_key(input_dev, KEY_WAKEUP, 1); + input_sync(input_dev); + input_report_key(input_dev, KEY_WAKEUP, 0); + input_sync(input_dev); + } + } +#endif + + if (tcm->pwr_state == LOW_PWR) + goto exit; + + touch_count = 0; + + for (idx = 0; idx < max_objects; idx++) { + if (tcm->prev_obj_status[idx] == LIFT && + object_data[idx].status == LIFT) + status = NOP; + else + status = object_data[idx].status; + + switch (status) { + case LIFT: +#ifdef TYPE_B_PROTOCOL + input_mt_slot(input_dev, idx); + input_mt_report_slot_state(input_dev, + MT_TOOL_FINGER, 0); +#endif + break; + case FINGER: + case GLOVED_OBJECT: + x = object_data[idx].x_pos; + y = object_data[idx].y_pos; + wx = object_data[idx].x_width; + wy = object_data[idx].y_width; +#ifdef REPORT_SWAP_XY + x = x ^ y; + y = x ^ y; + x = x ^ y; +#endif +#ifdef REPORT_FLIP_X + x = tcm->input_dev_params.max_x - x; +#endif +#ifdef REPORT_FLIP_Y + y = tcm->input_dev_params.max_y - y; +#endif +#ifdef TYPE_B_PROTOCOL + input_mt_slot(input_dev, idx); + input_mt_report_slot_state(input_dev, + MT_TOOL_FINGER, 1); +#endif + input_report_key(input_dev, BTN_TOUCH, 1); + input_report_key(input_dev, BTN_TOOL_FINGER, 1); + input_report_abs(input_dev, ABS_MT_POSITION_X, x); + input_report_abs(input_dev, ABS_MT_POSITION_Y, y); +#ifdef REPORT_TOUCH_WIDTH + input_report_abs(input_dev, + ABS_MT_TOUCH_MAJOR, MAX(wx, wy)); + input_report_abs(input_dev, + ABS_MT_TOUCH_MINOR, MIN(wx, wy)); +#endif +#ifndef TYPE_B_PROTOCOL + input_mt_sync(input_dev); +#endif + LOGD("Finger %d: x = %d, y = %d\n", idx, x, y); + touch_count++; + break; + default: + break; + } + + tcm->prev_obj_status[idx] = object_data[idx].status; + } + + if (touch_count == 0) { + input_report_key(input_dev, BTN_TOUCH, 0); + input_report_key(input_dev, BTN_TOOL_FINGER, 0); +#ifndef TYPE_B_PROTOCOL + input_mt_sync(input_dev); +#endif + } + + input_sync(input_dev); + +exit: + syna_pal_mutex_unlock(&tcm->tp_event_mutex); + +} + +/** + * syna_dev_create_input_device() + * + * Allocate an input device and set up relevant parameters to the + * input subsystem. + * + * @param + * [ in] tcm: the driver handle + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_dev_create_input_device(struct syna_tcm *tcm) +{ + int retval = 0; + struct tcm_dev *tcm_dev = tcm->tcm_dev; + struct input_dev *input_dev = NULL; +#ifdef DEV_MANAGED_API + struct device *dev = syna_request_managed_device(); + + if (!dev) { + LOGE("Invalid managed device\n"); + return -EINVAL; + } + + input_dev = devm_input_allocate_device(dev); +#else /* Legacy API */ + input_dev = input_allocate_device(); +#endif + if (input_dev == NULL) { + LOGE("Fail to allocate input device\n"); + return -ENODEV; + } + + input_dev->name = TOUCH_INPUT_NAME; + input_dev->phys = TOUCH_INPUT_PHYS_PATH; + input_dev->id.product = SYNAPTICS_TCM_DRIVER_ID; + input_dev->id.version = SYNAPTICS_TCM_DRIVER_VERSION; + input_dev->dev.parent = tcm->pdev->dev.parent; + input_set_drvdata(input_dev, tcm); + + set_bit(EV_SYN, input_dev->evbit); + set_bit(EV_KEY, input_dev->evbit); + set_bit(EV_ABS, input_dev->evbit); + set_bit(BTN_TOUCH, input_dev->keybit); + set_bit(BTN_TOOL_FINGER, input_dev->keybit); +#ifdef INPUT_PROP_DIRECT + set_bit(INPUT_PROP_DIRECT, input_dev->propbit); +#endif + +#ifdef ENABLE_WAKEUP_GESTURE + set_bit(KEY_WAKEUP, input_dev->keybit); + input_set_capability(input_dev, EV_KEY, KEY_WAKEUP); +#endif + + input_set_abs_params(input_dev, + ABS_MT_POSITION_X, 0, tcm_dev->max_x, 0, 0); + input_set_abs_params(input_dev, + ABS_MT_POSITION_Y, 0, tcm_dev->max_y, 0, 0); + + input_mt_init_slots(input_dev, tcm_dev->max_objects, + INPUT_MT_DIRECT); + +#ifdef REPORT_TOUCH_WIDTH + input_set_abs_params(input_dev, + ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0); + input_set_abs_params(input_dev, + ABS_MT_TOUCH_MINOR, 0, 255, 0, 0); +#endif + + tcm->input_dev_params.max_x = tcm_dev->max_x; + tcm->input_dev_params.max_y = tcm_dev->max_y; + tcm->input_dev_params.max_objects = tcm_dev->max_objects; + + retval = input_register_device(input_dev); + if (retval < 0) { + LOGE("Fail to register input device\n"); + input_free_device(input_dev); + input_dev = NULL; + return retval; + } + + tcm->input_dev = input_dev; + + return 0; +} + +/** + * syna_dev_release_input_device() + * + * Release an input device allocated previously. + * + * @param + * [ in] tcm: the driver handle + * + * @return + * none. + */ +static void syna_dev_release_input_device(struct syna_tcm *tcm) +{ + if (!tcm->input_dev) + return; + + input_unregister_device(tcm->input_dev); + input_free_device(tcm->input_dev); + + tcm->input_dev = NULL; +} + +/** + * syna_dev_check_input_params() + * + * Check if any of the input parameters registered to the input subsystem + * has changed. + * + * @param + * [ in] tcm: the driver handle + * + * @return + * positive value to indicate mismatching parameters; otherwise, return 0. + */ +static int syna_dev_check_input_params(struct syna_tcm *tcm) +{ + struct tcm_dev *tcm_dev = tcm->tcm_dev; + + if (tcm_dev->max_x == 0 && tcm_dev->max_y == 0) + return 0; + + if (tcm->input_dev_params.max_x != tcm_dev->max_x) + return 1; + + if (tcm->input_dev_params.max_y != tcm_dev->max_y) + return 1; + + if (tcm->input_dev_params.max_objects != tcm_dev->max_objects) + return 1; + + if (tcm_dev->max_objects > MAX_NUM_OBJECTS) { + LOGW("Out of max num objects defined, in app_info: %d\n", + tcm_dev->max_objects); + return 0; + } + + LOGN("Input parameters unchanged\n"); + + return 0; +} + +/** + * syna_dev_set_up_input_device() + * + * Set up input device to the input subsystem by confirming the supported + * parameters and creating the device. + * + * @param + * [ in] tcm: the driver handle + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_dev_set_up_input_device(struct syna_tcm *tcm) +{ + int retval = 0; + + if (IS_NOT_APP_FW_MODE(tcm->tcm_dev->dev_mode)) { + LOGN("Application firmware not running, current mode: %02x\n", + tcm->tcm_dev->dev_mode); + return 0; + } + + syna_dev_free_input_events(tcm); + + syna_pal_mutex_lock(&tcm->tp_event_mutex); + + retval = syna_dev_check_input_params(tcm); + if (retval == 0) + goto exit; + + if (tcm->input_dev != NULL) + syna_dev_release_input_device(tcm); + + retval = syna_dev_create_input_device(tcm); + if (retval < 0) { + LOGE("Fail to create input device\n"); + goto exit; + } + +exit: + syna_pal_mutex_unlock(&tcm->tp_event_mutex); + + return retval; +} + +/** + * syna_dev_isr() + * + * This is the function to be called when the interrupt is asserted. + * The purposes of this handler is to read events generated by device and + * retrieve all enqueued messages until ATTN is no longer asserted. + * + * @param + * [ in] irq: interrupt line + * [ in] data: private data being passed to the handler function + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static irqreturn_t syna_dev_isr(int irq, void *data) +{ + int retval; + unsigned char code = 0; + struct syna_tcm *tcm = data; + struct syna_hw_attn_data *attn = &tcm->hw_if->bdata_attn; + + if (unlikely(gpio_get_value(attn->irq_gpio) != attn->irq_on_state)) + goto exit; + + tcm->isr_pid = current->pid; + +#ifdef HAS_SYSFS_INTERFACE + if (tcm->is_attn_redirecting) { + syna_cdev_redirect_attn(tcm); + goto exit; + } +#endif + /* retrieve the original report date generated by firmware */ + retval = syna_tcm_get_event_data(tcm->tcm_dev, + &code, + &tcm->event_data); + if (retval < 0) { + LOGE("Fail to get event data\n"); + goto exit; + } + +#ifdef ENABLE_EXTERNAL_FRAME_PROCESS + if (tcm->report_to_queue[code] == EFP_ENABLE) { + syna_tcm_buf_lock(&tcm->tcm_dev->external_buf); + syna_cdev_update_report_queue(tcm, code, + &tcm->tcm_dev->external_buf); + syna_tcm_buf_unlock(&tcm->tcm_dev->external_buf); +#ifndef REPORT_CONCURRENTLY + goto exit; +#endif + } +#endif + /* report input event only when receiving a touch report */ + if (code == REPORT_TOUCH) { + /* parse touch report once received */ + retval = syna_tcm_parse_touch_report(tcm->tcm_dev, + tcm->event_data.buf, + tcm->event_data.data_length, + &tcm->tp_data); + if (retval < 0) { + LOGE("Fail to parse touch report\n"); + goto exit; + } + /* forward the touch event to system */ + syna_dev_report_input_events(tcm); + } + +exit: + return IRQ_HANDLED; +} + + +/** + * syna_dev_request_irq() + * + * Allocate an interrupt line and register the ISR handler + * + * @param + * [ in] tcm: the driver handle + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_dev_request_irq(struct syna_tcm *tcm) +{ + int retval; + struct syna_hw_attn_data *attn = &tcm->hw_if->bdata_attn; +#ifdef DEV_MANAGED_API + struct device *dev = syna_request_managed_device(); + + if (!dev) { + LOGE("Invalid managed device\n"); + retval = -EINVAL; + goto exit; + } +#endif + + if (attn->irq_gpio < 0) { + LOGE("Invalid IRQ GPIO\n"); + retval = -EINVAL; + goto exit; + } + + attn->irq_id = gpio_to_irq(attn->irq_gpio); + +#ifdef DEV_MANAGED_API + retval = devm_request_threaded_irq(dev, + attn->irq_id, + NULL, + syna_dev_isr, + attn->irq_flags, + PLATFORM_DRIVER_NAME, + tcm); +#else /* Legacy API */ + retval = request_threaded_irq(attn->irq_id, + NULL, + syna_dev_isr, + attn->irq_flags, + PLATFORM_DRIVER_NAME, + tcm); +#endif + if (retval < 0) { + LOGE("Fail to request threaded irq\n"); + goto exit; + } + + attn->irq_enabled = true; + + LOGI("Interrupt handler registered\n"); + +exit: + return retval; +} + +/** + * syna_dev_release_irq() + * + * Release an interrupt line allocated previously + * + * @param + * [ in] tcm: the driver handle + * + * @return + * none. + */ +static void syna_dev_release_irq(struct syna_tcm *tcm) +{ + struct syna_hw_attn_data *attn = &tcm->hw_if->bdata_attn; +#ifdef DEV_MANAGED_API + struct device *dev = syna_request_managed_device(); + + if (!dev) { + LOGE("Invalid managed device\n"); + return; + } +#endif + + if (attn->irq_id <= 0) + return; + + if (tcm->hw_if->ops_enable_irq) + tcm->hw_if->ops_enable_irq(tcm->hw_if, false); + +#ifdef DEV_MANAGED_API + devm_free_irq(dev, attn->irq_id, tcm); +#else + free_irq(attn->irq_id, tcm); +#endif + + attn->irq_id = 0; + attn->irq_enabled = false; + + LOGI("Interrupt handler released\n"); +} + +/** + * syna_dev_set_up_app_fw() + * + * Implement the essential steps for the initialization including the + * preparation of app info and the configuration of touch report. + * + * This function should be called whenever the device initially powers + * up, resets, or firmware update. + * + * @param + * [ in] tcm: tcm driver handle + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_dev_set_up_app_fw(struct syna_tcm *tcm) +{ + int retval = 0; + struct tcm_dev *tcm_dev = tcm->tcm_dev; + + if (IS_NOT_APP_FW_MODE(tcm_dev->dev_mode)) { + LOGN("Application firmware not running, current mode: %02x\n", + tcm_dev->dev_mode); + return -EINVAL; + } + + /* collect app info containing most of sensor information */ + retval = syna_tcm_get_app_info(tcm_dev, &tcm_dev->app_info); + if (retval < 0) { + LOGE("Fail to get application info\n"); + return -EIO; + } + + /* set up the format of touch report */ +#ifdef USE_CUSTOM_TOUCH_REPORT_CONFIG + retval = syna_tcm_set_touch_report_config(tcm_dev, + custom_touch_format, + (unsigned int)sizeof(custom_touch_format)); + if (retval < 0) { + LOGE("Fail to setup the custom touch report format\n"); + return -EIO; + } +#endif + /* preserve the format of touch report */ + retval = syna_tcm_preserve_touch_report_config(tcm_dev); + if (retval < 0) { + LOGE("Fail to preserve touch report config\n"); + return -EIO; + } + +#ifdef ENABLE_CUSTOM_TOUCH_ENTITY + /* set up custom touch data parsing method */ + retval = syna_tcm_set_custom_touch_data_parsing_callback(tcm_dev, + syna_dev_parse_custom_touch_report, + (void *)tcm); + if (retval < 0) { + LOGE("Fail to set up custom touch data parsing method\n"); + return -EIO; + } +#endif + return retval; +} + +/** + * syna_dev_reflash_startup_work() + * + * Perform firmware update during system startup. + * Function is available when the 'STARTUP_REFLASH' configuration + * is enabled. + * + * @param + * [ in] work: handle of work structure + * + * @return + * none. + */ +#ifdef STARTUP_REFLASH +static void syna_dev_reflash_startup_work(struct work_struct *work) +{ + int retval; + struct delayed_work *delayed_work; + struct syna_tcm *tcm; + struct tcm_dev *tcm_dev; + const struct firmware *fw_entry; + const unsigned char *fw_image = NULL; + unsigned int fw_image_size; + + delayed_work = container_of(work, struct delayed_work, work); + tcm = container_of(delayed_work, struct syna_tcm, reflash_work); + + tcm_dev = tcm->tcm_dev; + + /* get firmware image */ + retval = request_firmware(&fw_entry, + FW_IMAGE_NAME, + tcm->pdev->dev.parent); + if (retval < 0) { + LOGE("Fail to request %s\n", FW_IMAGE_NAME); + return; + } + + fw_image = fw_entry->data; + fw_image_size = fw_entry->size; + + LOGD("Firmware image size = %d\n", fw_image_size); + + pm_stay_awake(&tcm->pdev->dev); + + /* perform fw update */ +#ifdef MULTICHIP_DUT_REFLASH + /* do firmware update for the multichip-based device */ + retval = syna_tcm_romboot_do_multichip_reflash(tcm_dev, + fw_image, + fw_image_size, + RESP_IN_ATTN, + false); +#else + /* do firmware update for the common device */ + retval = syna_tcm_do_fw_update(tcm_dev, + fw_image, + fw_image_size, + RESP_IN_ATTN, + false); +#endif + if (retval < 0) { + LOGE("Fail to do reflash\n"); + goto exit; + } + + /* re-initialize the app fw */ + retval = syna_dev_set_up_app_fw(tcm); + if (retval < 0) { + LOGE("Fail to set up app fw after fw update\n"); + goto exit; + } + + /* allocate the input device if not registered yet */ + if (tcm->input_dev == NULL) { + retval = syna_dev_set_up_input_device(tcm); + if (retval < 0) { + LOGE("Fail to register input device\n"); + goto exit; + } + } + +exit: + pm_relax(&tcm->pdev->dev); +} +#endif +/** + * syna_dev_enter_normal_sensing() + * + * Helper to enter normal seneing mode + * + * @param + * [ in] tcm: tcm driver handle + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_dev_enter_normal_sensing(struct syna_tcm *tcm) +{ + int retval = 0; + + if (!tcm) + return -EINVAL; + + /* bring out of sleep mode. */ + retval = syna_tcm_sleep(tcm->tcm_dev, false); + if (retval < 0) { + LOGE("Fail to exit deep sleep\n"); + return retval; + } + + /* disable low power gesture mode, if needed */ + if (tcm->lpwg_enabled) { + retval = syna_dev_enable_lowpwr_gesture(tcm, false); + if (retval < 0) { + LOGE("Fail to disable low power gesture mode\n"); + return retval; + } + } + + tcm->pwr_state = PWR_ON; + + return 0; +} +/** + * syna_dev_enter_lowpwr_sensing() + * + * Helper to enter power-saved sensing mode, that + * may be the lower power gesture mode or deep sleep mode. + * + * @param + * [ in] tcm: tcm driver handle + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_dev_enter_lowpwr_sensing(struct syna_tcm *tcm) +{ + int retval = 0; + + if (!tcm) + return -EINVAL; + + /* enable low power gesture mode, if needed */ + if (tcm->lpwg_enabled) { + retval = syna_dev_enable_lowpwr_gesture(tcm, true); + if (retval < 0) { + LOGE("Fail to disable low power gesture mode\n"); + return retval; + } + } else { + /* enter sleep mode for non-LPWG cases */ + if (!tcm->slept_in_early_suspend) { + retval = syna_tcm_sleep(tcm->tcm_dev, true); + if (retval < 0) { + LOGE("Fail to enter deep sleep\n"); + return retval; + } + } + } + + tcm->pwr_state = LOW_PWR; + + return 0; +} +/** + * syna_dev_resume() + * + * Resume from the suspend state. + * If RESET_ON_RESUME is defined, a reset is issued to the touch controller. + * Otherwise, the touch controller is brought out of sleep mode. + * + * @param + * [ in] dev: an instance of device + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_dev_resume(struct device *dev) +{ + int retval; + struct syna_tcm *tcm = dev_get_drvdata(dev); + struct syna_hw_interface *hw_if = tcm->hw_if; + bool irq_enabled = true; + + /* exit directly if device isn't in suspend state */ + if (tcm->pwr_state == PWR_ON) + return 0; + + LOGI("Prepare to resume device\n"); + +#ifdef RESET_ON_RESUME + syna_pal_sleep_ms(RESET_ON_RESUME_DELAY_MS); + + if (hw_if->ops_hw_reset) { + hw_if->ops_hw_reset(hw_if); + } else { + retval = syna_tcm_reset(tcm->tcm_dev); + if (retval < 0) { + LOGE("Fail to do reset\n"); + goto exit; + } + } +#else + /* enter normal power mode */ + retval = syna_dev_enter_normal_sensing(tcm); + if (retval < 0) { + LOGE("Fail to enter normal power mode\n"); + goto exit; + } + + retval = syna_tcm_rezero(tcm->tcm_dev); + if (retval < 0) { + LOGE("Fail to rezero\n"); + goto exit; + } +#endif /* end of RESET_ON_RESUME */ + + LOGI("Prepare to set up application firmware\n"); + + /* set up app firmware */ + retval = syna_dev_set_up_app_fw(tcm); + if (retval < 0) { + LOGE("Fail to set up app firmware on resume\n"); + goto exit; + } + + retval = 0; + + LOGI("Device resumed (pwr_state:%d)\n", tcm->pwr_state); + +exit: + /* once lpwg is enabled, irq should be enabled already. + * otherwise, set irq back to active mode. + */ + irq_enabled = (!tcm->lpwg_enabled); + + /* enable irq */ + if (irq_enabled && (hw_if->ops_enable_irq)) + hw_if->ops_enable_irq(hw_if, true); + + tcm->slept_in_early_suspend = false; + + return retval; +} + +/** + * syna_dev_suspend() + * + * Put device into suspend state. + * Enter either the lower power gesture mode or sleep mode. + * + * @param + * [ in] dev: an instance of device + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_dev_suspend(struct device *dev) +{ +#ifdef POWER_ALIVE_AT_SUSPEND + int retval; +#endif + struct syna_tcm *tcm = dev_get_drvdata(dev); + struct syna_hw_interface *hw_if = tcm->hw_if; + bool irq_disabled = true; + + /* exit directly if device is already in suspend state */ + if (tcm->pwr_state != PWR_ON) + return 0; + + LOGI("Prepare to suspend device\n"); + + /* clear all input events */ + syna_dev_free_input_events(tcm); + +#ifdef POWER_ALIVE_AT_SUSPEND + /* enter power saved mode if power is not off */ + retval = syna_dev_enter_lowpwr_sensing(tcm); + if (retval < 0) { + LOGE("Fail to enter suspended power mode\n"); + return retval; + } +#else + tcm->pwr_state = PWR_OFF; +#endif + + /* once lpwg is enabled, irq should be alive. + * otherwise, disable irq in suspend. + */ + irq_disabled = (!tcm->lpwg_enabled); + + /* disable irq */ + if (irq_disabled && (hw_if->ops_enable_irq)) + hw_if->ops_enable_irq(hw_if, false); + + LOGI("Device suspended (pwr_state:%d)\n", tcm->pwr_state); + + return 0; +} + +#if defined(ENABLE_DISP_NOTIFIER) +/** + * syna_dev_early_suspend() + * + * If having early suspend support, enter the sleep mode for + * non-lpwg cases. + * + * @param + * [ in] dev: an instance of device + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_dev_early_suspend(struct device *dev) +{ + int retval; + struct syna_tcm *tcm = dev_get_drvdata(dev); + + /* exit directly if device is already in suspend state */ + if (tcm->pwr_state != PWR_ON) + return 0; + + if (!tcm->lpwg_enabled) { + retval = syna_tcm_sleep(tcm->tcm_dev, true); + if (retval < 0) { + LOGE("Fail to enter deep sleep\n"); + return retval; + } + } + + tcm->slept_in_early_suspend = true; + + return 0; +} +/** + * syna_dev_fb_notifier_cb() + * + * Listen the display screen on/off event and perform the corresponding + * actions. + * + * @param + * [ in] nb: instance of notifier_block + * [ in] action: fb action + * [ in] data: fb event data + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_dev_fb_notifier_cb(struct notifier_block *nb, + unsigned long action, void *data) +{ + int retval; + int transition; +#if defined(USE_DRM_PANEL_NOTIFIER) + struct drm_panel_notifier *evdata = data; +#else + struct fb_event *evdata = data; +#endif + struct syna_tcm *tcm = container_of(nb, struct syna_tcm, fb_notifier); + int time = 0; + int disp_blank_powerdown; + int disp_early_event_blank; + int disp_blank; + int disp_blank_unblank; + + if (!evdata || !evdata->data || !tcm) + return 0; + + retval = 0; + +#if defined(USE_DRM_PANEL_NOTIFIER) + disp_blank_powerdown = DRM_PANEL_BLANK_POWERDOWN; + disp_early_event_blank = DRM_PANEL_EARLY_EVENT_BLANK; + disp_blank = DRM_PANEL_EVENT_BLANK; + disp_blank_unblank = DRM_PANEL_BLANK_UNBLANK; +#else + disp_blank_powerdown = FB_BLANK_POWERDOWN; + disp_early_event_blank = FB_EARLY_EVENT_BLANK; + disp_blank = FB_EVENT_BLANK; + disp_blank_unblank = FB_BLANK_UNBLANK; +#endif + + transition = *(int *)evdata->data; + + /* confirm the firmware flashing is completed before screen off */ + if (transition == disp_blank_powerdown) { + while (ATOMIC_GET(tcm->tcm_dev->firmware_flashing)) { + syna_pal_sleep_ms(500); + + time += 500; + if (time >= 5000) { + LOGE("Timed out waiting for reflashing\n"); + ATOMIC_SET(tcm->tcm_dev->firmware_flashing, 0); + return -EIO; + } + } + } + + if (action == disp_early_event_blank && + transition == disp_blank_powerdown) { + retval = syna_dev_early_suspend(&tcm->pdev->dev); + } else if (action == disp_blank) { + if (transition == disp_blank_powerdown) { + retval = syna_dev_suspend(&tcm->pdev->dev); + tcm->fb_ready = 0; + } else if (transition == disp_blank_unblank) { +#ifndef RESUME_EARLY_UNBLANK + retval = syna_dev_resume(&tcm->pdev->dev); + tcm->fb_ready++; +#endif + } else if (action == disp_early_event_blank && + transition == disp_blank_unblank) { +#ifdef RESUME_EARLY_UNBLANK + retval = syna_dev_resume(&tcm->pdev->dev); + tcm->fb_ready++; +#endif + } + } + + return 0; +} +#endif + +/** + * syna_dev_disconnect() + * + * This function will power off the connected device. + * Then, all the allocated resource will be released. + * + * @param + * [ in] tcm: the driver handle + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_dev_disconnect(struct syna_tcm *tcm) +{ + struct syna_hw_interface *hw_if = tcm->hw_if; + + if (tcm->is_connected == false) { + LOGI("%s already disconnected\n", PLATFORM_DRIVER_NAME); + return 0; + } + +#ifdef STARTUP_REFLASH + cancel_delayed_work_sync(&tcm->reflash_work); + flush_workqueue(tcm->reflash_workqueue); + destroy_workqueue(tcm->reflash_workqueue); +#endif + + /* free interrupt line */ + if (hw_if->bdata_attn.irq_id) + syna_dev_release_irq(tcm); + + /* unregister input device */ + syna_dev_release_input_device(tcm); + + tcm->input_dev_params.max_x = 0; + tcm->input_dev_params.max_y = 0; + tcm->input_dev_params.max_objects = 0; + + /* power off */ + if (hw_if->ops_power_on) + hw_if->ops_power_on(hw_if, false); + + tcm->pwr_state = PWR_OFF; + tcm->is_connected = false; + + LOGI("%s device disconnected\n", PLATFORM_DRIVER_NAME); + + return 0; +} + +/** + * syna_dev_connect() + * + * This function will power on and identify the connected device. + * At the end of function, the ISR will be registered as well. + * + * @param + * [ in] tcm: the driver handle + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_dev_connect(struct syna_tcm *tcm) +{ + int retval; + struct syna_hw_interface *hw_if = tcm->hw_if; + struct tcm_dev *tcm_dev = tcm->tcm_dev; + + if (!tcm_dev) { + LOGE("Invalid tcm_dev\n"); + return -EINVAL; + } + + if (tcm->is_connected) { + LOGI("%s already connected\n", PLATFORM_DRIVER_NAME); + return 0; + } + + /* power on the connected device */ + if (hw_if->ops_power_on) { + retval = hw_if->ops_power_on(hw_if, true); + if (retval < 0) + return -ENODEV; + } + + /* perform a hardware reset */ + if (hw_if->ops_hw_reset) + hw_if->ops_hw_reset(hw_if); + + /* detect which modes of touch controller is running + * + * the signal of the Touch IC ready is called as "identify" + * report and generated by firmware + */ + retval = syna_tcm_detect_device(tcm->tcm_dev); + if (retval < 0) { + LOGE("Fail to detect the device\n"); + goto err_detect_dev; + } + + switch (retval) { + case MODE_APPLICATION_FIRMWARE: + retval = syna_dev_set_up_app_fw(tcm); + if (retval < 0) { +#ifdef FORCE_CONNECTION + LOGW("App firmware is not available yet\n"); + LOGW("However, connect and skip initialization\n"); + goto end; +#else + LOGE("Fail to set up application firmware\n"); + goto err_setup_app_fw; +#endif + } + + /* allocate and register to input device subsystem */ + retval = syna_dev_set_up_input_device(tcm); + if (retval < 0) { + LOGE("Fail to set up input device\n"); + goto err_setup_input_dev; + } + + break; + default: + LOGN("Application firmware not running, current mode: %02x\n", + retval); + break; + } + + /* register the interrupt handler */ + retval = syna_dev_request_irq(tcm); + if (retval < 0) { + LOGE("Fail to request the interrupt line\n"); + goto err_request_irq; + } + + /* for the reference, + * create a delayed work to perform fw update during the startup time + */ +#ifdef STARTUP_REFLASH + tcm->reflash_workqueue = + create_singlethread_workqueue("syna_reflash"); + INIT_DELAYED_WORK(&tcm->reflash_work, syna_dev_reflash_startup_work); + queue_delayed_work(tcm->reflash_workqueue, &tcm->reflash_work, + msecs_to_jiffies(STARTUP_REFLASH_DELAY_TIME_MS)); +#endif +#ifdef FORCE_CONNECTION +end: +#endif + tcm->pwr_state = PWR_ON; + tcm->is_connected = true; + + LOGI("TCM packrat: %d\n", tcm->tcm_dev->packrat_number); + LOGI("Configuration: name(%s), lpwg(%s), hw_reset(%s), irq_ctrl(%s)\n", + PLATFORM_DRIVER_NAME, + (tcm->lpwg_enabled) ? "yes" : "no", + (hw_if->ops_hw_reset) ? "yes" : "no", + (hw_if->ops_enable_irq) ? "yes" : "no"); + + return 0; + +err_request_irq: + /* unregister input device */ + syna_dev_release_input_device(tcm); + +err_setup_input_dev: +#ifdef FORCE_CONNECTION +#else +err_setup_app_fw: +#endif +err_detect_dev: + if (hw_if->ops_power_on) + hw_if->ops_power_on(hw_if, false); + + return retval; +} + +#ifdef USE_DRM_PANEL_NOTIFIER +static struct drm_panel *syna_dev_get_panel(struct device_node *np) +{ + int i; + int count; + struct device_node *node; + struct drm_panel *panel; + + count = of_count_phandle_with_args(np, "panel", NULL); + if (count <= 0) + return NULL; + + for (i = 0; i < count; i++) { + node = of_parse_phandle(np, "panel", i); + panel = of_drm_find_panel(node); + of_node_put(node); + if (!IS_ERR(panel)) { + LOGI("Find available panel\n"); + return panel; + } + } + + return NULL; +} +#endif + +/** + * syna_dev_probe() + * + * Install the TouchComm device driver + * + * @param + * [ in] pdev: an instance of platform device + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_dev_probe(struct platform_device *pdev) +{ + int retval; + struct syna_tcm *tcm = NULL; + struct tcm_dev *tcm_dev = NULL; + struct syna_hw_interface *hw_if = NULL; +#if defined(USE_DRM_PANEL_NOTIFIER) + struct device *dev; +#endif + + hw_if = pdev->dev.platform_data; + if (!hw_if) { + LOGE("Fail to find hardware configuration\n"); + return -EINVAL; + } + + tcm = syna_pal_mem_alloc(1, sizeof(struct syna_tcm)); + if (!tcm) { + LOGE("Fail to create the instance of syna_tcm\n"); + return -ENOMEM; + } + + /* allocate the TouchCom device handle + * recommend to set polling mode here because isr is not registered yet + */ + retval = syna_tcm_allocate_device(&tcm_dev, hw_if, RESP_IN_POLLING); + if ((retval < 0) || (!tcm_dev)) { + LOGE("Fail to allocate TouchCom device handle\n"); + goto err_allocate_cdev; + } + + tcm->tcm_dev = tcm_dev; + tcm->pdev = pdev; + tcm->hw_if = hw_if; + + syna_tcm_buf_init(&tcm->event_data); + + syna_pal_mutex_alloc(&tcm->tp_event_mutex); + +#ifdef ENABLE_WAKEUP_GESTURE + tcm->lpwg_enabled = true; +#else + tcm->lpwg_enabled = false; +#endif + tcm->irq_wake = false; + + tcm->is_connected = false; + tcm->pwr_state = PWR_OFF; + + tcm->dev_connect = syna_dev_connect; + tcm->dev_disconnect = syna_dev_disconnect; + tcm->dev_set_up_app_fw = syna_dev_set_up_app_fw; + tcm->dev_set_normal_sensing = syna_dev_enter_normal_sensing; + tcm->dev_set_lowpwr_sensing = syna_dev_enter_lowpwr_sensing; + + platform_set_drvdata(pdev, tcm); + + device_init_wakeup(&pdev->dev, 1); + +#if defined(TCM_CONNECT_IN_PROBE) + /* connect to target device */ + retval = tcm->dev_connect(tcm); + if (retval < 0) { + LOGE("Fail to connect to the device\n"); + syna_pal_mutex_free(&tcm->tp_event_mutex); + goto err_connect; + } +#endif + +#ifdef HAS_SYSFS_INTERFACE + /* create the device file and register to char device classes */ + retval = syna_cdev_create_sysfs(tcm, pdev); + if (retval < 0) { + LOGE("Fail to create the device sysfs\n"); + syna_pal_mutex_free(&tcm->tp_event_mutex); + goto err_create_cdev; + } +#endif + +#if defined(ENABLE_DISP_NOTIFIER) +#if defined(USE_DRM_PANEL_NOTIFIER) + dev = syna_request_managed_device(); + active_panel = syna_dev_get_panel(dev->of_node); + if (active_panel) { + tcm->fb_notifier.notifier_call = syna_dev_fb_notifier_cb; + retval = drm_panel_notifier_register(active_panel, + &tcm->fb_notifier); + if (retval < 0) { + LOGE("Fail to register FB notifier client\n"); + goto err_create_cdev; + } + } else { + LOGE("No available drm panel\n"); + } +#else + tcm->fb_notifier.notifier_call = syna_dev_fb_notifier_cb; + retval = fb_register_client(&tcm->fb_notifier); + if (retval < 0) { + LOGE("Fail to register FB notifier client\n"); + goto err_create_cdev; + } +#endif +#endif + + LOGI("%s TouchComm driver v%d.%s installed\n", + PLATFORM_DRIVER_NAME, + SYNAPTICS_TCM_DRIVER_VERSION, + SYNAPTICS_TCM_DRIVER_SUBVER); + + return 0; + +#ifdef HAS_SYSFS_INTERFACE +err_create_cdev: + syna_tcm_remove_device(tcm->tcm_dev); +#endif +#if defined(TCM_CONNECT_IN_PROBE) + tcm->dev_disconnect(tcm); +err_connect: +#endif + syna_tcm_buf_release(&tcm->event_data); + syna_pal_mutex_free(&tcm->tp_event_mutex); +err_allocate_cdev: + syna_pal_mem_free((void *)tcm); + + return retval; +} + +/** + * syna_dev_remove() + * + * Release all allocated resources and remove the TouchCom device handle + * + * @param + * [ in] pdev: an instance of platform device + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_dev_remove(struct platform_device *pdev) +{ + struct syna_tcm *tcm = platform_get_drvdata(pdev); + + if (!tcm) { + LOGW("Invalid handle to remove\n"); + return 0; + } + +#if defined(ENABLE_DISP_NOTIFIER) +#if defined(USE_DRM_PANEL_NOTIFIER) + if (active_panel) + drm_panel_notifier_unregister(active_panel, + &tcm->fb_notifier); +#else + fb_unregister_client(&tcm->fb_notifier); +#endif +#endif + +#ifdef HAS_SYSFS_INTERFACE + /* remove the cdev and sysfs nodes */ + syna_cdev_remove_sysfs(tcm); +#endif + + /* check the connection statusm, and do disconnection */ + if (tcm->dev_disconnect(tcm) < 0) + LOGE("Fail to do device disconnection\n"); + + syna_tcm_buf_release(&tcm->event_data); + + syna_pal_mutex_free(&tcm->tp_event_mutex); + + /* remove the allocated tcm device */ + syna_tcm_remove_device(tcm->tcm_dev); + + /* release the device context */ + syna_pal_mem_free((void *)tcm); + + return 0; +} + +/** + * syna_dev_shutdown() + * + * Call syna_dev_remove() to release all resources + * + * @param + * [in] pdev: an instance of platform device + * + * @return + * none. + */ +static void syna_dev_shutdown(struct platform_device *pdev) +{ + syna_dev_remove(pdev); +} + + +/** + * Declare a TouchComm platform device + */ +#ifdef CONFIG_PM +static const struct dev_pm_ops syna_dev_pm_ops = { +#if !defined(ENABLE_DISP_NOTIFIER) + .suspend = syna_dev_suspend, + .resume = syna_dev_resume, +#endif +}; +#endif + +static struct platform_driver syna_dev_driver = { + .driver = { + .name = PLATFORM_DRIVER_NAME, + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &syna_dev_pm_ops, +#endif + }, + .probe = syna_dev_probe, + .remove = syna_dev_remove, + .shutdown = syna_dev_shutdown, +}; + + +/** + * syna_dev_module_init() + * + * The entry function of the reference driver, which initialize the + * lower-level bus and register a platform driver. + * + * @param + * void. + * + * @return + * 0 if the driver registered and bound to a device, + * else returns a negative error code and with the driver not registered. + */ +static int __init syna_dev_module_init(void) +{ + int retval; + + retval = syna_hw_interface_init(); + if (retval < 0) + return retval; + + return platform_driver_register(&syna_dev_driver); +} + +/** + * syna_dev_module_exit() + * + * Function is called when un-installing the driver. + * Remove the registered platform driver and the associated bus driver. + * + * @param + * void. + * + * @return + * none. + */ +static void __exit syna_dev_module_exit(void) +{ + platform_driver_unregister(&syna_dev_driver); + + syna_hw_interface_exit(); +} + +module_init(syna_dev_module_init); +module_exit(syna_dev_module_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics TCM Touch Driver"); +MODULE_LICENSE("GPL v2"); + diff --git a/syna_tcm2.h b/syna_tcm2.h new file mode 100644 index 0000000..c65e031 --- /dev/null +++ b/syna_tcm2.h @@ -0,0 +1,416 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Synaptics TouchCom touchscreen driver + * + * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +/** + * @file syna_tcm2.h + * + * The header file is used for the Synaptics TouchComm reference driver. + * Platform-specific functions and included headers are implemented in + * syna_touchcom_platform.h and syna_touchcom_runtime.h. + */ + +#ifndef _SYNAPTICS_TCM2_DRIVER_H_ +#define _SYNAPTICS_TCM2_DRIVER_H_ + +#include "syna_tcm2_platform.h" +#include "synaptics_touchcom_core_dev.h" +#include "synaptics_touchcom_func_touch.h" + +#define PLATFORM_DRIVER_NAME "synaptics_tcm" + +#define TOUCH_INPUT_NAME "synaptics_tcm_touch" +#define TOUCH_INPUT_PHYS_PATH "synaptics_tcm/touch_input" + +#define CHAR_DEVICE_NAME "tcm" +#define CHAR_DEVICE_MODE (0x0600) + +#define SYNAPTICS_TCM_DRIVER_ID (1 << 0) +#define SYNAPTICS_TCM_DRIVER_VERSION 1 +#define SYNAPTICS_TCM_DRIVER_SUBVER "2.0" + +/** + * @section: Driver Configurations + * + * The macros in the driver files below are used for doing compile time + * configuration of the driver. + */ + +/** + * @brief: HAS_SYSFS_INTERFACE + * Open to enable the sysfs interface + * + * @brief: HAS_REFLASH_FEATURE + * Open to enable firmware reflash features + * + * @brief: HAS_ROMBOOT_REFLASH_FEATURE + * Open to enable ROMBOOT reflash features + * + * @brief: HAS_TESTING_FEATURE + * Open to enable testing features + */ +#if defined(CONFIG_TOUCHSCREEN_SYNA_TCM2_SYSFS) +#define HAS_SYSFS_INTERFACE +#endif +#if defined(CONFIG_TOUCHSCREEN_SYNA_TCM2_REFLASH) +#define HAS_REFLASH_FEATURE +#endif +#if defined(CONFIG_TOUCHSCREEN_SYNA_TCM2_ROMBOOT) +#define HAS_ROMBOOT_REFLASH_FEATURE +#endif +#if defined(CONFIG_TOUCHSCREEN_SYNA_TCM2_TESTING) +#define HAS_TESTING_FEATURE +#endif + +/** + * @brief: TYPE_B_PROTOCOL + * Open to enable the multi-touch (MT) protocol + */ +#define TYPE_B_PROTOCOL + +/** + * @brief: RESET_ON_RESUME + * Open if willing to issue a reset to the touch controller + * from suspend. + * Set "disable" in default. + */ +/* #define RESET_ON_RESUME */ + +/** + * @brief ENABLE_WAKEUP_GESTURE + * Open if having wake-up gesture support. + */ +/* #define ENABLE_WAKEUP_GESTURE */ + +/** + * @brief REPORT_SWAP_XY + * Open if trying to swap x and y position coordinate reported. + * @brief REPORT_FLIP_X + * Open if trying to flip x position coordinate reported. + * @brief REPORT_FLIP_Y + * Open if trying to flip x position coordinate reported. + */ +/* #define REPORT_SWAP_XY */ +/* #define REPORT_FLIP_X */ +/* #define REPORT_FLIP_Y */ + +/** + * @brief REPORT_TOUCH_WIDTH + * Open if willing to add the width data to the input event. + */ +#define REPORT_TOUCH_WIDTH + +/** + * @brief USE_CUSTOM_TOUCH_REPORT_CONFIG + * Open if willing to set up the format of touch report. + * The custom_touch_format[] array in syna_tcm2.c can be used + * to describe the customized report format. + */ +/* #define USE_CUSTOM_TOUCH_REPORT_CONFIG */ + +/** + * @brief STARTUP_REFLASH + * Open if willing to do fw checking and update at startup. + * The firmware image will be obtained by request_firmware() API, + * so please ensure the image is built-in or included properly. + * + * This property is available only when SYNA_TCM2_REFLASH + * feature is enabled. + */ +#if defined(HAS_REFLASH_FEATURE) || defined(HAS_ROMBOOT_REFLASH_FEATURE) +/* #define STARTUP_REFLASH */ +#endif +/** + * @brief MULTICHIP_DUT_REFLASH + * Open if willing to do fw update and the DUT belongs to multi-chip + * product. This property dependent on STARTUP_REFLASH property. + * + * Set "disable" in default. + */ +#if defined(HAS_ROMBOOT_REFLASH_FEATURE) && defined(STARTUP_REFLASH) +/* #define MULTICHIP_DUT_REFLASH */ +#endif + +/** + * @brief ENABLE_DISP_NOTIFIER + * Open if having display notification event and willing to listen + * the event from display driver. + * + * Set "disable" in default due to no generic notifier for DRM + */ +#if defined(CONFIG_FB) || defined(CONFIG_DRM_PANEL) +/* #define ENABLE_DISP_NOTIFIER */ +#endif +/** + * @brief RESUME_EARLY_UNBLANK + * Open if willing to resume in early un-blanking state. + * + * This property is available only when ENABLE_DISP_NOTIFIER + * feature is enabled. + */ +#ifdef ENABLE_DISP_NOTIFIER +/* #define RESUME_EARLY_UNBLANK */ +#endif +/** + * @brief USE_DRM_PANEL_NOTIFIER + * Open if willing to listen the notification event from + * DRM_PANEL. Please be noted that 'struct drm_panel_notifier' + * must be implemented in the target BSP. + * + * This property is available only when ENABLE_DISP_NOTIFIER + * feature is enabled. + * + * Set "disable" in default due to no generic notifier for DRM + */ +#if defined(ENABLE_DISP_NOTIFIER) && defined(CONFIG_DRM_PANEL) +#define USE_DRM_PANEL_NOTIFIER +#endif + +/** + * @brief ENABLE_EXTERNAL_FRAME_PROCESS + * Open if having external frame process to the userspace application. + * + * Set "enable" in default + * + * @brief REPORT_TYPES + * Total types of report being used for external frame process. + * + * @brief EFP_ENABLE / EFP_DISABLE + * Specific value to label whether the report is required to be + * process or not. + * + * @brief REPORT_CONCURRENTLY + * Open if willing to concurrently handle reports for both kernel + * and userspace application. + * + * Set "disable" in default + */ +#define ENABLE_EXTERNAL_FRAME_PROCESS +#define REPORT_TYPES (256) +#define EFP_ENABLE (1) +#define EFP_DISABLE (0) +/* #define REPORT_CONCURRENTLY */ + +/** + * @brief TCM_CONNECT_IN_PROBE + * Open if willing to detect and connect to TouchComm device at + * probe function; otherwise, please invoke connect() manually. + * + * Set "enable" in default + */ +#define TCM_CONNECT_IN_PROBE + +/** + * @brief FORCE_CONNECTION + * Open if willing to connect to TouchComm device w/o error outs. + * + * Set "disable" in default + */ +/* #define FORCE_CONNECTION */ + +/** + * @brief ENABLE_CUSTOM_TOUCH_ENTITY + * Open if having custom requirements to parse the custom code + * entity in the touch report. + * + * Set "disable" in default + */ +/* #define ENABLE_CUSTOM_TOUCH_ENTITY */ + +/** + * @section: Power States + * + * The below structure enumerates the power states of device + */ +enum power_state { + PWR_OFF = 0, + PWR_ON, + LOW_PWR, +}; + +/** + * @brief: context of the synaptics linux-based driver + * + * The structure defines the kernel specific data in linux-based driver + */ +struct syna_tcm { + + /* TouchComm device core context */ + struct tcm_dev *tcm_dev; + + /* PLatform device driver */ + struct platform_device *pdev; + + /* Generic touched data generated by tcm core lib */ + struct tcm_touch_data_blob tp_data; + + syna_pal_mutex_t tp_event_mutex; + + unsigned char prev_obj_status[MAX_NUM_OBJECTS]; + + /* Buffer stored the irq event data */ + struct tcm_buffer event_data; + + /* Hardware interface layer */ + struct syna_hw_interface *hw_if; + + /* ISR-related variables */ + pid_t isr_pid; + bool irq_wake; + + /* cdev and sysfs nodes creation */ + struct cdev char_dev; + dev_t char_dev_num; + int char_dev_ref_count; + + struct class *device_class; + struct device *device; + + struct kobject *sysfs_dir; + + /* Input device registration */ + struct input_dev *input_dev; + struct input_params { + unsigned int max_x; + unsigned int max_y; + unsigned int max_objects; + } input_dev_params; + + /* Workqueue used for fw update */ + struct delayed_work reflash_work; + struct workqueue_struct *reflash_workqueue; + + /* IOCTL-related variables */ + pid_t proc_pid; + struct task_struct *proc_task; + + /* flags */ + int pwr_state; + bool slept_in_early_suspend; + bool lpwg_enabled; + bool is_attn_redirecting; + unsigned char fb_ready; + bool is_connected; + + /* framebuffer callbacks notifier */ +#if defined(ENABLE_DISP_NOTIFIER) + struct notifier_block fb_notifier; +#endif + + /* fifo to pass the report to userspace */ + unsigned int fifo_remaining_frame; + struct list_head frame_fifo_queue; + wait_queue_head_t wait_frame; + unsigned char report_to_queue[REPORT_TYPES]; + + /* Specific function pointer to do device connection. + * + * This function will power on and identify the connected device. + * At the end of function, the ISR will be registered as well. + * + * @param + * [ in] tcm: the driver handle + * + * @return + * on success, 0; otherwise, negative value on error. + */ + int (*dev_connect)(struct syna_tcm *tcm); + + /* Specific function pointer to disconnect the device + * + * This function will power off the connected device. + * Then, all the allocated resource will be released. + * + * @param + * [ in] tcm: the driver handle + * + * @return + * on success, 0; otherwise, negative value on error. + */ + int (*dev_disconnect)(struct syna_tcm *tcm); + + /* Specific function pointer to set up app fw firmware + * + * This function should be called whenever the device initially + * powers up, resets, or firmware update. + * + * @param + * [ in] tcm: the driver handle + * + * @return + * on success, 0; otherwise, negative value on error. + */ + int (*dev_set_up_app_fw)(struct syna_tcm *tcm); + + /* Specific function pointer to enter normal sensing mode + * + * @param + * [ in] tcm: tcm driver handle + * + * @return + * on success, 0; otherwise, negative value on error. + */ + int (*dev_set_normal_sensing)(struct syna_tcm *tcm); + + /* Specific function pointer to enter power-saved sensing mode. + * + * @param + * [ in] tcm: tcm driver handle + * + * @return + * on success, 0; otherwise, negative value on error. + */ + int (*dev_set_lowpwr_sensing)(struct syna_tcm *tcm); +}; + +/** + * @brief: Helpers for cdevice nodes and sysfs nodes creation + * + * These functions are implemented in syna_touchcom_sysfs.c + * and available only when HAS_SYSFS_INTERFACE is enabled. + */ +#ifdef HAS_SYSFS_INTERFACE + +int syna_cdev_create_sysfs(struct syna_tcm *ptcm, + struct platform_device *pdev); + +void syna_cdev_remove_sysfs(struct syna_tcm *ptcm); + +void syna_cdev_redirect_attn(struct syna_tcm *ptcm); + +#ifdef ENABLE_EXTERNAL_FRAME_PROCESS +void syna_cdev_update_report_queue(struct syna_tcm *tcm, + unsigned char code, struct tcm_buffer *pevent_data); +#endif + +#endif + +#endif /* end of _SYNAPTICS_TCM2_DRIVER_H_ */ + diff --git a/syna_tcm2_platform.h b/syna_tcm2_platform.h new file mode 100644 index 0000000..23d2661 --- /dev/null +++ b/syna_tcm2_platform.h @@ -0,0 +1,306 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Synaptics TouchCom touchscreen driver + * + * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +/** + * @file: syna_tcm2_platform.h + * + * This file declares the platform-specific or hardware relevant data. + * + * The main structure, syna_hw_interface, abstracts the bus transferred, + * ATTN signal, RST_N pin, and power control operations. + */ + +#ifndef _SYNAPTICS_TCM2_PLATFORM_H_ +#define _SYNAPTICS_TCM2_PLATFORM_H_ + +#include "syna_tcm2_runtime.h" + +/** + * @section: The capability of bus transferred + * + * Declare read/write capability in bytes (0 = unlimited) + */ +#define RD_CHUNK_SIZE (512) +#define WR_CHUNK_SIZE (512) + + +/** + * @section: Defined Hardware-Specific Data + * + * @brief: syna_hw_bus_data + * Hardware Data for bus transferred + * + * @brief: syna_hw_attn_data + * Hardware Data for ATTN signal + * + * @brief: syna_hw_rst_data + * Hardware Data for RST_N pin + * + * @brief: syna_hw_pwr_data + * Hardware Data for power control + * + * @brief: syna_hw_interface + * Contain all above hardware data and abstract the + * hardware operations + */ + +/* The hardware data especially for bus transferred */ +struct syna_hw_bus_data { + unsigned char type; + /* capability of i/o chunk */ + unsigned int rd_chunk_size; + unsigned int wr_chunk_size; + /* clock frequency in hz */ + unsigned int frequency_hz; + /* parameters for i2c */ + unsigned int i2c_addr; + /* parameters for spi */ + unsigned int spi_mode; + unsigned int spi_byte_delay_us; + unsigned int spi_block_delay_us; + /* mutex to protect the i/o, if needed */ + syna_pal_mutex_t io_mutex; +}; + +/* The hardware data especially for ATTN signal */ +struct syna_hw_attn_data { + /* parameters */ + int irq_gpio; + int irq_on_state; + unsigned long irq_flags; + int irq_id; + bool irq_enabled; + /* mutex to protect the irq control, if needed */ + syna_pal_mutex_t irq_en_mutex; +}; + +/* The hardware data especially for RST_N pin */ +struct syna_hw_rst_data { + /* parameters */ + int reset_gpio; + int reset_on_state; + unsigned int reset_delay_ms; + unsigned int reset_active_ms; +}; + +/* The hardware data especially for power control */ +struct syna_hw_pwr_data { + /* parameters */ + int vdd_gpio; + int avdd_gpio; + int power_on_state; + unsigned int power_on_delay_ms; + /* voltage */ + unsigned int vdd; + unsigned int vled; + unsigned int vio; + unsigned int vddtx; + /* regulators */ + const char *vdd_reg_name; + void *vdd_reg_dev; + const char *avdd_reg_name; + void *avdd_reg_dev; +}; + +/** + * @section: Hardware Interface Abstraction Layer + * + * The structure contains the hardware-specific implementations + * on the target platform. + */ +struct syna_hw_interface { + /* The handle of hardware device */ + void *pdev; + + /* Hardware specific data */ + struct syna_hw_bus_data bdata_io; + struct syna_hw_attn_data bdata_attn; + struct syna_hw_rst_data bdata_rst; + struct syna_hw_pwr_data bdata_pwr; + + /* Operation to do power on/off, if supported + * + * This is an optional operation. + * + * Implementation should request that the power device be + * enabled with the output at the proper voltage. + * + * Assign the pointer NULL if power supply module is not controllable. + * + * @param + * [ in] hw_if: the handle of hw interface + * [ in] en: '1' for powering on, and '0' for powering off + * + * @return + * 0 on success; otherwise, on error. + */ + int (*ops_power_on)(struct syna_hw_interface *hw_if, + bool en); + + /* Operation to perform the hardware reset, if supported + * + * This is an optional operation. + * + * The actual reset waveform should be reference to the device spec. + * + * Assign the pointer NULL if RST_N pin is not controllable. + * + * @param + * [ in] hw_if: the handle of hw interface + * + * @return + * 0 on success; otherwise, on error. + */ + void (*ops_hw_reset)(struct syna_hw_interface *hw_if); + + /* Operation to set up the bus connection + * + * This is an optional operation to add in the extra configuration + * before doing connection. + * + * Assign the pointer NULL if this operation is not required. + * + * @param + * [ in] hw_if: the handle of hw interface + * [ in] config: parameters to change + * + * @return + * 0 or positive value on success; otherwise, on error. + */ + int (*ops_bus_setup)(struct syna_hw_interface *hw_if, + struct syna_hw_bus_data *config); + + /* Operation to read the bare data from bus + * + * This is an essential operation; otherwise, the communication + * will not be created. + * + * @param + * [ in] hw_if: the handle of hw interface + * [out] rd_data: buffer for storing data retrieved + * [ in] rd_len: length of reading data in bytes + * + * @return + * 0 or positive value on success; otherwise, on error. + */ + int (*ops_read_data)(struct syna_hw_interface *hw_if, + unsigned char *rd_data, unsigned int rd_len); + + /* Operation to write the bare data to bus + * + * This is an essential operation; otherwise, the communication + * will not be created. + * + * @param + * [ in] hw_if: the handle of hw interface + * [ in] wr_data: written data + * [ in] wr_len: length of written data in bytes + * + * @return + * 0 or positive value on success; otherwise, on error. + */ + int (*ops_write_data)(struct syna_hw_interface *hw_if, + unsigned char *wr_data, unsigned int wr_len); + + /* Operation to enable/disable the irq, if supported + * + * This is an optional operation. Providing this operation could + * minimize the frequency of ISR being called. + * + * Once disabled, ISR will not be invoked even ATTN is asserted. + * + * Assign the pointer NULL if irq is not controllable. + * + * @param + * [ in] hw_if: the handle of hw interface + * [ in] en: '1' for enabling, and '0' for disabling + * + * @return + * 0 on success; otherwise, on error. + */ + int (*ops_enable_irq)(struct syna_hw_interface *hw_if, + bool en); + + /* Operation to wait for the signal of interrupt, if supported + * + * This is an optional operation to help for creating the + * custom interrupt handler. Besides, the recommendation is to + * implement in one-shot approach if possible. + * + * Implementation should return control if ATTN is asserted or + * specified timeout expire. If timeout is 0, should check the + * state of the ATTN signal and return control immediately. + * + * Assign the pointer NULL if customized ISR is not required. + * + * @param + * [ in] hw_if: the handle of hw interface + * [ in] timeout_ms: time frame in milliseconds + * + * @return + * 0 on success; otherwise, on error. + */ + int (*ops_wait_irq)(struct syna_hw_interface *hw_if, + unsigned int timeout_ms); + +}; +/* end of structure syna_hw_interface */ + + +/** + * syna_hw_interface_init() + * + * Initialize the lower-level hardware interface module. + * After returning, the handle of hw interface should be ready. + * + * @param + * void + * + * @return + * on success, 0; otherwise, negative value on error. + */ +int syna_hw_interface_init(void); + +/** + * syna_hw_interface_exit() + * + * Delete the lower-level hardware interface module. + * + * @param + * void + * + * @return + * none. + */ +void syna_hw_interface_exit(void); + + +#endif /* end of _SYNAPTICS_TCM2_PLATFORM_H_ */ diff --git a/syna_tcm2_platform_i2c.c b/syna_tcm2_platform_i2c.c new file mode 100644 index 0000000..34ce950 --- /dev/null +++ b/syna_tcm2_platform_i2c.c @@ -0,0 +1,898 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Synaptics TouchCom touchscreen driver + * + * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +/** + * @file syna_tcm2_platform_i2c.c + * + * This file is the reference code of I2C module used for communicating with + * Synaptics TouchCom device using I2C + */ + +#include + +#include "syna_tcm2.h" +#include "syna_tcm2_platform.h" + +#define I2C_MODULE_NAME "synaptics_tcm_i2c" + +#define XFER_ATTEMPTS 5 + +static struct platform_device *syna_i2c_device; + + +/** + * syna_request_managed_device() + * + * Request and return the device pointer for managed + * + * @param + * none. + * + * @return + * a device pointer allocated previously + */ +#if defined(DEV_MANAGED_API) || defined(USE_DRM_PANEL_NOTIFIER) +struct device *syna_request_managed_device(void) +{ + if (!syna_i2c_device) + return NULL; + + return syna_i2c_device->dev.parent; +} +#endif + +/** + * syna_i2c_hw_reset() + * + * Toggle the hardware gpio pin to perform the chip reset + * + * @param + * [ in] hw_if: the handle of hw interface + * + * @return + * none. + */ +static void syna_i2c_hw_reset(struct syna_hw_interface *hw_if) +{ + struct syna_hw_rst_data *rst = &hw_if->bdata_rst; + + if (rst->reset_gpio >= 0) { + gpio_set_value(rst->reset_gpio, rst->reset_on_state); + syna_pal_sleep_ms(rst->reset_active_ms); + gpio_set_value(rst->reset_gpio, !rst->reset_on_state); + syna_pal_sleep_ms(rst->reset_delay_ms); + } +} + +/** + * syna_i2c_request_gpio() + * + * Setup the given gpio + * + * @param + * [ in] gpio: the target gpio + * [ in] config: '1' for setting up, and '0' to release the gpio + * [ in] dir: default direction of gpio + * [ in] state: default state of gpio + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_i2c_request_gpio(int gpio, bool config, int dir, + int state, char *label) +{ + int retval; +#ifdef DEV_MANAGED_API + struct device *dev = syna_request_managed_device(); + + if (!dev) { + LOGE("Invalid managed device\n"); + return -ENODEV; + } +#endif + + if (config) { + retval = snprintf(label, 16, "tcm_gpio_%d\n", gpio); + if (retval < 0) { + LOGE("Fail to set GPIO label\n"); + return retval; + } +#ifdef DEV_MANAGED_API + retval = devm_gpio_request(dev, gpio, label); +#else /* Legacy API */ + retval = gpio_request(gpio, label); +#endif + if (retval < 0) { + LOGE("Fail to request GPIO %d\n", gpio); + return retval; + } + + if (dir == 0) + retval = gpio_direction_input(gpio); + else + retval = gpio_direction_output(gpio, state); + + if (retval < 0) { + LOGE("Fail to set GPIO %d direction\n", gpio); + return retval; + } + } else { +#ifdef DEV_MANAGED_API + devm_gpio_free(dev, gpio); +#else /* Legacy API */ + gpio_free(gpio); +#endif + } + + return 0; +} + +/** + * syna_i2c_config_gpio() + * + * Initialize the GPIOs defined in device tree + * + * @param + * [ in] hw_if: the handle of hw interface + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_i2c_config_gpio(struct syna_hw_interface *hw_if) +{ + int retval; + static char str_irq_gpio[32] = {0}; + static char str_rst_gpio[32] = {0}; + static char str_vdd_gpio[32] = {0}; + static char str_avdd_gpio[32] = {0}; + struct syna_hw_attn_data *attn = &hw_if->bdata_attn; + struct syna_hw_pwr_data *pwr = &hw_if->bdata_pwr; + struct syna_hw_rst_data *rst = &hw_if->bdata_rst; + + if (attn->irq_gpio >= 0) { + retval = syna_i2c_request_gpio(attn->irq_gpio, + true, 0, 0, str_irq_gpio); + if (retval < 0) { + LOGE("Fail to configure interrupt GPIO %d\n", + attn->irq_gpio); + goto err_set_gpio_irq; + } + } + + if (rst->reset_gpio >= 0) { + retval = syna_i2c_request_gpio(rst->reset_gpio, + true, 1, !rst->reset_on_state, + str_rst_gpio); + if (retval < 0) { + LOGE("Fail to configure reset GPIO %d\n", + rst->reset_gpio); + goto err_set_gpio_reset; + } + } + + if (pwr->vdd_gpio >= 0) { + retval = syna_i2c_request_gpio(pwr->vdd_gpio, + true, 1, !pwr->power_on_state, + str_vdd_gpio); + if (retval < 0) { + LOGE("Fail to configure vdd GPIO %d\n", + pwr->vdd_gpio); + goto err_set_gpio_vdd; + } + } + + if (pwr->avdd_gpio >= 0) { + retval = syna_i2c_request_gpio(pwr->avdd_gpio, + true, 1, !pwr->power_on_state, + str_avdd_gpio); + if (retval < 0) { + LOGE("Fail to configure avdd GPIO %d\n", + pwr->avdd_gpio); + goto err_set_gpio_avdd; + } + } + return 0; + +err_set_gpio_avdd: + if (pwr->vdd_gpio >= 0) + syna_i2c_request_gpio(pwr->vdd_gpio, false, 0, 0, NULL); +err_set_gpio_vdd: + if (rst->reset_gpio >= 0) + syna_i2c_request_gpio(rst->reset_gpio, false, 0, 0, NULL); +err_set_gpio_reset: + if (attn->irq_gpio >= 0) + syna_i2c_request_gpio(attn->irq_gpio, false, 0, 0, NULL); +err_set_gpio_irq: + return retval; +} + +/** + * syna_i2c_enable_regulator() + * + * Enable or disable the regulator + * + * @param + * [ in] hw_if: the handle of hw interface + * [ in] en: '1' for enabling, and '0' for disabling + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_i2c_enable_regulator(struct syna_hw_interface *hw_if, + bool en) +{ + int retval; + struct syna_hw_pwr_data *pwr = &hw_if->bdata_pwr; + struct regulator *vdd_reg = pwr->vdd_reg_dev; + struct regulator *avdd_reg = pwr->avdd_reg_dev; + + if (!en) { + retval = 0; + goto disable_pwr_reg; + } + + if (vdd_reg) { + retval = regulator_enable(vdd_reg); + if (retval < 0) { + LOGE("Fail to enable vdd regulator\n"); + goto exit; + } + } + + if (avdd_reg) { + retval = regulator_enable(avdd_reg); + if (retval < 0) { + LOGE("Fail to enable avdd regulator\n"); + goto disable_avdd_reg; + } + syna_pal_sleep_ms(pwr->power_on_delay_ms); + } + + return 0; + +disable_pwr_reg: + if (vdd_reg) + regulator_disable(vdd_reg); + +disable_avdd_reg: + if (avdd_reg) + regulator_disable(avdd_reg); + +exit: + return retval; +} + +/** + * syna_i2c_get_regulator() + * + * Acquire or release the regulator + * + * @param + * [ in] hw_if: the handle of hw interface + * [ in] get: '1' for getting the regulator, and '0' for removing + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_i2c_get_regulator(struct syna_hw_interface *hw_if, + bool get) +{ + int retval; + struct device *dev = syna_i2c_device->dev.parent; + struct syna_hw_pwr_data *pwr = &hw_if->bdata_pwr; + + if (!get) { + retval = 0; + goto regulator_put; + } + + if (pwr->vdd_reg_name != NULL && *pwr->vdd_reg_name != 0) { +#ifdef DEV_MANAGED_API + pwr->vdd_reg_dev = devm_regulator_get(dev, pwr->vdd_reg_name); +#else /* Legacy API */ + pwr->vdd_reg_dev = regulator_get(dev, pwr->vdd_reg_name); +#endif + if (IS_ERR((struct regulator *)pwr->vdd_reg_dev)) { + LOGW("Vdd regulator is not ready\n"); + retval = PTR_ERR((struct regulator *)pwr->vdd_reg_dev); + goto exit; + } + } + + if (pwr->avdd_reg_name != NULL && *pwr->avdd_reg_name != 0) { +#ifdef DEV_MANAGED_API + pwr->avdd_reg_dev = devm_regulator_get(dev, pwr->avdd_reg_name); +#else /* Legacy API */ + pwr->avdd_reg_dev = regulator_get(dev, pwr->avdd_reg_name); +#endif + if (IS_ERR((struct regulator *)pwr->avdd_reg_dev)) { + LOGW("AVdd regulator is not ready\n"); + retval = PTR_ERR((struct regulator *)pwr->avdd_reg_dev); + goto regulator_vdd_put; + } + } + + return 0; + +regulator_put: + if (pwr->vdd_reg_dev) { +#ifdef DEV_MANAGED_API + devm_regulator_put(pwr->vdd_reg_dev); +#else /* Legacy API */ + regulator_put(pwr->vdd_reg_dev); +#endif + pwr->vdd_reg_dev = NULL; + } +regulator_vdd_put: + if (pwr->avdd_reg_dev) { +#ifdef DEV_MANAGED_API + devm_regulator_put(pwr->avdd_reg_dev); +#else /* Legacy API */ + regulator_put(pwr->avdd_reg_dev); +#endif + pwr->avdd_reg_dev = NULL; + } +exit: + return retval; +} + +/** + * syna_i2c_enable_irq() + * + * Enable or disable the handling of interrupt + * + * @param + * [ in] hw_if: the handle of hw interface + * [ in] en: '1' for enabling, and '0' for disabling + * + * @return + * 0 on success; otherwise, on error. + */ +static int syna_i2c_enable_irq(struct syna_hw_interface *hw_if, + bool en) +{ + int retval = 0; + struct syna_hw_attn_data *attn = &hw_if->bdata_attn; + + if (attn->irq_id == 0) + return 0; + + syna_pal_mutex_lock(&attn->irq_en_mutex); + + /* enable the handling of interrupt */ + if (en) { + if (attn->irq_enabled) { + LOGI("Interrupt already enabled\n"); + retval = 0; + goto exit; + } + + enable_irq(attn->irq_id); + attn->irq_enabled = true; + + LOGD("irq enabled\n"); + } + /* disable the handling of interrupt */ + else { + if (!attn->irq_enabled) { + LOGI("Interrupt already disabled\n"); + retval = 0; + goto exit; + } + + disable_irq_nosync(attn->irq_id); + attn->irq_enabled = false; + + LOGD("irq disabled\n"); + } + +exit: + syna_pal_mutex_unlock(&attn->irq_en_mutex); + + return retval; +} + + +/** + * syna_i2c_parse_dt() + * + * Parse and obtain board specific data from the device tree source file. + * Keep the data in structure syna_tcm_hw_data for later using. + * + * @param + * [ in] hw_if: the handle of hw interface + * [ in] dev: device model + * + * @return + * on success, 0; otherwise, negative value on error. + */ +#ifdef CONFIG_OF +static int syna_i2c_parse_dt(struct syna_hw_interface *hw_if, + struct device *dev) +{ + int retval; + u32 value; + struct property *prop; + struct device_node *np = dev->of_node; + const char *name; + struct syna_hw_attn_data *attn = &hw_if->bdata_attn; + struct syna_hw_pwr_data *pwr = &hw_if->bdata_pwr; + struct syna_hw_rst_data *rst = &hw_if->bdata_rst; + + prop = of_find_property(np, "synaptics,irq-gpio", NULL); + if (prop && prop->length) { + attn->irq_gpio = of_get_named_gpio_flags(np, + "synaptics,irq-gpio", 0, + (enum of_gpio_flags *)&attn->irq_flags); + } else { + attn->irq_gpio = -1; + } + + retval = of_property_read_u32(np, "synaptics,irq-on-state", &value); + if (retval < 0) + attn->irq_on_state = 0; + else + attn->irq_on_state = value; + + retval = of_property_read_string(np, "synaptics,avdd-name", &name); + if (retval < 0) + pwr->avdd_reg_name = NULL; + else + pwr->avdd_reg_name = name; + + retval = of_property_read_string(np, "synaptics,vdd-name", &name); + if (retval < 0) + pwr->vdd_reg_name = NULL; + else + pwr->vdd_reg_name = name; + + prop = of_find_property(np, "synaptics,vdd-gpio", NULL); + if (prop && prop->length) { + pwr->vdd_gpio = of_get_named_gpio_flags(np, + "synaptics,vdd-gpio", 0, NULL); + } else { + pwr->vdd_gpio = -1; + } + + prop = of_find_property(np, "synaptics,avdd-gpio", NULL); + if (prop && prop->length) { + pwr->avdd_gpio = of_get_named_gpio_flags(np, + "synaptics,avdd-gpio", 0, NULL); + } else { + pwr->avdd_gpio = -1; + } + + prop = of_find_property(np, "synaptics,power-on-state", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,power-on-state", + &value); + if (retval < 0) { + LOGE("Fail to read power-on-state property\n"); + return retval; + } + + pwr->power_on_state = value; + + } else { + pwr->power_on_state = 0; + } + + prop = of_find_property(np, "synaptics,power-delay-ms", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,power-delay-ms", + &value); + if (retval < 0) { + LOGE("Fail to read power-delay-ms property\n"); + return retval; + } + + pwr->power_on_delay_ms = value; + + } else { + pwr->power_on_delay_ms = 0; + } + + prop = of_find_property(np, "synaptics,reset-gpio", NULL); + if (prop && prop->length) { + rst->reset_gpio = of_get_named_gpio_flags(np, + "synaptics,reset-gpio", 0, NULL); + } else { + rst->reset_gpio = -1; + } + + prop = of_find_property(np, "synaptics,reset-on-state", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,reset-on-state", + &value); + if (retval < 0) { + LOGE("Fail to read reset-on-state property\n"); + return retval; + } + + rst->reset_on_state = value; + + } else { + rst->reset_on_state = 0; + } + + prop = of_find_property(np, "synaptics,reset-active-ms", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,reset-active-ms", + &value); + if (retval < 0) { + LOGE("Fail to read reset-active-ms property\n"); + return retval; + } + + rst->reset_active_ms = value; + + } else { + rst->reset_active_ms = 0; + } + + prop = of_find_property(np, "synaptics,reset-delay-ms", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,reset-delay-ms", + &value); + if (retval < 0) { + LOGE("Fail to read reset-delay-ms property\n"); + return retval; + } + + rst->reset_delay_ms = value; + + } else { + rst->reset_delay_ms = 0; + } + + return 0; +} +#endif + + +/** + * syna_i2c_read() + * + * TouchCom over I2C uses the normal I2C addressing and transaction direction + * mechanisms to select the device and retrieve the data. + * + * @param + * [ in] hw_if: the handle of hw interface + * [out] rd_data: buffer for storing data retrieved from device + * [ in] rd_len: number of bytes retrieved from device + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_i2c_read(struct syna_hw_interface *hw_if, + unsigned char *rd_data, unsigned int rd_len) +{ + int retval; + unsigned int attempt; + struct i2c_msg msg; + struct i2c_client *i2c = hw_if->pdev; + struct syna_hw_bus_data *bus = &hw_if->bdata_io; + + if (!i2c) { + LOGE("Invalid bus io device\n"); + return -EINVAL; + } + + syna_pal_mutex_lock(&bus->io_mutex); + + msg.addr = i2c->addr; + msg.flags = I2C_M_RD; + msg.len = rd_len; + msg.buf = rd_data; + + for (attempt = 0; attempt < XFER_ATTEMPTS; attempt++) { + if (i2c_transfer(i2c->adapter, &msg, 1) == 1) { + retval = rd_len; + goto exit; + } + LOGE("Transfer attempt %d failed\n", attempt + 1); + + if (attempt + 1 == XFER_ATTEMPTS) { + retval = -EIO; + goto exit; + } + + syna_pal_sleep_ms(20); + } + +exit: + syna_pal_mutex_unlock(&bus->io_mutex); + + return retval; +} + +/** + * syna_i2c_write() + * + * TouchCom over I2C uses the normal I2C addressing and transaction direction + * mechanisms to select the device and send the data to the device. + * + * @param + * [ in] hw_if: the handle of hw interface + * [ in] wr_data: written data + * [ in] wr_len: length of written data in bytes + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_i2c_write(struct syna_hw_interface *hw_if, + unsigned char *wr_data, unsigned int wr_len) +{ + int retval; + unsigned int attempt; + struct i2c_msg msg; + struct i2c_client *i2c = hw_if->pdev; + struct syna_hw_bus_data *bus = &hw_if->bdata_io; + + if (!i2c) { + LOGE("Invalid bus io device\n"); + return -EINVAL; + } + + syna_pal_mutex_lock(&bus->io_mutex); + + msg.addr = i2c->addr; + msg.flags = 0; + msg.len = wr_len; + msg.buf = wr_data; + + for (attempt = 0; attempt < XFER_ATTEMPTS; attempt++) { + if (i2c_transfer(i2c->adapter, &msg, 1) == 1) { + retval = wr_len; + goto exit; + } + LOGE("Transfer attempt %d failed\n", attempt + 1); + + if (attempt + 1 == XFER_ATTEMPTS) { + retval = -EIO; + goto exit; + } + + syna_pal_sleep_ms(20); + } + +exit: + syna_pal_mutex_unlock(&bus->io_mutex); + + return retval; +} + + +/** + * syna_hw_interface + * + * Provide the hardware specific settings in defaults. + * Be noted the followings could be changed after .dtsi is parsed + */ +static struct syna_hw_interface syna_i2c_hw_if = { + .bdata_io = { + .type = BUS_TYPE_I2C, + .rd_chunk_size = RD_CHUNK_SIZE, + .wr_chunk_size = WR_CHUNK_SIZE, + }, + .bdata_attn = { + .irq_enabled = false, + .irq_on_state = 0, + }, + .bdata_rst = { + .reset_on_state = 0, + .reset_delay_ms = 200, + .reset_active_ms = 20, + }, + .bdata_pwr = { + .power_on_state = 1, + .power_on_delay_ms = 200, + }, + .ops_power_on = syna_i2c_enable_regulator, + .ops_hw_reset = syna_i2c_hw_reset, + .ops_read_data = syna_i2c_read, + .ops_write_data = syna_i2c_write, + .ops_enable_irq = syna_i2c_enable_irq, +}; + +/** + * syna_i2c_probe() + * + * Prepare the specific hardware interface and register the platform i2c device + * + * @param + * [ in] i2c: i2c client device + * [ in] dev_id: i2c device id + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *dev_id) +{ + int retval; + struct syna_hw_attn_data *attn = &syna_i2c_hw_if.bdata_attn; + struct syna_hw_bus_data *bus = &syna_i2c_hw_if.bdata_io; + + /* allocate an i2c platform device */ + syna_i2c_device = platform_device_alloc(PLATFORM_DRIVER_NAME, 0); + if (!syna_i2c_device) { + LOGE("Fail to allocate platform device\n"); + return _ENODEV; + } + +#ifdef CONFIG_OF + syna_i2c_parse_dt(&syna_i2c_hw_if, &i2c->dev); +#endif + + syna_pal_mutex_alloc(&attn->irq_en_mutex); + syna_pal_mutex_alloc(&bus->io_mutex); + + + /* keep the i/o device */ + syna_i2c_hw_if.pdev = i2c; + + syna_i2c_device->dev.parent = &i2c->dev; + syna_i2c_device->dev.platform_data = &syna_i2c_hw_if; + + /* enable the regulators */ + retval = syna_i2c_get_regulator(&syna_i2c_hw_if, true); + if (retval < 0) + return retval; + + /* initialize the gpio pins */ + retval = syna_i2c_config_gpio(&syna_i2c_hw_if); + if (retval < 0) { + LOGE("Fail to config gpio\n"); + return retval; + } + + /* register the i2c platform device */ + retval = platform_device_add(syna_i2c_device); + if (retval < 0) { + LOGE("Fail to add platform device\n"); + return retval; + } + + return 0; +} + +/** + * syna_i2c_remove() + * + * Unregister the platform i2c device + * + * @param + * [ in] i2c: i2c client device + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_i2c_remove(struct i2c_client *i2c) +{ + struct syna_hw_attn_data *attn = &syna_i2c_hw_if.bdata_attn; + struct syna_hw_pwr_data *pwr = &syna_i2c_hw_if.bdata_pwr; + struct syna_hw_rst_data *rst = &syna_i2c_hw_if.bdata_rst; + struct syna_hw_bus_data *bus = &syna_i2c_hw_if.bdata_io; + + /* disable gpios */ + if (pwr->avdd_gpio >= 0) + syna_i2c_request_gpio(pwr->avdd_gpio, false, 0, 0, NULL); + if (pwr->vdd_gpio >= 0) + syna_i2c_request_gpio(pwr->vdd_gpio, false, 0, 0, NULL); + if (rst->reset_gpio >= 0) + syna_i2c_request_gpio(rst->reset_gpio, false, 0, 0, NULL); + if (attn->irq_gpio >= 0) + syna_i2c_request_gpio(attn->irq_gpio, false, 0, 0, NULL); + + /* disable the regulators */ + syna_i2c_get_regulator(&syna_i2c_hw_if, false); + + syna_pal_mutex_free(&attn->irq_en_mutex); + syna_pal_mutex_free(&bus->io_mutex); + + /* remove the platform device */ + syna_i2c_device->dev.platform_data = NULL; + platform_device_unregister(syna_i2c_device); + + return 0; +} + +/** + * Describe an i2c device driver and its related declarations + */ +static const struct i2c_device_id syna_i2c_id_table[] = { + {I2C_MODULE_NAME, 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, syna_i2c_id_table); + +#ifdef CONFIG_OF +static const struct of_device_id syna_i2c_of_match_table[] = { + { + .compatible = "synaptics,tcm-i2c", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, syna_i2c_of_match_table); +#else +#define syna_i2c_of_match_table NULL +#endif + +static struct i2c_driver syna_i2c_driver = { + .driver = { + .name = I2C_MODULE_NAME, + .owner = THIS_MODULE, + .of_match_table = syna_i2c_of_match_table, + }, + .probe = syna_i2c_probe, + .remove = syna_i2c_remove, + .id_table = syna_i2c_id_table, +}; + + +/** + * syna_hw_interface_init() + * + * Initialize the lower-level hardware interface module. + * After returning, the handle of hw interface should be ready. + * + * @param + * void + * + * @return + * on success, 0; otherwise, negative value on error. + */ +int syna_hw_interface_init(void) +{ + return i2c_add_driver(&syna_i2c_driver); +} + +/** + * syna_hw_interface_exit() + * + * Delete the lower-level hardware interface module + * + * @param + * void + * + * @return + * none. + */ +void syna_hw_interface_exit(void) +{ + i2c_del_driver(&syna_i2c_driver); +} + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics TouchCom I2C Bus Module"); +MODULE_LICENSE("GPL v2"); + diff --git a/syna_tcm2_platform_spi.c b/syna_tcm2_platform_spi.c new file mode 100644 index 0000000..e69a968 --- /dev/null +++ b/syna_tcm2_platform_spi.c @@ -0,0 +1,1058 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Synaptics TouchCom touchscreen driver + * + * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +/** + * @file syna_tcm2_platform_spi.c + * + * This file is the reference code of I2C module used for communicating with + * Synaptics TouchCom device using I2C + */ + +#include + +#include "syna_tcm2.h" +#include "syna_tcm2_platform.h" + +#define SPI_MODULE_NAME "synaptics_tcm_spi" + +static unsigned char *buf; + +static unsigned int buf_size; + +static struct spi_transfer *xfer; + +static struct platform_device *syna_spi_device; + + +/** + * syna_request_managed_device() + * + * Request and return the device pointer for managed + * + * @param + * none. + * + * @return + * a device pointer allocated previously + */ +#if defined(DEV_MANAGED_API) || defined(USE_DRM_PANEL_NOTIFIER) +struct device *syna_request_managed_device(void) +{ + if (!syna_spi_device) + return NULL; + + return syna_spi_device->dev.parent; +} +#endif + +/** + * syna_spi_hw_reset() + * + * Toggle the hardware gpio pin to perform the chip reset + * + * @param + * [ in] hw_if: the handle of hw interface + * + * @return + * none. + */ +static void syna_spi_hw_reset(struct syna_hw_interface *hw_if) +{ + struct syna_hw_rst_data *rst = &hw_if->bdata_rst; + + if (rst->reset_gpio >= 0) { + gpio_set_value(rst->reset_gpio, rst->reset_on_state); + syna_pal_sleep_ms(rst->reset_active_ms); + gpio_set_value(rst->reset_gpio, !rst->reset_on_state); + syna_pal_sleep_ms(rst->reset_delay_ms); + } +} + +/** + * syna_spi_request_gpio() + * + * Setup the given gpio + * + * @param + * [ in] gpio: the target gpio + * [ in] config: '1' for setting up, and '0' to release the gpio + * [ in] dir: default direction of gpio + * [ in] state: default state of gpio + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_spi_request_gpio(int gpio, bool config, int dir, + int state, char *label) +{ + int retval; +#ifdef DEV_MANAGED_API + struct device *dev = syna_request_managed_device(); + + if (!dev) { + LOGE("Invalid managed device\n"); + return -ENODEV; + } +#endif + + if (config) { + retval = snprintf(label, 16, "tcm_gpio_%d\n", gpio); + if (retval < 0) { + LOGE("Fail to set GPIO label\n"); + return retval; + } +#ifdef DEV_MANAGED_API + retval = devm_gpio_request(dev, gpio, label); +#else /* Legacy API */ + retval = gpio_request(gpio, label); +#endif + if (retval < 0) { + LOGE("Fail to request GPIO %d\n", gpio); + return retval; + } + + if (dir == 0) + retval = gpio_direction_input(gpio); + else + retval = gpio_direction_output(gpio, state); + + if (retval < 0) { + LOGE("Fail to set GPIO %d direction\n", gpio); + return retval; + } + } else { +#ifdef DEV_MANAGED_API + devm_gpio_free(dev, gpio); +#else /* Legacy API */ + gpio_free(gpio); +#endif + } + + return 0; +} + +/** + * syna_spi_config_gpio() + * + * Initialize the GPIOs defined in device tree + * + * @param + * [ in] hw_if: the handle of hw interface + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_spi_config_gpio(struct syna_hw_interface *hw_if) +{ + int retval; + static char str_irq_gpio[32] = {0}; + static char str_rst_gpio[32] = {0}; + static char str_vdd_gpio[32] = {0}; + static char str_avdd_gpio[32] = {0}; + struct syna_hw_attn_data *attn = &hw_if->bdata_attn; + struct syna_hw_pwr_data *pwr = &hw_if->bdata_pwr; + struct syna_hw_rst_data *rst = &hw_if->bdata_rst; + + if (attn->irq_gpio >= 0) { + retval = syna_spi_request_gpio(attn->irq_gpio, + true, 0, 0, str_irq_gpio); + if (retval < 0) { + LOGE("Fail to configure interrupt GPIO %d\n", + attn->irq_gpio); + goto err_set_gpio_irq; + } + } + + if (rst->reset_gpio >= 0) { + retval = syna_spi_request_gpio(rst->reset_gpio, + true, 1, !rst->reset_on_state, + str_rst_gpio); + if (retval < 0) { + LOGE("Fail to configure reset GPIO %d\n", + rst->reset_gpio); + goto err_set_gpio_reset; + } + } + + if (pwr->vdd_gpio >= 0) { + retval = syna_spi_request_gpio(pwr->vdd_gpio, + true, 1, !pwr->power_on_state, + str_vdd_gpio); + if (retval < 0) { + LOGE("Fail to configure vdd GPIO %d\n", + pwr->vdd_gpio); + goto err_set_gpio_vdd; + } + } + + if (pwr->avdd_gpio >= 0) { + retval = syna_spi_request_gpio(pwr->avdd_gpio, + true, 1, !pwr->power_on_state, + str_avdd_gpio); + if (retval < 0) { + LOGE("Fail to configure avdd GPIO %d\n", + pwr->avdd_gpio); + goto err_set_gpio_avdd; + } + } + return 0; + +err_set_gpio_avdd: + if (pwr->vdd_gpio >= 0) + syna_spi_request_gpio(pwr->vdd_gpio, false, 0, 0, NULL); +err_set_gpio_vdd: + if (rst->reset_gpio >= 0) + syna_spi_request_gpio(rst->reset_gpio, false, 0, 0, NULL); +err_set_gpio_reset: + if (attn->irq_gpio >= 0) + syna_spi_request_gpio(attn->irq_gpio, false, 0, 0, NULL); +err_set_gpio_irq: + return retval; +} + +/** + * syna_spi_enable_regulator() + * + * Enable or disable the regulator + * + * @param + * [ in] hw_if: the handle of hw interface + * [ in] en: '1' for enabling, and '0' for disabling + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_spi_enable_regulator(struct syna_hw_interface *hw_if, + bool en) +{ + int retval; + struct syna_hw_pwr_data *pwr = &hw_if->bdata_pwr; + struct regulator *vdd_reg = pwr->vdd_reg_dev; + struct regulator *avdd_reg = pwr->avdd_reg_dev; + + if (!en) { + retval = 0; + goto disable_pwr_reg; + } + + if (vdd_reg) { + retval = regulator_enable(vdd_reg); + if (retval < 0) { + LOGE("Fail to enable vdd regulator\n"); + goto exit; + } + } + + if (avdd_reg) { + retval = regulator_enable(avdd_reg); + if (retval < 0) { + LOGE("Fail to enable avdd regulator\n"); + goto disable_avdd_reg; + } + syna_pal_sleep_ms(pwr->power_on_delay_ms); + } + + return 0; + +disable_pwr_reg: + if (vdd_reg) + regulator_disable(vdd_reg); + +disable_avdd_reg: + if (avdd_reg) + regulator_disable(avdd_reg); + +exit: + return retval; +} + +/** + * syna_spi_get_regulator() + * + * Acquire or release the regulator + * + * @param + * [ in] hw_if: the handle of hw interface + * [ in] get: '1' for getting the regulator, and '0' for removing + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_spi_get_regulator(struct syna_hw_interface *hw_if, + bool get) +{ + int retval; + struct device *dev = syna_spi_device->dev.parent; + struct syna_hw_pwr_data *pwr = &hw_if->bdata_pwr; + + if (!get) { + retval = 0; + goto regulator_put; + } + + if (pwr->vdd_reg_name != NULL && *pwr->vdd_reg_name != 0) { +#ifdef DEV_MANAGED_API + pwr->vdd_reg_dev = devm_regulator_get(dev, pwr->vdd_reg_name); +#else /* Legacy API */ + pwr->vdd_reg_dev = regulator_get(dev, pwr->vdd_reg_name); +#endif + if (IS_ERR((struct regulator *)pwr->vdd_reg_dev)) { + LOGW("Vdd regulator is not ready\n"); + retval = PTR_ERR((struct regulator *)pwr->vdd_reg_dev); + goto exit; + } + } + + if (pwr->avdd_reg_name != NULL && *pwr->avdd_reg_name != 0) { +#ifdef DEV_MANAGED_API + pwr->avdd_reg_dev = devm_regulator_get(dev, pwr->avdd_reg_name); +#else /* Legacy API */ + pwr->avdd_reg_dev = regulator_get(dev, pwr->avdd_reg_name); +#endif + if (IS_ERR((struct regulator *)pwr->avdd_reg_dev)) { + LOGW("AVdd regulator is not ready\n"); + retval = PTR_ERR((struct regulator *)pwr->avdd_reg_dev); + goto regulator_vdd_put; + } + } + + return 0; + +regulator_put: + if (pwr->vdd_reg_dev) { +#ifdef DEV_MANAGED_API + devm_regulator_put(pwr->vdd_reg_dev); +#else /* Legacy API */ + regulator_put(pwr->vdd_reg_dev); +#endif + pwr->vdd_reg_dev = NULL; + } +regulator_vdd_put: + if (pwr->avdd_reg_dev) { +#ifdef DEV_MANAGED_API + devm_regulator_put(pwr->avdd_reg_dev); +#else /* Legacy API */ + regulator_put(pwr->avdd_reg_dev); +#endif + pwr->avdd_reg_dev = NULL; + } +exit: + return retval; +} + +/** + * syna_spi_enable_irq() + * + * Enable or disable the handling of interrupt + * + * @param + * [ in] hw_if: the handle of hw interface + * [ in] en: '1' for enabling, and '0' for disabling + * + * @return + * 0 on success; otherwise, on error. + */ +static int syna_spi_enable_irq(struct syna_hw_interface *hw_if, + bool en) +{ + int retval = 0; + struct syna_hw_attn_data *attn = &hw_if->bdata_attn; + + if (attn->irq_id == 0) + return 0; + + syna_pal_mutex_lock(&attn->irq_en_mutex); + + /* enable the handling of interrupt */ + if (en) { + if (attn->irq_enabled) { + LOGI("Interrupt already enabled\n"); + retval = 0; + goto exit; + } + + enable_irq(attn->irq_id); + attn->irq_enabled = true; + + LOGD("irq enabled\n"); + } + /* disable the handling of interrupt */ + else { + if (!attn->irq_enabled) { + LOGI("Interrupt already disabled\n"); + retval = 0; + goto exit; + } + + disable_irq_nosync(attn->irq_id); + attn->irq_enabled = false; + + LOGD("irq disabled\n"); + } + +exit: + syna_pal_mutex_unlock(&attn->irq_en_mutex); + + return retval; +} + + +/** + * syna_spi_parse_dt() + * + * Parse and obtain board specific data from the device tree source file. + * Keep the data in structure syna_tcm_hw_data for later using. + * + * @param + * [ in] hw_if: the handle of hw interface + * [ in] dev: device model + * + * @return + * on success, 0; otherwise, negative value on error. + */ +#ifdef CONFIG_OF +static int syna_spi_parse_dt(struct syna_hw_interface *hw_if, + struct device *dev) +{ + int retval; + u32 value; + struct property *prop; + struct device_node *np = dev->of_node; + const char *name; + struct syna_hw_attn_data *attn = &hw_if->bdata_attn; + struct syna_hw_pwr_data *pwr = &hw_if->bdata_pwr; + struct syna_hw_rst_data *rst = &hw_if->bdata_rst; + struct syna_hw_bus_data *bus = &hw_if->bdata_io; + + prop = of_find_property(np, "synaptics,irq-gpio", NULL); + if (prop && prop->length) { + attn->irq_gpio = of_get_named_gpio_flags(np, + "synaptics,irq-gpio", 0, + (enum of_gpio_flags *)&attn->irq_flags); + } else { + attn->irq_gpio = -1; + } + + retval = of_property_read_u32(np, "synaptics,irq-on-state", &value); + if (retval < 0) + attn->irq_on_state = 0; + else + attn->irq_on_state = value; + + retval = of_property_read_string(np, "synaptics,avdd-name", &name); + if (retval < 0) + pwr->avdd_reg_name = NULL; + else + pwr->avdd_reg_name = name; + + retval = of_property_read_string(np, "synaptics,vdd-name", &name); + if (retval < 0) + pwr->vdd_reg_name = NULL; + else + pwr->vdd_reg_name = name; + + prop = of_find_property(np, "synaptics,vdd-gpio", NULL); + if (prop && prop->length) { + pwr->vdd_gpio = of_get_named_gpio_flags(np, + "synaptics,vdd-gpio", 0, NULL); + } else { + pwr->vdd_gpio = -1; + } + + prop = of_find_property(np, "synaptics,avdd-gpio", NULL); + if (prop && prop->length) { + pwr->avdd_gpio = of_get_named_gpio_flags(np, + "synaptics,avdd-gpio", 0, NULL); + } else { + pwr->avdd_gpio = -1; + } + + prop = of_find_property(np, "synaptics,power-on-state", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,power-on-state", + &value); + if (retval < 0) { + LOGE("Fail to read power-on-state property\n"); + return retval; + } + + pwr->power_on_state = value; + + } else { + pwr->power_on_state = 0; + } + + prop = of_find_property(np, "synaptics,power-delay-ms", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,power-delay-ms", + &value); + if (retval < 0) { + LOGE("Fail to read power-delay-ms property\n"); + return retval; + } + + pwr->power_on_delay_ms = value; + + } else { + pwr->power_on_delay_ms = 0; + } + + prop = of_find_property(np, "synaptics,reset-gpio", NULL); + if (prop && prop->length) { + rst->reset_gpio = of_get_named_gpio_flags(np, + "synaptics,reset-gpio", 0, NULL); + } else { + rst->reset_gpio = -1; + } + + prop = of_find_property(np, "synaptics,reset-on-state", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,reset-on-state", + &value); + if (retval < 0) { + LOGE("Fail to read reset-on-state property\n"); + return retval; + } + + rst->reset_on_state = value; + + } else { + rst->reset_on_state = 0; + } + + prop = of_find_property(np, "synaptics,reset-active-ms", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,reset-active-ms", + &value); + if (retval < 0) { + LOGE("Fail to read reset-active-ms property\n"); + return retval; + } + + rst->reset_active_ms = value; + + } else { + rst->reset_active_ms = 0; + } + + prop = of_find_property(np, "synaptics,reset-delay-ms", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,reset-delay-ms", + &value); + if (retval < 0) { + LOGE("Fail to read reset-delay-ms property\n"); + return retval; + } + + rst->reset_delay_ms = value; + + } else { + rst->reset_delay_ms = 0; + } + + prop = of_find_property(np, "synaptics,spi-byte-delay-us", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, + "synaptics,spi-byte-delay-us", &value); + if (retval < 0) { + LOGE("Fail to read byte-delay-us property\n"); + return retval; + } + + bus->spi_byte_delay_us = value; + + } else { + bus->spi_byte_delay_us = 0; + } + + prop = of_find_property(np, "synaptics,spi-block-delay-us", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, + "synaptics,spi-block-delay-us", &value); + if (retval < 0) { + LOGE("Fail to read block-delay-us property\n"); + return retval; + } + bus->spi_block_delay_us = value; + + } else { + bus->spi_block_delay_us = 0; + } + + prop = of_find_property(np, "synaptics,spi-mode", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,spi-mode", + &value); + if (retval < 0) { + LOGE("Fail to read synaptics,spi-mode property\n"); + return retval; + } + + bus->spi_mode = value; + + } else { + bus->spi_mode = 0; + } + + + return 0; +} +#endif + +/** + * syna_tcm_spi_alloc_mem() + * + * Manage and allocate the memory to buf being as a temporary buffer for IO + * + * @param + * [ in] count: number of spi_transfer structures to send + * [ in] size: size of temporary buffer + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_spi_alloc_mem(unsigned int count, unsigned int size) +{ + static unsigned int xfer_count; + + if (count > xfer_count) { + syna_pal_mem_free((void *)xfer); + xfer = syna_pal_mem_alloc(count, sizeof(*xfer)); + if (!xfer) { + LOGE("Fail to allocate memory for xfer\n"); + xfer_count = 0; + return -ENOMEM; + } + xfer_count = count; + } else { + syna_pal_mem_set(xfer, 0, count * sizeof(*xfer)); + } + + if (size > buf_size) { + if (buf_size) + syna_pal_mem_free((void *)buf); + buf = syna_pal_mem_alloc(size, sizeof(unsigned char)); + if (!buf) { + LOGE("Fail to allocate memory for buf\n"); + buf_size = 0; + return -ENOMEM; + } + buf_size = size; + } + + return 0; +} + + +/** + * syna_spi_read() + * + * TouchCom over SPI requires the host to assert the SSB signal to address + * the device and retrieve the data. + * + * @param + * [ in] hw_if: the handle of hw interface + * [out] rd_data: buffer for storing data retrieved from device + * [ in] rd_len: number of bytes retrieved from device + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_spi_read(struct syna_hw_interface *hw_if, + unsigned char *rd_data, unsigned int rd_len) +{ + int retval; + unsigned int idx; + struct spi_message msg; + struct spi_device *spi = hw_if->pdev; + struct syna_hw_bus_data *bus = &hw_if->bdata_io; + + if (!spi) { + LOGE("Invalid bus io device\n"); + return -EINVAL; + } + + syna_pal_mutex_lock(&bus->io_mutex); + + spi_message_init(&msg); + + if (bus->spi_byte_delay_us == 0) + retval = syna_spi_alloc_mem(1, rd_len); + else + retval = syna_spi_alloc_mem(rd_len, 1); + if (retval < 0) { + LOGE("Fail to allocate memory\n"); + goto exit; + } + + if (bus->spi_byte_delay_us == 0) { + syna_pal_mem_set(buf, 0xff, rd_len); + xfer[0].len = rd_len; + xfer[0].tx_buf = buf; + xfer[0].rx_buf = rd_data; + if (bus->spi_block_delay_us) + xfer[0].delay_usecs = bus->spi_block_delay_us; + spi_message_add_tail(&xfer[0], &msg); + } else { + buf[0] = 0xff; + for (idx = 0; idx < rd_len; idx++) { + xfer[idx].len = 1; + xfer[idx].tx_buf = buf; + xfer[idx].rx_buf = &rd_data[idx]; + xfer[idx].delay_usecs = bus->spi_byte_delay_us; + if (bus->spi_block_delay_us && (idx == rd_len - 1)) + xfer[idx].delay_usecs = bus->spi_block_delay_us; + spi_message_add_tail(&xfer[idx], &msg); + } + } + + retval = spi_sync(spi, &msg); + if (retval != 0) { + LOGE("Failed to complete SPI transfer, error = %d\n", retval); + goto exit; + } + + retval = rd_len; + +exit: + syna_pal_mutex_unlock(&bus->io_mutex); + + return retval; +} + +/** + * syna_spi_write() + * + * TouchCom over SPI requires the host to assert the SSB signal to address + * the device and send the data to the device. + * + * @param + * [ in] hw_if: the handle of hw interface + * [ in] wr_data: written data + * [ in] wr_len: length of written data in bytes + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_spi_write(struct syna_hw_interface *hw_if, + unsigned char *wr_data, unsigned int wr_len) +{ + int retval; + unsigned int idx; + struct spi_message msg; + struct spi_device *spi = hw_if->pdev; + struct syna_hw_bus_data *bus = &hw_if->bdata_io; + + if (!spi) { + LOGE("Invalid bus io device\n"); + return -EINVAL; + } + + syna_pal_mutex_lock(&bus->io_mutex); + + spi_message_init(&msg); + + if (bus->spi_byte_delay_us == 0) + retval = syna_spi_alloc_mem(1, 0); + else + retval = syna_spi_alloc_mem(wr_len, 0); + if (retval < 0) { + LOGE("Failed to allocate memory\n"); + goto exit; + } + + if (bus->spi_byte_delay_us == 0) { + xfer[0].len = wr_len; + xfer[0].tx_buf = wr_data; + if (bus->spi_block_delay_us) + xfer[0].delay_usecs = bus->spi_block_delay_us; + spi_message_add_tail(&xfer[0], &msg); + } else { + for (idx = 0; idx < wr_len; idx++) { + xfer[idx].len = 1; + xfer[idx].tx_buf = &wr_data[idx]; + xfer[idx].delay_usecs = bus->spi_byte_delay_us; + if (bus->spi_block_delay_us && (idx == wr_len - 1)) + xfer[idx].delay_usecs = bus->spi_block_delay_us; + spi_message_add_tail(&xfer[idx], &msg); + } + } + + retval = spi_sync(spi, &msg); + if (retval != 0) { + LOGE("Fail to complete SPI transfer, error = %d\n", retval); + goto exit; + } + + retval = wr_len; + +exit: + syna_pal_mutex_unlock(&bus->io_mutex); + + return retval; +} + + +/** + * syna_hw_interface + * + * Provide the hardware specific settings in defaults. + * Be noted the followings could be changed after .dtsi is parsed + */ +static struct syna_hw_interface syna_spi_hw_if = { + .bdata_io = { + .type = BUS_TYPE_SPI, + .rd_chunk_size = RD_CHUNK_SIZE, + .wr_chunk_size = WR_CHUNK_SIZE, + }, + .bdata_attn = { + .irq_enabled = false, + .irq_on_state = 0, + }, + .bdata_rst = { + .reset_on_state = 0, + .reset_delay_ms = 200, + .reset_active_ms = 20, + }, + .bdata_pwr = { + .power_on_state = 1, + .power_on_delay_ms = 200, + }, + .ops_power_on = syna_spi_enable_regulator, + .ops_hw_reset = syna_spi_hw_reset, + .ops_read_data = syna_spi_read, + .ops_write_data = syna_spi_write, + .ops_enable_irq = syna_spi_enable_irq, +}; + +/** + * syna_spi_probe() + * + * Prepare the specific hardware interface and register the platform spi device + * + * @param + * [ in] spi: spi device + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_spi_probe(struct spi_device *spi) +{ + int retval; + struct syna_hw_attn_data *attn = &syna_spi_hw_if.bdata_attn; + struct syna_hw_bus_data *bus = &syna_spi_hw_if.bdata_io; + + if (spi->master->flags & SPI_MASTER_HALF_DUPLEX) { + LOGE("Full duplex not supported by host\n"); + return -EIO; + } + + /* allocate an spi platform device */ + syna_spi_device = platform_device_alloc(PLATFORM_DRIVER_NAME, 0); + if (!syna_spi_device) { + LOGE("Fail to allocate platform device\n"); + return _ENODEV; + } + +#ifdef CONFIG_OF + syna_spi_parse_dt(&syna_spi_hw_if, &spi->dev); +#endif + + syna_pal_mutex_alloc(&attn->irq_en_mutex); + syna_pal_mutex_alloc(&bus->io_mutex); + + switch (bus->spi_mode) { + case 0: + spi->mode = SPI_MODE_0; + break; + case 1: + spi->mode = SPI_MODE_1; + break; + case 2: + spi->mode = SPI_MODE_2; + break; + case 3: + spi->mode = SPI_MODE_3; + break; + } + + /* keep the i/o device */ + syna_spi_hw_if.pdev = spi; + + syna_spi_device->dev.parent = &spi->dev; + syna_spi_device->dev.platform_data = &syna_spi_hw_if; + + spi->bits_per_word = 8; + + /* set up spi driver */ + retval = spi_setup(spi); + if (retval < 0) { + LOGE("Fail to set up SPI protocol driver\n"); + return retval; + } + + /* enable the regulators */ + retval = syna_spi_get_regulator(&syna_spi_hw_if, true); + if (retval < 0) + return retval; + + /* initialize the gpio pins */ + retval = syna_spi_config_gpio(&syna_spi_hw_if); + if (retval < 0) { + LOGE("Fail to config gpio\n"); + return retval; + } + + /* register the spi platform device */ + retval = platform_device_add(syna_spi_device); + if (retval < 0) { + LOGE("Fail to add platform device\n"); + return retval; + } + + return 0; +} + +/** + * syna_spi_remove() + * + * Unregister the platform spi device + * + * @param + * [ in] spi: spi device + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_spi_remove(struct spi_device *spi) +{ + struct syna_hw_attn_data *attn = &syna_spi_hw_if.bdata_attn; + struct syna_hw_pwr_data *pwr = &syna_spi_hw_if.bdata_pwr; + struct syna_hw_rst_data *rst = &syna_spi_hw_if.bdata_rst; + struct syna_hw_bus_data *bus = &syna_spi_hw_if.bdata_io; + + /* disable gpios */ + if (pwr->avdd_gpio >= 0) + syna_spi_request_gpio(pwr->avdd_gpio, false, 0, 0, NULL); + if (pwr->vdd_gpio >= 0) + syna_spi_request_gpio(pwr->vdd_gpio, false, 0, 0, NULL); + if (rst->reset_gpio >= 0) + syna_spi_request_gpio(rst->reset_gpio, false, 0, 0, NULL); + if (attn->irq_gpio >= 0) + syna_spi_request_gpio(attn->irq_gpio, false, 0, 0, NULL); + + /* disable the regulators */ + syna_spi_get_regulator(&syna_spi_hw_if, false); + + syna_pal_mutex_free(&attn->irq_en_mutex); + syna_pal_mutex_free(&bus->io_mutex); + + /* remove the platform device */ + syna_spi_device->dev.platform_data = NULL; + platform_device_unregister(syna_spi_device); + + return 0; +} + +/** + * Describe an spi device driver and its related declarations + */ +static const struct spi_device_id syna_spi_id_table[] = { + {SPI_MODULE_NAME, 0}, + {}, +}; +MODULE_DEVICE_TABLE(spi, syna_spi_id_table); + +#ifdef CONFIG_OF +static const struct of_device_id syna_spi_of_match_table[] = { + { + .compatible = "synaptics,tcm-spi", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, syna_spi_of_match_table); +#else +#define syna_spi_of_match_table NULL +#endif + +static struct spi_driver syna_spi_driver = { + .driver = { + .name = SPI_MODULE_NAME, + .owner = THIS_MODULE, + .of_match_table = syna_spi_of_match_table, + }, + .probe = syna_spi_probe, + .remove = syna_spi_remove, + .id_table = syna_spi_id_table, +}; + + +/** + * syna_hw_interface_init() + * + * Initialize the lower-level hardware interface module. + * After returning, the handle of hw interface should be ready. + * + * @param + * void + * + * @return + * on success, 0; otherwise, negative value on error. + */ +int syna_hw_interface_init(void) +{ + return spi_register_driver(&syna_spi_driver); +} + +/** + * syna_hw_interface_exit() + * + * Delete the lower-level hardware interface module + * + * @param + * void + * + * @return + * none. + */ +void syna_hw_interface_exit(void) +{ + syna_pal_mem_free((void *)buf); + + syna_pal_mem_free((void *)xfer); + + spi_unregister_driver(&syna_spi_driver); +} + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics TouchCom SPI Bus Module"); +MODULE_LICENSE("GPL v2"); + diff --git a/syna_tcm2_runtime.h b/syna_tcm2_runtime.h new file mode 100644 index 0000000..b5ef979 --- /dev/null +++ b/syna_tcm2_runtime.h @@ -0,0 +1,753 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Synaptics TouchCom touchscreen driver + * + * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +/** + * @file syna_tcm2_runtime.h + * + * This file abstracts platform-specific headers and C runtime APIs being used + * on the target platform. + */ + +#ifndef _SYNAPTICS_TCM2_C_RUNTIME_H_ +#define _SYNAPTICS_TCM2_C_RUNTIME_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_DRM_PANEL +#include +#elif CONFIG_FB +#include +#include +#endif +#include +#include + +/** + * @brief: DEV_MANAGED_API + * + * For linux kernel, managed interface was created for resources commonly + * used by device drivers using devres. + * + * Open if willing to use managed-APIs rather than legacy APIs. + */ +#define DEV_MANAGED_API + +#if defined(DEV_MANAGED_API) || defined(USE_DRM_PANEL_NOTIFIER) +extern struct device *syna_request_managed_device(void); +#endif + +/** + * @section: Log helpers + * + * @brief: LOGD + * Output the debug message + * + * @brief: LOGI + * Output the info message + * + * @brief: LOGN + * Output the notice message + * + * @brief: LOGW + * Output the warning message + * + * @brief: LOGE + * Output the error message + */ +#define LOGD(log, ...) \ + pr_debug("[ debug] %s: " log, __func__, ##__VA_ARGS__) +#define LOGI(log, ...) \ + pr_info("[ info] %s: " log, __func__, ##__VA_ARGS__) +#define LOGN(log, ...) \ + pr_notice("[ info] %s: " log, __func__, ##__VA_ARGS__) +#define LOGW(log, ...) \ + pr_warn("[warning] %s: " log, __func__, ##__VA_ARGS__) +#define LOGE(log, ...) \ + pr_err("[ error] %s: " log, __func__, ##__VA_ARGS__) + + +/** + * @section: Error Codes returned + * Functions usually return 0 or positive value on success. + * Thus, please defines negative value here. + */ +#define _EIO (-EIO) /* I/O errors */ +#define _ENOMEM (-ENOMEM) /* Out of memory */ +#define _EINVAL (-EINVAL) /* Invalid parameters */ +#define _ENODEV (-ENODEV) /* No such device */ +#define _ETIMEDOUT (-ETIMEDOUT) /* execution timeout */ + + +/** + * @section: Data Comparison helpers + * + * @brief: MAX + * Find the maximum value between + * + * @brief: MIN: + * Find the minimum value between + * + * @brief: GET_BIT + * Return the value of target bit + */ +#define MAX(a, b) \ + ({__typeof__(a) _a = (a); \ + __typeof__(b) _b = (b); \ + _a > _b ? _a : _b; }) + +#define MIN(a, b) \ + ({__typeof__(a) _a = (a); \ + __typeof__(b) _b = (b); \ + _a < _b ? _a : _b; }) + +#define GET_BIT(var, pos) \ + (((var) & (1 << (pos))) >> (pos)) + + +/** + * @section: C Atomic operations + * + * @brief: ATOMIC_SET + * Set an atomic data + * + * @brief: ATOMIC_GET: + * Get an atomic data + */ +typedef atomic_t syna_pal_atomic_t; + +#define ATOMIC_SET(atomic, value) \ + atomic_set(&atomic, value) + +#define ATOMIC_GET(atomic) \ + atomic_read(&atomic) + + +/** + * @section: C Integer Calculation helpers + * + * @brief: syna_pal_le2_to_uint + * Convert 2-byte data to an unsigned integer + * + * @brief: syna_pal_le4_to_uint + * Convert 4-byte data to an unsigned integer + * + * @brief: syna_pal_ceil_div + * Calculate the ceiling of the integer division + */ + +/** + * syna_pal_le2_to_uint() + * + * Convert 2-byte data in little-endianness to an unsigned integer + * + * @param + * [ in] src: 2-byte data in little-endianness + * + * @return + * an unsigned integer converted + */ +static inline unsigned int syna_pal_le2_to_uint(const unsigned char *src) +{ + return (unsigned int)src[0] + + (unsigned int)src[1] * 0x100; +} +/** + * syna_pal_le4_to_uint() + * + * Convert 4-byte data in little-endianness to an unsigned integer + * + * @param + * [ in] src: 4-byte data in little-endianness + * + * @return + * an unsigned integer converted + */ +static inline unsigned int syna_pal_le4_to_uint(const unsigned char *src) +{ + return (unsigned int)src[0] + + (unsigned int)src[1] * 0x100 + + (unsigned int)src[2] * 0x10000 + + (unsigned int)src[3] * 0x1000000; +} +/** + * syna_pal_ceil_div() + * + * Calculate the ceiling of the integer division + * + * @param + * [ in] dividend: the dividend value + * [ in] divisor: the divisor value + * + * @return + * the ceiling of the integer division + */ +static inline unsigned int syna_pal_ceil_div(unsigned int dividend, + unsigned int divisor) +{ + return (dividend + divisor - 1) / divisor; +} + + +/** + * @section: C Runtime for Memory Management helpers + * + * @brief: syna_pal_mem_calloc + * Allocate a block of memory space + * + * @brief: syna_pal_mem_free + * Deallocate a block of memory previously allocated + * + * @brief: syna_pal_mem_set + * Fill memory with a constant byte + * + * @brief: syna_pal_mem_cpy + * Ensure the safe size before doing memory copy + */ + +/** + * syna_pal_mem_calloc() + * + * Allocates a block of memory for an array of 'num' elements, + * each of them has 'size' bytes long, and initializes all its bits to zero. + * + * @param + * [ in] num: number of elements for an array + * [ in] size: number of bytes for each elements + * + * @return + * On success, a pointer to the memory block allocated by the function. + */ +static inline void *syna_pal_mem_alloc(unsigned int num, unsigned int size) +{ +#ifdef DEV_MANAGED_API + struct device *dev = syna_request_managed_device(); + + if (!dev) { + LOGE("Invalid managed device\n"); + return NULL; + } +#endif + + if ((int)(num * size) <= 0) { + LOGE("Invalid parameter\n"); + return NULL; + } + +#ifdef DEV_MANAGED_API + return devm_kcalloc(dev, num, size, GFP_KERNEL); +#else /* Legacy API */ + return kcalloc(num, size, GFP_KERNEL); +#endif +} +/** + * syna_pal_mem_free() + * + * Deallocate a block of memory previously allocated. + * + * @param + * [ in] ptr: a memory block previously allocated + * + * @return + * none. + */ +static inline void syna_pal_mem_free(void *ptr) +{ +#ifdef DEV_MANAGED_API + struct device *dev = syna_request_managed_device(); + + if (!dev) { + LOGE("Invalid managed device\n"); + return; + } + + if (ptr) + devm_kfree(dev, ptr); +#else /* Legacy API */ + kfree(ptr); +#endif +} +/** + * syna_pal_mem_set() + * + * Fill memory with a constant byte + * + * @param + * [ in] ptr: pointer to a memory block + * [ in] c: the constant value + * [ in] n: number of byte being set + * + * @return + * none. + */ +static inline void syna_pal_mem_set(void *ptr, int c, unsigned int n) +{ + memset(ptr, c, n); +} +/** + * syna_pal_mem_cpy() + * + * Ensure the safe size before copying the values of num bytes from the + * location to the memory block pointed to by destination. + * + * @param + * [out] dest: pointer to the destination space + * [ in] dest_size: size of destination array + * [ in] src: pointer to the source of data to be copied + * [ in] src_size: size of source array + * [ in] num: number of bytes to copy + * + * @return + * 0 on success; otherwise, on error. + */ +static inline int syna_pal_mem_cpy(void *dest, unsigned int dest_size, + const void *src, unsigned int src_size, unsigned int num) +{ + if (dest == NULL || src == NULL) + return -1; + + if (num > dest_size || num > src_size) { + LOGE("Invalid size. src:%d, dest:%d, num:%d\n", + src_size, dest_size, num); + return -1; + } + + memcpy((void *)dest, (const void *)src, num); + + return 0; +} + + +/** + * @section: C Runtime for Muxtex Control helpers + * + * @brief: syna_pal_mutex_alloc + * Create a mutex object + * + * @brief: syna_pal_mutex_free + * Release the mutex object previously allocated + * + * @brief: syna_pal_mutex_lock + * Lock the mutex + * + * @brief: syna_pal_mutex_unlock + * Unlock the mutex previously locked + */ + +typedef struct mutex syna_pal_mutex_t; + +/** + * syna_pal_mutex_alloc() + * + * Create a mutex object. + * + * @param + * [out] ptr: pointer to the mutex handle being allocated + * + * @return + * 0 on success; otherwise, on error. + */ +static inline int syna_pal_mutex_alloc(syna_pal_mutex_t *ptr) +{ + mutex_init((struct mutex *)ptr); + return 0; +} +/** + * syna_pal_mutex_free() + * + * Release the mutex object previously allocated. + * + * @param + * [ in] ptr: mutex handle previously allocated + * + * @return + * none. + */ +static inline void syna_pal_mutex_free(syna_pal_mutex_t *ptr) +{ + /* do nothing */ +} +/** + * syna_pal_mutex_lock() + * + * Acquire/lock the mutex. + * + * @param + * [ in] ptr: a mutex handle + * + * @return + * none. + */ +static inline void syna_pal_mutex_lock(syna_pal_mutex_t *ptr) +{ + mutex_lock((struct mutex *)ptr); +} +/** + * syna_pal_mutex_unlock() + * + * Unlock the locked mutex. + * + * @param + * [ in] ptr: a mutex handle + * + * @return + * none. + */ +static inline void syna_pal_mutex_unlock(syna_pal_mutex_t *ptr) +{ + mutex_unlock((struct mutex *)ptr); +} + + +/** + * @section: C Runtime for Completion Event + * + * @brief: syna_pal_completion_alloc + * Allocate a completion event + * + * @brief: syna_pal_completion_free + * Release the completion event previously allocated + * + * @brief: syna_pal_completion_complete + * Complete the completion event being waiting for + * + * @brief: syna_pal_completion_reset + * Reset or reinitialize the completion event + * + * @brief: syna_pal_completion_wait_for + * Wait for the completion event + */ + +typedef struct completion syna_pal_completion_t; + +/** + * syna_pal_completion_alloc() + * + * Allocate a completion event, and the default state is not set. + * Caller must reset the event before each use. + * + * @param + * [out] ptr: pointer to the completion handle being allocated + * + * @return + * 0 on success; otherwise, on error. + */ +static inline int syna_pal_completion_alloc(syna_pal_completion_t *ptr) +{ + init_completion((struct completion *)ptr); + return 0; +} +/** + * syna_pal_completion_free() + * + * Release the completion event previously allocated + * + * @param + * [ in] ptr: the completion event previously allocated + event + * @return + * none. + */ +static inline void syna_pal_completion_free(syna_pal_completion_t *ptr) +{ + /* do nothing */ +} +/** + * syna_pal_completion_complete() + * + * Complete the completion event being waiting for + * + * @param + * [ in] ptr: the completion event + * + * @return + * none. + */ +static inline void syna_pal_completion_complete(syna_pal_completion_t *ptr) +{ + complete((struct completion *)ptr); +} +/** + * syna_pal_completion_reset() + * + * Reset or reinitialize the completion event + * + * @param + * [ in] ptr: the completion event + * + * @return + * none. + */ +static inline void syna_pal_completion_reset(syna_pal_completion_t *ptr) +{ +#if (KERNEL_VERSION(3, 13, 0) > LINUX_VERSION_CODE) + init_completion((struct completion *)ptr); +#else + reinit_completion((struct completion *)ptr); +#endif +} +/** + * syna_pal_completion_wait_for() + * + * Wait for the completion event during the given time slot + * + * @param + * [ in] ptr: the completion event + * [ in] timeout_ms: time frame in milliseconds + * + * @return + * 0 if a signal is received; otherwise, on timeout or error occurs. + */ +static inline int syna_pal_completion_wait_for(syna_pal_completion_t *ptr, + unsigned int timeout_ms) +{ + int retval; + + retval = wait_for_completion_timeout((struct completion *)ptr, + msecs_to_jiffies(timeout_ms)); + if (retval == 0) /* timeout occurs */ + return -1; + + return 0; +} + + +/** + * @section: C Runtime to Pause the Execution + * + * @brief: syna_pal_sleep_ms + * Sleep for a fixed amount of time in milliseconds + * + * @brief: syna_pal_sleep_us + * Sleep for a range of time in microseconds + * + * @brief: syna_pal_busy_delay_ms + * Busy wait for a fixed amount of time in milliseconds + */ + +/** + * syna_pal_sleep_ms() + * + * Sleep for a fixed amount of time in milliseconds + * + * @param + * [ in] time_ms: time frame in milliseconds + * + * @return + * none. + */ +static inline void syna_pal_sleep_ms(int time_ms) +{ + msleep(time_ms); +} +/** + * syna_pal_sleep_us() + * + * Sleep for a range of time in microseconds + * + * @param + * [ in] time_us_min: the min. time frame in microseconds + * [ in] time_us_max: the max. time frame in microseconds + * + * @return + * none. + */ +static inline void syna_pal_sleep_us(int time_us_min, int time_us_max) +{ + usleep_range(time_us_min, time_us_max); +} +/** + * syna_pal_busy_delay_ms() + * + * Busy wait for a fixed amount of time in milliseconds + * + * @param + * [ in] time_ms: time frame in milliseconds + * + * @return + * none. + */ +static inline void syna_pal_busy_delay_ms(int time_ms) +{ + mdelay(time_ms); +} + + +/** + * @section: C Runtime for String operations + * + * @brief: syna_pal_str_len + * Return the length of C string + * + * @brief: syna_pal_str_cpy: + * Ensure the safe size before doing C string copy + * + * @brief: syna_pal_str_cmp: + * Compare whether the given C strings are equal or not + */ + +/** + * syna_pal_str_len() + * + * Return the length of C string + * + * @param + * [ in] str: an array of characters + * + * @return + * the length of given string + */ +static inline unsigned int syna_pal_str_len(const char *str) +{ + return (unsigned int)strlen(str); +} +/** + * syna_pal_str_cpy() + * + * Copy the C string pointed by source into the array pointed by destination. + * + * @param + * [ in] dest: pointer to the destination C string + * [ in] dest_size: size of destination C string + * [out] src: pointer to the source of C string to be copied + * [ in] src_size: size of source C string + * [ in] num: number of bytes to copy + * + * @return + * 0 on success; otherwise, on error. + */ +static inline int syna_pal_str_cpy(char *dest, unsigned int dest_size, + const char *src, unsigned int src_size, unsigned int num) +{ + if (dest == NULL || src == NULL) + return -1; + + if (num > dest_size || num > src_size) { + LOGE("Invalid size. src:%d, dest:%d, num:%d\n", + src_size, dest_size, num); + return -1; + } + + strncpy(dest, src, num); + + return 0; +} +/** + * syna_pal_str_cmp() + * + * Compares up to num characters of the C string str1 to those of the + * C string str2. + * + * @param + * [ in] str1: C string to be compared + * [ in] str2: C string to be compared + * [ in] num: number of characters to compare + * + * @return + * 0 if both strings are equal; otherwise, not equal. + */ +static inline int syna_pal_str_cmp(const char *str1, const char *str2, + unsigned int num) +{ + return strncmp(str1, str2, num); +} +/** + * syna_pal_hex_to_uint() + * + * Convert the given string in hex to an integer returned + * + * @param + * [ in] str: C string to be converted + * [ in] length: target length + * + * @return + * An integer converted + */ +static inline unsigned int syna_pal_hex_to_uint(char *str, int length) +{ + unsigned int result = 0; + char *ptr = NULL; + + for (ptr = str; ptr != str + length; ++ptr) { + result <<= 4; + if (*ptr >= 'A') + result += *ptr - 'A' + 10; + else + result += *ptr - '0'; + } + + return result; +} + +/** + * @section: C Runtime for Checksum Calculation + * + * @brief: syna_pal_crc32 + * Calculates the CRC32 value + */ + +/** + * syna_pal_crc32() + * + * Calculates the CRC32 value of the data + * + * @param + * [ in] seed: the previous crc32 value + * [ in] data: byte data for the calculation + * [ in] len: the byte length of the data. + * + * @return + * 0 if both strings are equal; otherwise, not equal. + */ +static inline unsigned int syna_pal_crc32(unsigned int seed, + const char *data, unsigned int len) +{ + return crc32(seed, data, len); +} + +#endif /* end of _SYNAPTICS_TCM2_C_RUNTIME_H_ */ diff --git a/syna_tcm2_sysfs.c b/syna_tcm2_sysfs.c new file mode 100644 index 0000000..84677bc --- /dev/null +++ b/syna_tcm2_sysfs.c @@ -0,0 +1,2207 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Synaptics TouchCom touchscreen driver + * + * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +/** + * @file syna_tcm2_sysfs.c + * + * This file implements cdev and ioctl interface in the reference driver. + */ + +#include + +#include "syna_tcm2.h" +#include "synaptics_touchcom_core_dev.h" +#include "synaptics_touchcom_func_base.h" +#include "synaptics_touchcom_func_touch.h" +#ifdef HAS_TESTING_FEATURE +#include "syna_tcm2_testing.h" +#endif + + +/* #define ENABLE_PID_TASK */ + +#define SIG_ATTN (46) + +/* structure for IOCTLs + */ +struct syna_ioctl_data { + unsigned int data_length; + unsigned int buf_size; + unsigned char __user *buf; +}; + +#if defined(CONFIG_COMPAT) && defined(HAVE_COMPAT_IOCTL) +struct syna_tcm_ioctl_data_compat { + unsigned int data_length; + unsigned int buf_size; + compat_uptr_t __user *buf; +}; +#endif + +/* defines the IOCTLs supported + */ +#define IOCTL_MAGIC 's' + +/* Previous IOCTLs in early driver */ +#define OLD_RESET_ID (0x00) +#define OLD_SET_IRQ_MODE_ID (0x01) +#define OLD_SET_RAW_MODE_ID (0x02) +#define OLD_CONCURRENT_ID (0x03) + +#define IOCTL_OLD_RESET \ + _IO(IOCTL_MAGIC, OLD_RESET_ID) +#define IOCTL_OLD_SET_IRQ_MODE \ + _IOW(IOCTL_MAGIC, OLD_SET_IRQ_MODE_ID, int) +#define IOCTL_OLD_SET_RAW_MODE \ + _IOW(IOCTL_MAGIC, OLD_SET_RAW_MODE_ID, int) +#define IOCTL_OLD_CONCURRENT \ + _IOW(IOCTL_MAGIC, OLD_CONCURRENT_ID, int) + +/* Standard IOCTLs in TCM2 driver */ +#define STD_IOCTL_BEGIN (0x10) +#define STD_SET_PID_ID (0x11) +#define STD_ENABLE_IRQ_ID (0x12) +#define STD_RAW_READ_ID (0x13) +#define STD_RAW_WRITE_ID (0x14) +#define STD_GET_FRAME_ID (0x15) +#define STD_SEND_MESSAGE_ID (0x16) +#define STD_SET_REPORTS_ID (0x17) +#define STD_CHECK_FRAMES_ID (0x18) +#define STD_CLEAN_OUT_FRAMES_ID (0x19) + +#define IOCTL_STD_IOCTL_BEGIN \ + _IOR(IOCTL_MAGIC, STD_IOCTL_BEGIN) +#define IOCTL_STD_SET_PID \ + _IOW(IOCTL_MAGIC, STD_SET_PID_ID, struct syna_ioctl_data *) +#define IOCTL_STD_ENABLE_IRQ \ + _IOW(IOCTL_MAGIC, STD_ENABLE_IRQ_ID, struct syna_ioctl_data *) +#define IOCTL_STD_RAW_READ \ + _IOR(IOCTL_MAGIC, STD_RAW_READ_ID, struct syna_ioctl_data *) +#define IOCTL_STD_RAW_WRITE \ + _IOW(IOCTL_MAGIC, STD_RAW_WRITE_ID, struct syna_ioctl_data *) +#define IOCTL_STD_GET_FRAME \ + _IOWR(IOCTL_MAGIC, STD_GET_FRAME_ID, struct syna_ioctl_data *) +#define IOCTL_STD_SEND_MESSAGE \ + _IOWR(IOCTL_MAGIC, STD_SEND_MESSAGE_ID, struct syna_ioctl_data *) +#define IOCTL_STD_SET_REPORT_TYPES \ + _IOW(IOCTL_MAGIC, STD_SET_REPORTS_ID, struct syna_ioctl_data *) +#define IOCTL_STD_CHECK_FRAMES \ + _IOWR(IOCTL_MAGIC, STD_CHECK_FRAMES_ID, struct syna_ioctl_data *) +#define IOCTL_STD_CLEAN_OUT_FRAMES \ + _IOWR(IOCTL_MAGIC, STD_CLEAN_OUT_FRAMES_ID, struct syna_ioctl_data *) + +/* g_sysfs_dir represents the root directory of sysfs nodes being created + */ +static struct kobject *g_sysfs_dir; + +/* g_extif_mutex is used to protect the access from the userspace application + */ +static syna_pal_mutex_t g_extif_mutex; + +/* g_cdev_buf is a temporary buffer storing the data from userspace + */ +static struct tcm_buffer g_cdev_cbuf; + +/* g_fifo_queue_mutex is used to protect the access from + * the userspace application + */ +static syna_pal_mutex_t g_fifo_queue_mutex; + +/* The g_sysfs_io_polling_interval is used to set the polling interval + * for syna_tcm_send_command from syna_cdev_ioctl_send_message. + * It will set to the mode SYSFS_FULL_INTERRUPT for using the full + * interrupt mode. The way to update this variable is through the + * syna_cdev_ioctl_enable_irq. + */ +unsigned int g_sysfs_io_polling_interval; + +/* a buffer to record the streaming report + * considering touch report and another reports may be co-enabled + * at the same time, give a little buffer here (3 sec x 300 fps) + */ +#define FIFO_QUEUE_MAX_FRAMES (1200) +#define SEND_MESSAGE_HEADER_LENGTH (3) + +/* Indicate the interrupt status especially for sysfs using */ +#define SYSFS_DISABLED_INTERRUPT (0) +#define SYSFS_ENABLED_INTERRUPT (1) + +/*Define a data structure that contains a list_head*/ +struct fifo_queue { + struct list_head next; + unsigned char *fifo_data; + unsigned int data_length; + struct timeval timestamp; +}; + + +/** + * syna_sysfs_info_show() + * + * Attribute to show the device and driver information to the console. + * + * @param + * [ in] kobj: an instance of kobj + * [ in] attr: an instance of kobj attribute structure + * [out] buf: string buffer shown on console + * + * @return + * on success, number of characters being output; + * otherwise, negative value on error. + */ +static ssize_t syna_sysfs_info_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int retval; + unsigned int count; + struct device *p_dev; + struct kobject *p_kobj; + struct syna_tcm *tcm; + struct tcm_dev *tcm_dev; + int i; + + p_kobj = g_sysfs_dir->parent; + p_dev = container_of(p_kobj, struct device, kobj); + tcm = dev_get_drvdata(p_dev); + tcm_dev = tcm->tcm_dev; + + syna_pal_mutex_lock(&g_extif_mutex); + + count = 0; + + if (!tcm->is_connected) { + retval = snprintf(buf, PAGE_SIZE - count, + "Device is NOT connected\n"); + goto exit; + } + + retval = snprintf(buf, PAGE_SIZE - count, + "Driver version: %d.%s\n", + SYNAPTICS_TCM_DRIVER_VERSION, + SYNAPTICS_TCM_DRIVER_SUBVER); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = snprintf(buf, PAGE_SIZE - count, + "Core lib version: %d.%02d\n\n", + (unsigned char)(SYNA_TCM_CORE_LIB_VERSION >> 8), + (unsigned char)SYNA_TCM_CORE_LIB_VERSION); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = snprintf(buf, PAGE_SIZE - count, + "TouchComm version: %d\n", tcm_dev->id_info.version); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + switch (tcm_dev->id_info.mode) { + case MODE_APPLICATION_FIRMWARE: + retval = snprintf(buf, PAGE_SIZE - count, + "Firmware mode: Application Firmware, 0x%02x\n", + tcm_dev->id_info.mode); + if (retval < 0) + goto exit; + break; + case MODE_BOOTLOADER: + retval = snprintf(buf, PAGE_SIZE - count, + "Firmware mode: Bootloader, 0x%02x\n", + tcm_dev->id_info.mode); + if (retval < 0) + goto exit; + break; + case MODE_ROMBOOTLOADER: + retval = snprintf(buf, PAGE_SIZE - count, + "Firmware mode: Rom Bootloader, 0x%02x\n", + tcm_dev->id_info.mode); + if (retval < 0) + goto exit; + break; + default: + retval = snprintf(buf, PAGE_SIZE - count, + "Firmware mode: Mode 0x%02x\n", + tcm_dev->id_info.mode); + if (retval < 0) + goto exit; + break; + } + buf += retval; + count += retval; + + retval = snprintf(buf, PAGE_SIZE - count, + "Part number: "); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = syna_pal_mem_cpy(buf, + PAGE_SIZE - count, + tcm_dev->id_info.part_number, + sizeof(tcm_dev->id_info.part_number), + sizeof(tcm_dev->id_info.part_number)); + if (retval < 0) { + LOGE("Fail to copy part number string\n"); + goto exit; + } + buf += sizeof(tcm_dev->id_info.part_number); + count += sizeof(tcm_dev->id_info.part_number); + + retval = snprintf(buf, PAGE_SIZE - count, "\n"); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = snprintf(buf, PAGE_SIZE - count, + "Packrat number: %d\n\n", tcm_dev->packrat_number); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + if (tcm_dev->id_info.mode != MODE_APPLICATION_FIRMWARE) { + retval = count; + goto exit; + } + + retval = snprintf(buf, PAGE_SIZE - count, "Config ID: "); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + for (i = 0; i < MAX_SIZE_CONFIG_ID; i++) { + retval = snprintf(buf, PAGE_SIZE - count, + "0x%2x ", tcm_dev->config_id[i]); + if (retval < 0) + goto exit; + buf += retval; + count += retval; + } + + retval = snprintf(buf, PAGE_SIZE - count, "\n"); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = snprintf(buf, PAGE_SIZE - count, + "Max X & Y: %d, %d\n", tcm_dev->max_x, tcm_dev->max_y); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = snprintf(buf, PAGE_SIZE - count, + "Num of objects: %d\n", tcm_dev->max_objects); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = snprintf(buf, PAGE_SIZE - count, + "Num of cols & rows: %d, %d\n", tcm_dev->cols, tcm_dev->rows); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = count; + +exit: + syna_pal_mutex_unlock(&g_extif_mutex); + return retval; +} + +static struct kobj_attribute kobj_attr_info = + __ATTR(info, 0444, syna_sysfs_info_show, NULL); + +/** + * syna_sysfs_irq_en_store() + * + * Attribute to disable/enable the irq + * + * @param + * [ in] kobj: an instance of kobj + * [ in] attr: an instance of kobj attribute structure + * [ in] buf: string buffer input + * [ in] count: size of buffer input + * + * @return + * on success, return count; otherwise, return error code + */ +static ssize_t syna_sysfs_irq_en_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int retval = 0; + unsigned int input; + struct device *p_dev; + struct kobject *p_kobj; + struct syna_tcm *tcm; + + p_kobj = g_sysfs_dir->parent; + p_dev = container_of(p_kobj, struct device, kobj); + tcm = dev_get_drvdata(p_dev); + + if (kstrtouint(buf, 10, &input)) + return -EINVAL; + + if (!tcm->hw_if->ops_enable_irq) + return 0; + + if (!tcm->is_connected) { + LOGW("Device is NOT connected\n"); + retval = count; + goto exit; + } + + syna_pal_mutex_lock(&g_extif_mutex); + + /* disable the interrupt line */ + if (input == 0) { + retval = tcm->hw_if->ops_enable_irq(tcm->hw_if, false); + if (retval < 0) { + LOGE("Fail to disable interrupt\n"); + goto exit; + } + } else if (input == 1) { + /* enable the interrupt line */ + retval = tcm->hw_if->ops_enable_irq(tcm->hw_if, true); + if (retval < 0) { + LOGE("Fail to enable interrupt\n"); + goto exit; + } + } else { + retval = -EINVAL; + goto exit; + } + + retval = count; + +exit: + syna_pal_mutex_unlock(&g_extif_mutex); + return retval; +} + +static struct kobj_attribute kobj_attr_irq_en = + __ATTR(irq_en, 0220, NULL, syna_sysfs_irq_en_store); + +/** + * syna_sysfs_reset_store() + * + * Attribute to issue a reset. + * "1" for a sw reset; "2" for a hardware reset + * + * @param + * [ in] kobj: an instance of kobj + * [ in] attr: an instance of kobj attribute structure + * [ in] buf: string buffer input + * [ in] count: size of buffer input + * + * @return + * on success, return count; otherwise, return error code + */ +static ssize_t syna_sysfs_reset_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int retval = 0; + unsigned int input; + struct device *p_dev; + struct kobject *p_kobj; + struct syna_tcm *tcm; + + p_kobj = g_sysfs_dir->parent; + p_dev = container_of(p_kobj, struct device, kobj); + tcm = dev_get_drvdata(p_dev); + + if (kstrtouint(buf, 10, &input)) + return -EINVAL; + + if (!tcm->is_connected) { + LOGW("Device is NOT connected\n"); + retval = count; + goto exit; + } + + syna_pal_mutex_lock(&g_extif_mutex); + + if (input == 1) { + retval = syna_tcm_reset(tcm->tcm_dev); + if (retval < 0) { + LOGE("Fail to do reset\n"); + goto exit; + } + } else if (input == 2) { + if (!tcm->hw_if->ops_hw_reset) + goto exit; + + tcm->hw_if->ops_hw_reset(tcm->hw_if); + } else { + goto exit; + } + + /* check the fw setup in case the settings is changed */ + if (IS_APP_FW_MODE(tcm->tcm_dev->dev_mode)) { + retval = tcm->dev_set_up_app_fw(tcm); + if (retval < 0) { + LOGE("Fail to set up app fw\n"); + goto exit; + } + } + + retval = count; + +exit: + syna_pal_mutex_unlock(&g_extif_mutex); + return retval; +} + +static struct kobj_attribute kobj_attr_reset = + __ATTR(reset, 0220, NULL, syna_sysfs_reset_store); + +/** + * declaration of sysfs attributes + */ +static struct attribute *attrs[] = { + &kobj_attr_info.attr, + &kobj_attr_irq_en.attr, + &kobj_attr_reset.attr, + NULL, +}; + +static struct attribute_group attr_group = { + .attrs = attrs, +}; + +/** + * syna_sysfs_create_dir() + * + * Create a directory and register it with sysfs. + * Then, create all defined sysfs files. + * + * @param + * [ in] tcm: the driver handle + * [ in] pdev: an instance of platform device + * + * @return + * on success, 0; otherwise, negative value on error. + */ +int syna_sysfs_create_dir(struct syna_tcm *tcm, + struct platform_device *pdev) +{ + int retval = 0; + + g_sysfs_dir = kobject_create_and_add("sysfs", + &pdev->dev.kobj); + if (!g_sysfs_dir) { + LOGE("Fail to create sysfs directory\n"); + return -ENOTDIR; + } + + tcm->sysfs_dir = g_sysfs_dir; + + retval = sysfs_create_group(g_sysfs_dir, &attr_group); + if (retval < 0) { + LOGE("Fail to create sysfs group\n"); + + kobject_put(tcm->sysfs_dir); + return retval; + } + +#ifdef HAS_TESTING_FEATURE + retval = syna_testing_create_dir(tcm, g_sysfs_dir); + if (retval < 0) { + LOGE("Fail to create testing sysfs\n"); + + sysfs_remove_group(tcm->sysfs_dir, &attr_group); + kobject_put(tcm->sysfs_dir); + return retval; + } +#endif + + return 0; +} +/** + * syna_sysfs_remove_dir() + * + * Remove the allocate sysfs directory + * + * @param + * [ in] tcm: the driver handle + * + * @return + * on success, 0; otherwise, negative value on error. + */ +void syna_sysfs_remove_dir(struct syna_tcm *tcm) +{ + if (!tcm) { + LOGE("Invalid tcm device handle\n"); + return; + } + + if (tcm->sysfs_dir) { +#ifdef HAS_TESTING_FEATURE + syna_testing_remove_dir(); +#endif + + sysfs_remove_group(tcm->sysfs_dir, &attr_group); + + kobject_put(tcm->sysfs_dir); + } + +} +#ifdef ENABLE_EXTERNAL_FRAME_PROCESS +/** + * syna_cdev_insert_fifo() + * + * Insert/Push the data to the queue. + * + * This function is called by syna_cdev_update_report_queue(), + * where the event data will be placed as the below format in byte + * and use this function to store the data in queue. + * [0 ] : status / report code + * [1 : 2 ] : length of data frame + * [3 : N + 3] : N bytes of data payload + * + * @param + * [ in] tcm: the driver handle + * [ in] buf_ptr: points to a data going to push + * [ in] length: data length + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_cdev_insert_fifo(struct syna_tcm *tcm, + unsigned char *buf_ptr, unsigned int length) +{ + int retval = 0; + struct fifo_queue *pfifo_data; + struct fifo_queue *pfifo_data_temp; + static int pre_remaining_frames = -1; + + syna_pal_mutex_lock(&g_fifo_queue_mutex); + + /* check queue buffer limit */ + if (tcm->fifo_remaining_frame >= FIFO_QUEUE_MAX_FRAMES) { + if (tcm->fifo_remaining_frame != pre_remaining_frames) + LOGI("Reached %d and drop FIFO first frame\n", + tcm->fifo_remaining_frame); + + pfifo_data_temp = list_first_entry(&tcm->frame_fifo_queue, + struct fifo_queue, next); + + list_del(&pfifo_data_temp->next); + kfree(pfifo_data_temp->fifo_data); + kfree(pfifo_data_temp); + pre_remaining_frames = tcm->fifo_remaining_frame; + tcm->fifo_remaining_frame--; + } else if (pre_remaining_frames >= FIFO_QUEUE_MAX_FRAMES) { + LOGI("Reached limit, dropped oldest frame, remaining:%d\n", + tcm->fifo_remaining_frame); + pre_remaining_frames = tcm->fifo_remaining_frame; + } + + pfifo_data = kmalloc(sizeof(*pfifo_data), GFP_KERNEL); + if (!(pfifo_data)) { + LOGE("Failed to allocate memory\n"); + LOGE("Allocation size = %zu\n", (sizeof(*pfifo_data))); + retval = -ENOMEM; + goto exit; + } + + pfifo_data->fifo_data = kmalloc(length, GFP_KERNEL); + if (!(pfifo_data->fifo_data)) { + LOGE("Failed to allocate memory, size = %d\n", length); + retval = -ENOMEM; + goto exit; + } + + pfifo_data->data_length = length; + + memcpy((void *)pfifo_data->fifo_data, (void *)buf_ptr, length); + do_gettimeofday(&(pfifo_data->timestamp)); + /* append the data to the tail for FIFO queueing */ + list_add_tail(&pfifo_data->next, &tcm->frame_fifo_queue); + tcm->fifo_remaining_frame++; + retval = 0; + +exit: + syna_pal_mutex_unlock(&g_fifo_queue_mutex); + return retval; +} +#endif +/** + * syna_cdev_ioctl_check_frame() + * + * Check the queuing status and wait for the data if it's empty. + * + * @param + * [ in] tcm: the driver handle + * [ in/out] ubuf_ptr: points to a memory space from userspace + * [ in] buf_size: size of given space + * [ in] data_size: timeout value for queue waiting + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_cdev_ioctl_check_frame(struct syna_tcm *tcm, + const unsigned char *ubuf_ptr, unsigned int buf_size, + unsigned int data_size) +{ + int retval = 0; + int result = 0; + unsigned int timeout = 0; + unsigned int frames = 0; + unsigned char data[4] = {0}; + + if (!tcm->is_connected) { + LOGE("Not connected\n"); + return _EINVAL; + } + + if (buf_size < sizeof(data) || data_size < sizeof(data)) { + LOGE("Invalid sync data size, buf_size: %u\n", buf_size); + retval = -EINVAL; + goto exit; + } + + result = copy_from_user(data, ubuf_ptr, + sizeof(data)); + if (result) { + LOGE("Fail to copy data from user space\n"); + retval = -EBADE; + goto exit; + } + + /* Parese the waiting duration length */ + timeout = syna_pal_le4_to_uint(&data[0]); + LOGD("Time out: %d\n", timeout); + + if (list_empty(&tcm->frame_fifo_queue)) { + LOGD("The queue is empty, wait for the frames\n"); + result = wait_event_interruptible_timeout(tcm->wait_frame, + (tcm->fifo_remaining_frame > 0), + msecs_to_jiffies(timeout)); + if (result == 0) { + LOGD("Queue waiting timed out after %dms\n", timeout); + retval = -ETIMEDOUT; + goto exit; + } + LOGD("Data queued\n"); + retval = data_size; + } else { + LOGD("Queue is not empty\n"); + retval = data_size; + } + +exit: + if (retval > 0) { + frames = tcm->fifo_remaining_frame; + data[0] = (unsigned char)(frames & 0xff); + data[1] = (unsigned char)((frames >> 8) & 0xff); + data[2] = (unsigned char)((frames >> 16) & 0xff); + data[3] = (unsigned char)((frames >> 24) & 0xff); + result = copy_to_user((void *)ubuf_ptr, + data, sizeof(data)); + if (result) { + LOGE("Fail to copy data to user space\n"); + retval = -EBADE; + } + } + + return retval; +} + +/** + * syna_cdev_clean_queue() + * + * Clean the data queue. + * All data in the queue will be cleaned up in every time of device + * open and close. + * + * @param + * [ in] tcm: the driver handle + * + * @return + * void. + */ +static void syna_cdev_clean_queue(struct syna_tcm *tcm) +{ + struct fifo_queue *pfifo_data; + + syna_pal_mutex_lock(&g_fifo_queue_mutex); + + while (!list_empty(&tcm->frame_fifo_queue)) { + pfifo_data = list_first_entry(&tcm->frame_fifo_queue, + struct fifo_queue, next); + list_del(&pfifo_data->next); + kfree(pfifo_data->fifo_data); + kfree(pfifo_data); + if (tcm->fifo_remaining_frame != 0) + tcm->fifo_remaining_frame--; + } + + LOGD("Queue cleaned, frame: %d\n", tcm->fifo_remaining_frame); + + syna_pal_mutex_unlock(&g_fifo_queue_mutex); +} +/** + * syna_cdev_ioctl_get_frame() + * + * Read the data from the queue and return to userspace if data is + * copied or the specified timeout is expired. + * + * Please be noted that the retried data is formatted as follows. + * [0 ] : status / report code + * [1 : 2 ] : length of data frame + * [3 : N + 3] : N bytes of data payload + * + * @param + * [ in] tcm: the driver handle + * [in/out] ubuf_ptr: points to a memory space from userspace + * [ in] buf_size: size of given space + * [out] frame_size: frame size returned + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_cdev_ioctl_get_frame(struct syna_tcm *tcm, + const unsigned char *ubuf_ptr, unsigned int buf_size, + unsigned int *frame_size) +{ + int retval = 0; + int timeout = 0; + unsigned char timeout_data[4] = {0}; + struct fifo_queue *pfifo_data; + + if (!tcm->is_connected) { + LOGE("Not connected\n"); + return _EINVAL; + } + + if (buf_size < sizeof(timeout_data)) { + LOGE("Invalid sync data size, buf_size:%d\n", buf_size); + retval = -EINVAL; + goto exit; + } + +#if !defined(ENABLE_EXTERNAL_FRAME_PROCESS) + LOGE("ENABLE_EXTERNAL_FRAME_PROCESS is not enabled\n"); + return -EFAULT; +#endif + + retval = copy_from_user(timeout_data, ubuf_ptr, sizeof(timeout_data)); + if (retval) { + LOGE("Fail to copy data from user space, size:%d\n", retval); + retval = -EBADE; + goto exit; + } + + /* get the waiting duration */ + timeout = syna_pal_le4_to_uint(&timeout_data[0]); + LOGD("Wait time: %dms\n", timeout); + + if (list_empty(&tcm->frame_fifo_queue)) { + LOGD("The queue is empty, wait for the frame\n"); + retval = wait_event_interruptible_timeout(tcm->wait_frame, + (tcm->fifo_remaining_frame > 0), + msecs_to_jiffies(timeout)); + if (retval == 0) { + LOGD("Queue waiting timed out after %dms\n", timeout); + retval = -ETIMEDOUT; + *frame_size = 0; + goto exit; + } + LOGD("Data queued\n"); + } + + /* confirm the queue status */ + if (list_empty(&tcm->frame_fifo_queue)) { + LOGD("Is queue empty? The remaining frame = %d\n", + tcm->fifo_remaining_frame); + retval = -ENODATA; + goto exit; + } + + syna_pal_mutex_lock(&g_fifo_queue_mutex); + + pfifo_data = list_first_entry(&tcm->frame_fifo_queue, + struct fifo_queue, next); + + LOGD("Pop data from the queue, data length = %d\n", + pfifo_data->data_length); + + if (buf_size >= pfifo_data->data_length) { + retval = copy_to_user((void *)ubuf_ptr, + pfifo_data->fifo_data, + pfifo_data->data_length); + if (retval) { + LOGE("Fail to copy data to user space, size:%d\n", + retval); + retval = -EBADE; + } + + *frame_size = pfifo_data->data_length; + + } else { + LOGE("No enough space for data copy, buf_size:%d data:%d\n", + buf_size, pfifo_data->data_length); + + retval = -EOVERFLOW; + goto exit; + } + + LOGD("From FIFO: (0x%02x, 0x%02x, 0x%02x, 0x%02x)\n", + pfifo_data->fifo_data[0], pfifo_data->fifo_data[1], + pfifo_data->fifo_data[2], pfifo_data->fifo_data[3]); + + list_del(&pfifo_data->next); + kfree(pfifo_data->fifo_data); + kfree(pfifo_data); + if (tcm->fifo_remaining_frame != 0) + tcm->fifo_remaining_frame--; + + syna_pal_mutex_unlock(&g_fifo_queue_mutex); + + if (retval >= 0) + retval = pfifo_data->data_length; + +exit: + return retval; +} + +/** + * syna_cdev_ioctl_set_reports() + * + * Assign the report types for queuing. The enabled reports will be queued + * into the FIFO queue. + * + * @param + * [ in] tcm: the driver handle + * [ in] ubuf_ptr: points to a memory space from userspace + * [ in] buf_size: size of given space + * [ in] report_size: report types data size + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_cdev_ioctl_set_reports(struct syna_tcm *tcm, + const unsigned char *ubuf_ptr, unsigned int buf_size, + unsigned int report_size) +{ + int retval = 0; + unsigned char data[REPORT_TYPES] = {0}; + unsigned int reports = 0; + unsigned int report_set = 0; + + if (buf_size < sizeof(data)) { + LOGE("Invalid sync data size, buf_size:%d, expected:%d\n", + buf_size, (unsigned int)sizeof(data)); + return -EINVAL; + } + +#if !defined(ENABLE_EXTERNAL_FRAME_PROCESS) + LOGE("ENABLE_EXTERNAL_FRAME_PROCESS is not enabled\n"); + return -EINVAL; +#endif + + if (report_size == 0) { + LOGE("Invalid written size\n"); + return -EINVAL; + } + + retval = copy_from_user(data, ubuf_ptr, report_size); + if (retval) { + LOGE("Fail to copy data from user space, size:%d\n", retval); + retval = -EBADE; + goto exit; + } + + retval = syna_pal_mem_cpy(tcm->report_to_queue, REPORT_TYPES, + data, sizeof(data), REPORT_TYPES); + for (reports = 0 ; reports < REPORT_TYPES ; reports++) { + if (tcm->report_to_queue[reports] == EFP_ENABLE) { + report_set++; + LOGD("Set report 0x%02x for queue\n", reports); + } + } + + LOGI("Forward %d types of reports to the Queue.\n", report_set); + + retval = report_set; + +exit: + return retval; +} +/** + * syna_cdev_ioctl_send_message() + * + * Send the command/message from userspace. + * + * For updating the g_sysfs_io_polling_interval, it need to be configured + * by syna_cdev_ioctl_enable_irq from userspace. + * + * @param + * [ in] tcm: the driver handle + * [ in/out] ubuf_ptr: points to a memory space from userspace + * [ in] buf_size: size of given space + * [ in/out] msg_size: size of message + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_cdev_ioctl_send_message(struct syna_tcm *tcm, + const unsigned char *ubuf_ptr, unsigned int buf_size, + unsigned int *msg_size) +{ + int retval = 0; + unsigned char *data = NULL; + unsigned char resp_code = 0; + unsigned int payload_length = 0; + unsigned int delay_ms_resp = RESP_IN_POLLING; + struct tcm_buffer resp_data_buf; + + if (!tcm->is_connected) { + LOGE("Not connected\n"); + return -ENXIO; + } + + if (buf_size < SEND_MESSAGE_HEADER_LENGTH) { + LOGE("Invalid sync data size, buf_size:%d\n", buf_size); + return -EINVAL; + } + + if (*msg_size == 0) { + LOGE("Invalid message length, msg size: 0\n"); + return -EINVAL; + } + + syna_tcm_buf_lock(&g_cdev_cbuf); + + retval = syna_tcm_buf_alloc(&g_cdev_cbuf, buf_size); + if (retval < 0) { + LOGE("Fail to allocate memory for g_cdev_cbuf, size: %d\n", + buf_size); + goto exit; + } + + data = g_cdev_cbuf.buf; + + retval = copy_from_user(data, ubuf_ptr, *msg_size); + if (retval) { + LOGE("Fail to copy data from user space, size:%d\n", *msg_size); + retval = -EBADE; + goto exit; + } + + payload_length = syna_pal_le2_to_uint(&data[1]); + LOGD("Command = 0x%02x, payload length = %d\n", + data[0], payload_length); + + /* init a buffer for the response data */ + syna_tcm_buf_init(&resp_data_buf); + + if (g_sysfs_io_polling_interval == RESP_IN_ATTN) + delay_ms_resp = RESP_IN_ATTN; + else + delay_ms_resp = g_sysfs_io_polling_interval; + + retval = syna_tcm_send_command(tcm->tcm_dev, + data[0], + &data[3], + payload_length, + &resp_code, + &resp_data_buf, + delay_ms_resp); + if (retval < 0) { + LOGE("Fail to send command:%d\n", retval); + goto exit; + } + + syna_pal_mem_set(data, 0, buf_size); + /* status code */ + data[0] = resp_code; + /* the length for response data */ + data[1] = (unsigned char)(resp_data_buf.data_length & 0xff); + data[2] = (unsigned char)((resp_data_buf.data_length >> 8) & 0xff); + /* response data */ + if (resp_data_buf.data_length > 0) { + retval = syna_pal_mem_cpy(&g_cdev_cbuf.buf[3], + (g_cdev_cbuf.buf_size - SEND_MESSAGE_HEADER_LENGTH), + resp_data_buf.buf, + resp_data_buf.buf_size, + resp_data_buf.data_length); + if (retval < 0) { + LOGE("Fail to copy resp data\n"); + goto exit; + } + } + +#ifdef ENABLE_EXTERNAL_FRAME_PROCESS + /* It's for queuing the data when user is polling the command + * response for the selected responses. The response will not be + * queued if the user doesn't set the report/response types through + * syna_cdev_ioctl_set_reports. + */ + if (delay_ms_resp != RESP_IN_ATTN) { + if (tcm->report_to_queue[resp_code] == EFP_ENABLE) { + syna_cdev_update_report_queue(tcm, resp_code, + &resp_data_buf); + } + } +#endif + + if (buf_size < resp_data_buf.data_length) { + LOGE("No enough space for data copy, buf_size:%d data:%d\n", + buf_size, resp_data_buf.data_length); + retval = -EOVERFLOW; + goto exit; + } + + retval = copy_to_user((void *)ubuf_ptr, + data, resp_data_buf.data_length); + if (retval) { + LOGE("Fail to copy data to user space\n"); + retval = -EBADE; + goto exit; + } + + *msg_size = resp_data_buf.data_length + 3; + retval = *msg_size; + +exit: + syna_tcm_buf_unlock(&g_cdev_cbuf); + + syna_tcm_buf_release(&resp_data_buf); + + return retval; +} + +/** + * syna_cdev_ioctl_enable_irq() + * + * Enable or disable the irq via IOCTL. + * + * Expect to get 4 bytes unsigned int parameter from userspace: + * 0: disable the irq. + * 1: enable the irq and set g_sysfs_io_polling_interval + * to RESP_IN_ATTN + * otherwise: enable the irq and also assign the polling interval + * to a specific time, which will be used when calling + * syna_cdev_ioctl_send_message. + * the min. polling time is RESP_IN_POLLING + * + * @param + * [ in] tcm: the driver handle + * [ in] ubuf_ptr: points to a memory space from userspace + * [ in] buf_size: size of given space + * [ in] data_size: size of actual data + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_cdev_ioctl_enable_irq(struct syna_tcm *tcm, + const unsigned char *ubuf_ptr, unsigned int buf_size, + unsigned int data_size) +{ + int retval = 0; + unsigned int data; + + if (!tcm->is_connected) { + LOGE("Not connected\n"); + return -ENXIO; + } + + if ((buf_size < sizeof(data)) || (data_size < sizeof(data))) { + LOGE("Invalid sync data size, buf_size:%d, data_size:%d\n", + buf_size, data_size); + return -EINVAL; + } + + if (!tcm->hw_if->ops_enable_irq) { + LOGW("Not support irq control\n"); + return -EFAULT; + } + + retval = copy_from_user(&data, ubuf_ptr, buf_size); + if (retval) { + LOGE("Fail to copy data from user space, size:%d\n", retval); + return -EBADE; + } + + switch (data) { + case SYSFS_DISABLED_INTERRUPT: + retval = tcm->hw_if->ops_enable_irq(tcm->hw_if, false); + if (retval < 0) { + LOGE("Fail to disable interrupt\n"); + return retval; + } + + g_sysfs_io_polling_interval = + tcm->tcm_dev->msg_data.default_resp_reading; + + LOGI("IRQ is disabled by userspace application\n"); + + break; + case SYSFS_ENABLED_INTERRUPT: + retval = tcm->hw_if->ops_enable_irq(tcm->hw_if, true); + if (retval < 0) { + LOGE("Fail to enable interrupt\n"); + return retval; + } + + g_sysfs_io_polling_interval = RESP_IN_ATTN; + + LOGI("IRQ is enabled by userspace application\n"); + + break; + default: + /* recover the interrupt and also assing the polling interval */ + retval = tcm->hw_if->ops_enable_irq(tcm->hw_if, true); + if (retval < 0) { + LOGE("Fail to enable interrupt\n"); + return retval; + } + + g_sysfs_io_polling_interval = data; + if (g_sysfs_io_polling_interval < RESP_IN_POLLING) + g_sysfs_io_polling_interval = RESP_IN_POLLING; + + LOGI("IRQ is enabled by userspace application\n"); + LOGI("Polling interval is set to %d ms\n", + g_sysfs_io_polling_interval); + + break; + } + + return 0; +} +/** + * syna_cdev_ioctl_store_pid() + * + * Save PID through IOCTL interface + * + * @param + * [ in] tcm: the driver handle + * [ in] ubuf_ptr: points to a memory space from userspace + * [ in] buf_size: size of given space + * [ in] data_size: size of actual data + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_cdev_ioctl_store_pid(struct syna_tcm *tcm, + const unsigned char *ubuf_ptr, unsigned int buf_size, + unsigned int data_size) +{ + int retval = 0; + unsigned char *data = NULL; + + if (!tcm->is_connected) { + LOGE("Not connected\n"); + return -ENXIO; + } + + if (buf_size < 4) { + LOGE("Invalid sync data size, buf_size:%d\n", buf_size); + return -EINVAL; + } + + if (data_size < 4) { + LOGE("Invalid data_size\n"); + return -EINVAL; + } + + syna_tcm_buf_lock(&g_cdev_cbuf); + + retval = syna_tcm_buf_alloc(&g_cdev_cbuf, buf_size); + if (retval < 0) { + LOGE("Fail to allocate memory for g_cdev_buf, size: %d\n", + buf_size); + goto exit; + } + + data = g_cdev_cbuf.buf; + + retval = copy_from_user(data, ubuf_ptr, data_size); + if (retval) { + LOGE("Fail to copy data from user space, size:%d\n", retval); + retval = -EBADE; + goto exit; + } + + tcm->proc_pid = syna_pal_le4_to_uint(&data[0]); + + LOGD("PID: %d\n", (unsigned int)tcm->proc_pid); +#ifdef ENABLE_PID_TASK + if (tcm->proc_pid) { + tcm->proc_task = pid_task( + find_vpid(tcm->proc_pid), + PIDTYPE_PID); + if (!tcm->proc_task) { + LOGE("Fail to locate task, pid: %d\n", + (unsigned int)tcm->proc_pid); + retval = -ESRCH; + goto exit; + } + } +#endif +exit: + syna_tcm_buf_unlock(&g_cdev_cbuf); + + return retval; +} +/** + * syna_cdev_ioctl_raw_read() + * + * Read the data from device directly without routing to command wrapper + * interface. + * + * @param + * [ in] tcm: the driver handle + * [in/out] ubuf_ptr: ubuf_ptr: points to a memory space from userspace + * [ in] buf_size: size of given space + * [ in] rd_size: reading size + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_cdev_ioctl_raw_read(struct syna_tcm *tcm, + const unsigned char *ubuf_ptr, unsigned int buf_size, + unsigned int rd_size) +{ + int retval = 0; + unsigned char *data = NULL; + + if (!tcm->is_connected) { + LOGE("Not connected\n"); + return -ENXIO; + } + + if ((buf_size < 0) || (rd_size > buf_size)) { + LOGE("Invalid sync data size, buf_size:%d, rd_size:%d\n", + buf_size, rd_size); + return -EINVAL; + } + + if (rd_size == 0) { + LOGE("The read length is 0\n"); + return 0; + } + + syna_tcm_buf_lock(&g_cdev_cbuf); + + retval = syna_tcm_buf_alloc(&g_cdev_cbuf, rd_size); + if (retval < 0) { + LOGE("Fail to allocate memory for g_cdev_cbuf, size: %d\n", + rd_size); + goto exit; + } + + data = g_cdev_cbuf.buf; + + retval = syna_tcm_read(tcm->tcm_dev, + data, + rd_size); + if (retval < 0) { + LOGE("Fail to read raw data, size: %d\n", rd_size); + goto exit; + } + + if (copy_to_user((void *)ubuf_ptr, data, rd_size)) { + LOGE("Fail to copy data to user space\n"); + retval = -EBADE; + goto exit; + } + + retval = rd_size; + +exit: + syna_tcm_buf_unlock(&g_cdev_cbuf); + + return retval; +} +/** + * syna_cdev_ioctl_raw_write() + * + * Write the given data to device directly without routing to command wrapper + * interface. + * + * @param + * [ in] tcm: the driver handle + * [ in] ubuf_ptr: points to a memory space from userspace + * [ in] buf_size: size of given space + * [ in] wr_size: size to write + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_cdev_ioctl_raw_write(struct syna_tcm *tcm, + const unsigned char *ubuf_ptr, unsigned int buf_size, + unsigned int wr_size) +{ + int retval = 0; + unsigned char *data = NULL; + + if (!tcm->is_connected) { + LOGE("Not connected\n"); + return -ENXIO; + } + + if ((buf_size < 0) || (wr_size > buf_size)) { + LOGE("Invalid sync data size, buf_size:%d, wr_size:%d\n", + buf_size, wr_size); + return -EINVAL; + } + + if (wr_size == 0) { + LOGE("Invalid written size\n"); + return -EINVAL; + } + + syna_tcm_buf_lock(&g_cdev_cbuf); + + retval = syna_tcm_buf_alloc(&g_cdev_cbuf, wr_size); + if (retval < 0) { + LOGE("Fail to allocate memory for g_cdev_cbuf, size: %d\n", + wr_size); + goto exit; + } + + data = g_cdev_cbuf.buf; + + retval = copy_from_user(data, ubuf_ptr, wr_size); + if (retval) { + LOGE("Fail to copy data from user space, size:%d\n", retval); + retval = -EBADE; + goto exit; + } + + LOGD("Write command: 0x%02x, legnth: 0x%02x, 0x%02x\n", + data[0], data[1], data[2]); + + retval = syna_tcm_write(tcm->tcm_dev, + data, + wr_size); + if (retval < 0) { + LOGE("Fail to write raw data, size: %d\n", wr_size); + goto exit; + } + + retval = wr_size; + +exit: + syna_tcm_buf_unlock(&g_cdev_cbuf); + + return retval; +} + +/** + * syna_cdev_ioctl_dispatch() + * + * Dispatch the IOCTLs operation based on the given code + * + * @param + * [ in] tcm: the driver handle + * [ in] code: code for the target operation + * [ in] ubuf_ptr: points to a memory space from userspace + * [ in] ubuf_size: size of given space + * [ in] wr_size: written data size + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_cdev_ioctl_dispatch(struct syna_tcm *tcm, + unsigned int code, const unsigned char *ubuf_ptr, + unsigned int ubuf_size, unsigned int *data_size) +{ + int retval = 0; + + switch (code) { + case STD_SET_PID_ID: + retval = syna_cdev_ioctl_store_pid(tcm, + ubuf_ptr, ubuf_size, *data_size); + break; + case STD_ENABLE_IRQ_ID: + retval = syna_cdev_ioctl_enable_irq(tcm, + ubuf_ptr, ubuf_size, *data_size); + break; + case STD_RAW_WRITE_ID: + retval = syna_cdev_ioctl_raw_write(tcm, + ubuf_ptr, ubuf_size, *data_size); + break; + case STD_RAW_READ_ID: + retval = syna_cdev_ioctl_raw_read(tcm, + ubuf_ptr, ubuf_size, *data_size); + break; + case STD_GET_FRAME_ID: + retval = syna_cdev_ioctl_get_frame(tcm, + ubuf_ptr, ubuf_size, data_size); + break; + case STD_SEND_MESSAGE_ID: + retval = syna_cdev_ioctl_send_message(tcm, + ubuf_ptr, ubuf_size, data_size); + break; + case STD_SET_REPORTS_ID: + retval = syna_cdev_ioctl_set_reports(tcm, + ubuf_ptr, ubuf_size, *data_size); + break; + case STD_CHECK_FRAMES_ID: + retval = syna_cdev_ioctl_check_frame(tcm, + ubuf_ptr, ubuf_size, *data_size); + break; + case STD_CLEAN_OUT_FRAMES_ID: + LOGD("STD_CLEAN_OUT_FRAMES_ID called\n"); + syna_cdev_clean_queue(tcm); + retval = 0; + break; + default: + LOGE("Unknown ioctl code: 0x%x\n", code); + return -EINVAL; + } + + return retval; +} +/** + * syna_cdev_ioctl_old_dispatch() + * + * Dispatch the old IOCTLs operation based on the given code + * + * @param + * [ in] tcm: the driver handle + * [ in] code: code for the target operation + * [ in] arg: argument passed from user-space + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_cdev_ioctl_old_dispatch(struct syna_tcm *tcm, + unsigned int code, unsigned long arg) +{ + int retval = 0; + + switch (code) { + case OLD_RESET_ID: + retval = syna_tcm_reset(tcm->tcm_dev); + if (retval < 0) { + LOGE("Fail to do reset\n"); + break; + } + + retval = tcm->dev_set_up_app_fw(tcm); + if (retval < 0) { + LOGE("Fail to set up app fw\n"); + break; + } + + break; + case OLD_SET_IRQ_MODE_ID: + if (!tcm->hw_if->ops_enable_irq) { + retval = -EFAULT; + break; + } + + if (arg == 0) + retval = tcm->hw_if->ops_enable_irq(tcm->hw_if, + false); + else if (arg == 1) + retval = tcm->hw_if->ops_enable_irq(tcm->hw_if, + true); + break; + case OLD_SET_RAW_MODE_ID: + if (arg == 0) + tcm->is_attn_redirecting = false; + else if (arg == 1) + tcm->is_attn_redirecting = true; + + break; + case OLD_CONCURRENT_ID: + retval = 0; + break; + + default: + LOGE("Unknown ioctl code: 0x%x\n", code); + retval = -EINVAL; + break; + } + + return retval; +} + +/** + * syna_cdev_ioctls() + * + * Used to implements the IOCTL operations + * + * @param + * [ in] filp: represents the file descriptor + * [ in] cmd: command code sent from userspace + * [ in] arg: arguments sent from userspace + * + * @return + * on success, 0; otherwise, negative value on error. + */ +#ifdef HAVE_UNLOCKED_IOCTL +static long syna_cdev_ioctls(struct file *filp, unsigned int cmd, + unsigned long arg) +#else +static int syna_cdev_ioctls(struct inode *inp, struct file *filp, + unsigned int cmd, unsigned long arg) +#endif +{ + int retval = 0; + struct device *p_dev; + struct kobject *p_kobj; + struct syna_tcm *tcm; + struct syna_ioctl_data ioc_data; + unsigned char *ptr = NULL; + + p_kobj = g_sysfs_dir->parent; + p_dev = container_of(p_kobj, struct device, kobj); + tcm = dev_get_drvdata(p_dev); + + syna_pal_mutex_lock(&g_extif_mutex); + + retval = 0; + + /* handle the old IOCTLs */ + if ((_IOC_NR(cmd)) < STD_IOCTL_BEGIN) { + retval = syna_cdev_ioctl_old_dispatch(tcm, + (unsigned int)_IOC_NR(cmd), arg); + + goto exit; + } else if ((_IOC_NR(cmd)) == STD_IOCTL_BEGIN) { + retval = 1; + goto exit; + } + + retval = copy_from_user(&ioc_data, + (void __user *) arg, + sizeof(struct syna_ioctl_data)); + if (retval) { + LOGE("Fail to copy ioctl_data from user space, size:%d\n", + retval); + retval = -EBADE; + goto exit; + } + + ptr = ioc_data.buf; + + retval = syna_cdev_ioctl_dispatch(tcm, + (unsigned int)_IOC_NR(cmd), + (const unsigned char *)ptr, + ioc_data.buf_size, + &ioc_data.data_length); + if (retval < 0) + goto exit; + + retval = copy_to_user((void __user *) arg, + &ioc_data, + sizeof(struct syna_ioctl_data)); + if (retval) { + LOGE("Fail to update ioctl_data to user space, size:%d\n", + retval); + retval = -EBADE; + goto exit; + } + +exit: + syna_pal_mutex_unlock(&g_extif_mutex); + + return retval; +} + +#if defined(CONFIG_COMPAT) && defined(HAVE_COMPAT_IOCTL) +/** + * syna_cdev_compat_ioctls() + * + * Used to implements the IOCTL compatible operations + * + * @param + * [ in] filp: represents the file descriptor + * [ in] cmd: command code sent from userspace + * [ in] arg: arguments sent from userspace + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static long syna_cdev_compat_ioctls(struct file *filp, + unsigned int cmd, unsigned long arg) +{ + int retval = 0; + struct device *p_dev; + struct kobject *p_kobj; + struct syna_tcm *tcm; + struct syna_tcm_ioctl_data_compat ioc_data; + unsigned char *ptr = NULL; + + p_kobj = g_sysfs_dir->parent; + p_dev = container_of(p_kobj, struct device, kobj); + tcm = dev_get_drvdata(p_dev); + + syna_pal_mutex_lock(&g_extif_mutex); + + retval = 0; + + /* handle the old IOCTLs */ + if ((_IOC_NR(cmd)) < STD_IOCTL_BEGIN) { + retval = syna_cdev_ioctl_old_dispatch(tcm, + (unsigned int)_IOC_NR(cmd), arg); + + goto exit; + } else if ((_IOC_NR(cmd)) == STD_IOCTL_BEGIN) { + retval = 1; + goto exit; + } + + retval = copy_from_user(&ioc_data, + (struct syna_tcm_ioctl_data_compat __user *) compat_ptr(arg), + sizeof(struct syna_tcm_ioctl_data_compat)); + if (retval) { + LOGE("Fail to copy ioctl_data from user space, size:%d\n", + retval); + retval = -EBADE; + goto exit; + } + + ptr = compat_ptr((unsigned long)ioc_data.buf); + + retval = syna_cdev_ioctl_dispatch(tcm, + (unsigned int)_IOC_NR(cmd), + (const unsigned char *)ptr, + ioc_data.buf_size, + &ioc_data.data_length); + if (retval < 0) + goto exit; + + retval = copy_to_user(compat_ptr(arg), + &ioc_data, + sizeof(struct syna_tcm_ioctl_data_compat)); + if (retval) { + LOGE("Fail to update ioctl_data to user space, size:%d\n", + retval); + retval = -EBADE; + goto exit; + } + +exit: + syna_pal_mutex_unlock(&g_extif_mutex); + + return retval; +} +#endif + +/** + * syna_cdev_llseek() + * + * Used to change the current position in a file. + * + * @param + * [ in] filp: represents the file descriptor + * [ in] off: the file position + * [ in] whence: flag for seeking + * + * @return + * not support + */ +static loff_t syna_cdev_llseek(struct file *filp, + loff_t off, int whence) +{ + return -EFAULT; +} +/** + * syna_cdev_read() + * + * Used to read data through the device file. + * Function will use raw write approach. + * + * @param + * [ in] filp: represents the file descriptor + * [out] buf: given buffer from userspace + * [ in] count: size of buffer + * [ in] f_pos: the file position + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static ssize_t syna_cdev_read(struct file *filp, + char __user *buf, size_t count, loff_t *f_pos) +{ + int retval = 0; + struct device *p_dev; + struct kobject *p_kobj; + struct syna_tcm *tcm; + + p_kobj = g_sysfs_dir->parent; + p_dev = container_of(p_kobj, struct device, kobj); + tcm = dev_get_drvdata(p_dev); + + if (count == 0) + return 0; + + syna_pal_mutex_lock(&g_extif_mutex); + + retval = syna_cdev_ioctl_raw_read(tcm, + (const unsigned char *)buf, count, count); + if (retval != count) { + LOGE("Invalid read operation, request:%d, return:%d\n", + (unsigned int)count, retval); + } + + syna_pal_mutex_unlock(&g_extif_mutex); + + return retval; +} +/** + * syna_cdev_write() + * + * Used to send data to device through the device file. + * Function will use raw write approach. + * + * @param + * [ in] filp: represents the file descriptor + * [ in] buf: given buffer from userspace + * [ in] count: size of buffer + * [ in] f_pos: the file position + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static ssize_t syna_cdev_write(struct file *filp, + const char __user *buf, size_t count, loff_t *f_pos) +{ + int retval = 0; + struct device *p_dev; + struct kobject *p_kobj; + struct syna_tcm *tcm; + + p_kobj = g_sysfs_dir->parent; + p_dev = container_of(p_kobj, struct device, kobj); + tcm = dev_get_drvdata(p_dev); + + if (count == 0) + return 0; + + syna_pal_mutex_lock(&g_extif_mutex); + + retval = syna_cdev_ioctl_raw_write(tcm, + (const unsigned char *)buf, count, count); + if (retval != count) { + LOGE("Invalid write operation, request:%d, return:%d\n", + (unsigned int)count, retval); + } + + syna_pal_mutex_unlock(&g_extif_mutex); + + return retval; +} +/** + * syna_cdev_open() + * + * Invoked when the device file is being open, which should be + * always the first operation performed on the device file + * + * @param + * [ in] inp: represents a file in rootfs + * [ in] filp: represents the file descriptor + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_cdev_open(struct inode *inp, struct file *filp) +{ + struct device *p_dev; + struct kobject *p_kobj; + struct syna_tcm *tcm; + + p_kobj = g_sysfs_dir->parent; + p_dev = container_of(p_kobj, struct device, kobj); + tcm = dev_get_drvdata(p_dev); + + syna_pal_mutex_lock(&g_extif_mutex); + + if (tcm->char_dev_ref_count != 0) { + LOGN("cdev already open, %d\n", + tcm->char_dev_ref_count); + return -EBUSY; + } + + tcm->char_dev_ref_count++; + + g_sysfs_io_polling_interval = 0; + +#ifdef ENABLE_EXTERNAL_FRAME_PROCESS + syna_cdev_clean_queue(tcm); +#endif + syna_pal_mutex_unlock(&g_extif_mutex); + + LOGI("cdev open\n"); + + return 0; +} +/** + * syna_cdev_release() + * + * Invoked when the device file is being released + * + * @param + * [ in] inp: represents a file in rootfs + * [ in] filp: represents the file descriptor + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_cdev_release(struct inode *inp, struct file *filp) +{ + struct device *p_dev; + struct kobject *p_kobj; + struct syna_tcm *tcm; + + p_kobj = g_sysfs_dir->parent; + p_dev = container_of(p_kobj, struct device, kobj); + tcm = dev_get_drvdata(p_dev); + + syna_pal_mutex_lock(&g_extif_mutex); + + if (tcm->char_dev_ref_count <= 0) { + LOGN("cdev already closed, %d\n", + tcm->char_dev_ref_count); + return 0; + } + + tcm->char_dev_ref_count--; + + tcm->is_attn_redirecting = false; + syna_pal_mem_set(tcm->report_to_queue, 0, REPORT_TYPES); +#ifdef ENABLE_EXTERNAL_FRAME_PROCESS + syna_cdev_clean_queue(tcm); +#endif + syna_pal_mutex_unlock(&g_extif_mutex); + + g_sysfs_io_polling_interval = 0; + + LOGI("cdev close\n"); + + return 0; +} + +/** + * Declare the operations of TouchCom device file + */ +static const struct file_operations device_fops = { + .owner = THIS_MODULE, +#ifdef HAVE_UNLOCKED_IOCTL + .unlocked_ioctl = syna_cdev_ioctls, +#if defined(CONFIG_COMPAT) && defined(HAVE_COMPAT_IOCTL) + .compat_ioctl = syna_cdev_compat_ioctls, +#endif +#else + .ioctl = syna_cdev_ioctls, +#endif + .llseek = syna_cdev_llseek, + .read = syna_cdev_read, + .write = syna_cdev_write, + .open = syna_cdev_open, + .release = syna_cdev_release, +}; +/** + * syna_cdev_redirect_attn() + * + * Expose the status of ATTN signal to userspace + * + * @param + * [ in] tcm: the driver handle + * + * @return + * none. + */ +void syna_cdev_redirect_attn(struct syna_tcm *tcm) +{ + if (tcm->proc_pid) + return; +} +#ifdef ENABLE_EXTERNAL_FRAME_PROCESS +/** + * syna_cdev_update_report_queue() + * + * Push the selected data to the queue. + * + * @param + * [ in] tcm: the driver handle + * [ in] code: report type + * [ in] pevent_data: report payload + * + * @return + * none. + */ +void syna_cdev_update_report_queue(struct syna_tcm *tcm, + unsigned char code, struct tcm_buffer *pevent_data) +{ + int retval; + unsigned char *frame_buffer = NULL; + unsigned int frame_length = 0; + + if (pevent_data == NULL) { + LOGE("Returned, invalid event data pointer\n"); + return; + } + frame_length = pevent_data->data_length + 3; + LOGD("The overall queuing data length = %d\n", frame_length); + frame_buffer = (unsigned char *)syna_pal_mem_alloc(frame_length, + sizeof(unsigned char)); + if (!frame_buffer) { + LOGE("Fail to allocate buffer, size: %d, data_length: %d\n", + pevent_data->data_length + 3, pevent_data->data_length); + return; + } + + frame_buffer[0] = code; + frame_buffer[1] = (unsigned char)pevent_data->data_length; + frame_buffer[2] = (unsigned char)(pevent_data->data_length >> 8); + + if (pevent_data->data_length > 0) { + retval = syna_pal_mem_cpy(&frame_buffer[3], + (frame_length - 3), + pevent_data->buf, + pevent_data->data_length, + pevent_data->data_length); + if (retval < 0) { + LOGE("Fail to copy data to buffer, size: %d\n", + pevent_data->data_length); + goto exit; + } + } + retval = syna_cdev_insert_fifo(tcm, frame_buffer, frame_length); + if (retval < 0) { + LOGE("Fail to insert data to fifo\n"); + goto exit; + } + + wake_up_interruptible(&(tcm->wait_frame)); + +exit: + syna_pal_mem_free((void *)frame_buffer); +} +#endif +/** + * syna_cdev_devnode() + * + * Provide the declaration of devtmpfs + * + * @param + * [ in] dev: an instance of device + * [ in] mode: mode of created node + * + * @return + * the string of devtmpfs + */ +static char *syna_cdev_devnode(struct device *dev, umode_t *mode) +{ + if (!mode) + return NULL; + + /* S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH */ + *mode = CHAR_DEVICE_MODE; + + return kasprintf(GFP_KERNEL, "%s", dev_name(dev)); +} +/** + * syna_cdev_create_sysfs() + * + * Create a device node and register it with sysfs. + * + * @param + * [ in] tcm: the driver handle + * [ in] pdev: an instance of platform device + * + * @return + * on success, 0; otherwise, negative value on error. + */ +int syna_cdev_create_sysfs(struct syna_tcm *tcm, + struct platform_device *pdev) +{ + int retval = 0; + struct class *device_class = NULL; + struct device *device = NULL; + static int cdev_major_num; + + tcm->device_class = NULL; + tcm->device = NULL; + + tcm->is_attn_redirecting = false; + + syna_pal_mutex_alloc(&g_extif_mutex); +#ifdef ENABLE_EXTERNAL_FRAME_PROCESS + syna_pal_mutex_alloc(&g_fifo_queue_mutex); +#endif + syna_tcm_buf_init(&g_cdev_cbuf); + + if (cdev_major_num) { + tcm->char_dev_num = MKDEV(cdev_major_num, 0); + retval = register_chrdev_region(tcm->char_dev_num, 1, + PLATFORM_DRIVER_NAME); + if (retval < 0) { + LOGE("Fail to register char device\n"); + goto err_register_chrdev_region; + } + } else { + retval = alloc_chrdev_region(&tcm->char_dev_num, 0, 1, + PLATFORM_DRIVER_NAME); + if (retval < 0) { + LOGE("Fail to allocate char device\n"); + goto err_alloc_chrdev_region; + } + + cdev_major_num = MAJOR(tcm->char_dev_num); + } + + cdev_init(&tcm->char_dev, &device_fops); + tcm->char_dev.owner = THIS_MODULE; + + retval = cdev_add(&tcm->char_dev, tcm->char_dev_num, 1); + if (retval < 0) { + LOGE("Fail to add cdev_add\n"); + goto err_add_chardev; + } + + device_class = class_create(THIS_MODULE, PLATFORM_DRIVER_NAME); + if (IS_ERR(device_class)) { + LOGE("Fail to create device class\n"); + retval = PTR_ERR(device_class); + goto err_create_class; + } + + device_class->devnode = syna_cdev_devnode; + + device = device_create(device_class, NULL, + tcm->char_dev_num, NULL, + CHAR_DEVICE_NAME"%d", MINOR(tcm->char_dev_num)); + if (IS_ERR(tcm->device)) { + LOGE("Fail to create character device\n"); + retval = -ENOENT; + goto err_create_device; + } + + tcm->device_class = device_class; + + tcm->device = device; + + tcm->char_dev_ref_count = 0; + tcm->proc_pid = 0; + +#ifdef ENABLE_EXTERNAL_FRAME_PROCESS + INIT_LIST_HEAD(&tcm->frame_fifo_queue); + init_waitqueue_head(&tcm->wait_frame); +#endif + syna_pal_mem_set(tcm->report_to_queue, 0, REPORT_TYPES); + + retval = syna_sysfs_create_dir(tcm, pdev); + if (retval < 0) { + LOGE("Fail to create sysfs dir\n"); + retval = -ENOTDIR; + goto err_create_dir; + } + + return 0; + +err_create_dir: + device_destroy(device_class, tcm->char_dev_num); +err_create_device: + class_destroy(device_class); +err_create_class: + cdev_del(&tcm->char_dev); +err_add_chardev: + unregister_chrdev_region(tcm->char_dev_num, 1); +err_alloc_chrdev_region: +err_register_chrdev_region: + return retval; +} +/** + * syna_cdev_remove_sysfs() + * + * Remove the allocate cdev device node and release the resource + * + * @param + * [ in] tcm: the driver handle + * + * @return + * none. + */ +void syna_cdev_remove_sysfs(struct syna_tcm *tcm) +{ + if (!tcm) { + LOGE("Invalid tcm driver handle\n"); + return; + } + + syna_sysfs_remove_dir(tcm); + + syna_pal_mem_set(tcm->report_to_queue, 0, REPORT_TYPES); + syna_cdev_clean_queue(tcm); + syna_pal_mutex_free(&g_fifo_queue_mutex); + + tcm->char_dev_ref_count = 0; + tcm->proc_pid = 0; + + if (tcm->device) { + device_destroy(tcm->device_class, tcm->char_dev_num); + class_destroy(tcm->device_class); + cdev_del(&tcm->char_dev); + unregister_chrdev_region(tcm->char_dev_num, 1); + } + + syna_tcm_buf_release(&g_cdev_cbuf); + + syna_pal_mutex_free(&g_extif_mutex); + + tcm->device_class = NULL; + + tcm->device = NULL; +} + + diff --git a/syna_tcm2_testing.c b/syna_tcm2_testing.c new file mode 100644 index 0000000..c41a92e --- /dev/null +++ b/syna_tcm2_testing.c @@ -0,0 +1,660 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Synaptics TouchCom touchscreen driver + * + * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +/** + * @file syna_tcm2_testing.c + * + * This file implements the sample code to perform chip testing. + */ +#include "syna_tcm2_testing.h" +#include "syna_tcm2_testing_limits.h" +#include "synaptics_touchcom_core_dev.h" +#include "synaptics_touchcom_func_base.h" + +/* g_testing_dir represents the root folder of testing sysfs + */ +static struct kobject *g_testing_dir; +static struct syna_tcm *g_tcm_ptr; + + +/** + * syna_testing_compare_byte_vector() + * + * Sample code to compare the test result with limits + * by byte vector + * + * @param + * [ in] data: target test data + * [ in] data_size: size of test data + * [ in] limit: test limit value to be compared with + * [ in] limit_size: size of test limit + * + * @return + * on success, true; otherwise, return false + */ +static bool syna_testing_compare_byte_vector(unsigned char *data, + unsigned int data_size, const unsigned char *limit, + unsigned int limit_size) +{ + bool result = false; + unsigned char tmp; + unsigned char p, l; + int i, j; + + if (!data || (data_size == 0)) { + LOGE("Invalid test data\n"); + return false; + } + if (!limit || (limit_size == 0)) { + LOGE("Invalid limits\n"); + return false; + } + + if (limit_size < data_size) { + LOGE("Limit size mismatched, data size: %d, limits: %d\n", + data_size, limit_size); + return false; + } + + result = true; + for (i = 0; i < data_size; i++) { + tmp = data[i]; + + for (j = 0; j < 8; j++) { + p = GET_BIT(tmp, j); + l = GET_BIT(limit[i], j); + if (p != l) { + LOGE("Fail on TRX-%03d (data:%X, limit:%X)\n", + (i*8 + j), p, l); + result = false; + } + } + } + + return result; +} + +/** + * syna_testing_compare_frame() + * + * Sample code to compare the test result with limits + * by a lower-bound frame + * + * @param + * [ in] data: target test data + * [ in] data_size: size of test data + * [ in] rows: the number of rows + * [ in] cols: the number of column + * [ in] limits_hi: upper-bound test limit + * [ in] limits_lo: lower-bound test limit + * + * @return + * on success, true; otherwise, return false + */ +static bool syna_testing_compare_frame(unsigned char *data, + unsigned int data_size, int rows, int cols, + const short *limits_hi, const short *limits_lo) +{ + bool result = false; + short *data_ptr = NULL; + short limit; + int i, j; + + if (!data || (data_size == 0)) { + LOGE("Invalid test data\n"); + return false; + } + + if (data_size < (2 * rows * cols)) { + LOGE("Size mismatched, data:%d (exppected:%d)\n", + data_size, (2 * rows * cols)); + result = false; + return false; + } + + if (rows > LIMIT_BOUNDARY) { + LOGE("Rows mismatched, rows:%d (exppected:%d)\n", + rows, LIMIT_BOUNDARY); + result = false; + return false; + } + + if (cols > LIMIT_BOUNDARY) { + LOGE("Columns mismatched, cols: %d (exppected:%d)\n", + cols, LIMIT_BOUNDARY); + result = false; + return false; + } + + result = true; + + if (!limits_hi) + goto end_of_upper_bound_limit; + + data_ptr = (short *)&data[0]; + for (i = 0; i < rows; i++) { + for (j = 0; j < cols; j++) { + limit = limits_hi[i * LIMIT_BOUNDARY + j]; + if (*data_ptr > limit) { + LOGE("Fail on (%2d,%2d)=%5d, limits_hi:%4d\n", + i, j, *data_ptr, limit); + result = false; + } + data_ptr++; + } + } + +end_of_upper_bound_limit: + + if (!limits_lo) + goto end_of_lower_bound_limit; + + data_ptr = (short *)&data[0]; + for (i = 0; i < rows; i++) { + for (j = 0; j < cols; j++) { + limit = limits_lo[i * LIMIT_BOUNDARY + j]; + if (*data_ptr < limit) { + LOGE("Fail on (%2d,%2d)=%5d, limits_lo:%4d\n", + i, j, *data_ptr, limit); + result = false; + } + data_ptr++; + } + } + +end_of_lower_bound_limit: + return result; +} + +/** + * syna_testing_device_id() + * + * Sample code to ensure the device id is expected + * + * @param + * [ in] tcm: the driver handle + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_testing_device_id(struct syna_tcm *tcm) +{ + int retval; + bool result; + struct tcm_identification_info info; + char *strptr = NULL; + + LOGI("Start testing\n"); + + retval = syna_tcm_identify(tcm->tcm_dev, &info); + if (retval < 0) { + LOGE("Fail to get identification\n"); + result = false; + goto exit; + } + + strptr = strnstr(info.part_number, + device_id_limit, + strlen(info.part_number)); + if (strptr != NULL) + result = true; + else { + LOGE("Device ID mismatched, FW: %s (limit: %s)\n", + info.part_number, device_id_limit); + result = false; + } + +exit: + LOGI("Result = %s\n", (result)?"pass":"fail"); + + return ((result) ? 0 : -1); +} + +/** + * syna_testing_config_id() + * + * Sample code to ensure the config id is expected + * + * @param + * [ in] tcm: the driver handle + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_testing_config_id(struct syna_tcm *tcm) +{ + int retval; + bool result; + struct tcm_application_info info; + int idx; + + LOGI("Start testing\n"); + + retval = syna_tcm_get_app_info(tcm->tcm_dev, &info); + if (retval < 0) { + LOGE("Fail to get app info\n"); + result = false; + goto exit; + } + + result = true; + for (idx = 0; idx < sizeof(config_id_limit); idx++) { + if (config_id_limit[idx] != info.customer_config_id[idx]) { + LOGE("Fail on byte.%d (data: %02X, limit: %02X)\n", + idx, info.customer_config_id[idx], + config_id_limit[idx]); + result = false; + } + } + +exit: + LOGI("Result = %s\n", (result)?"pass":"fail"); + + return ((result) ? 0 : -1); +} + +/** + * syna_testing_check_id_show() + * + * Attribute to show the result of ID comparsion to the console. + * + * @param + * [ in] kobj: an instance of kobj + * [ in] attr: an instance of kobj attribute structure + * [out] buf: string buffer shown on console + * + * @return + * on success, number of characters being output; + * otherwise, negative value on error. + */ +static ssize_t syna_testing_check_id_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int retval; + unsigned int count = 0; + struct syna_tcm *tcm = g_tcm_ptr; + + if (!tcm->is_connected) { + retval = snprintf(buf, PAGE_SIZE, + "Device is NOT connected\n"); + goto exit; + } + + count = 0; + + retval = syna_testing_device_id(tcm); + + retval = snprintf(buf, PAGE_SIZE - count, + "Device ID check: %s\n", + (retval < 0) ? "fail" : "pass"); + + buf += retval; + count += retval; + + retval = syna_testing_config_id(tcm); + + retval = snprintf(buf, PAGE_SIZE - count, + "Config ID check: %s\n", + (retval < 0) ? "fail" : "pass"); + + buf += retval; + count += retval; + + retval = count; +exit: + return retval; +} + +static struct kobj_attribute kobj_attr_check_id = + __ATTR(check_id, 0444, syna_testing_check_id_show, NULL); + +/** + * syna_testing_pt01() + * + * Sample code to perform PT01 testing + * + * @param + * [ in] tcm: the driver handle + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_testing_pt01(struct syna_tcm *tcm) +{ + int retval; + bool result = false; + struct tcm_buffer test_data; + + syna_tcm_buf_init(&test_data); + + LOGI("Start testing\n"); + + retval = syna_tcm_run_production_test(tcm->tcm_dev, + TEST_PID01_TRX_TRX_SHORTS, + &test_data); + if (retval < 0) { + LOGE("Fail to run test %d\n", TEST_PID01_TRX_TRX_SHORTS); + result = false; + goto exit; + } + + result = syna_testing_compare_byte_vector(test_data.buf, + test_data.data_length, + pt01_limits, + ARRAY_SIZE(pt01_limits)); + +exit: + LOGI("Result = %s\n", (result)?"pass":"fail"); + + syna_tcm_buf_release(&test_data); + + return ((result) ? 0 : -1); +} + +/** + * syna_testing_pt01_show() + * + * Attribute to show the result of PT01 test to the console. + * + * @param + * [ in] kobj: an instance of kobj + * [ in] attr: an instance of kobj attribute structure + * [out] buf: string buffer shown on console + * + * @return + * on success, number of characters being output; + * otherwise, negative value on error. + */ +static ssize_t syna_testing_pt01_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int retval; + unsigned int count = 0; + struct syna_tcm *tcm = g_tcm_ptr; + + if (!tcm->is_connected) { + count = snprintf(buf, PAGE_SIZE, + "Device is NOT connected\n"); + goto exit; + } + + retval = syna_testing_pt01(tcm); + + count = snprintf(buf, PAGE_SIZE, + "TEST PT$01: %s\n", + (retval < 0) ? "fail" : "pass"); + +exit: + return count; +} + +static struct kobj_attribute kobj_attr_pt01 = + __ATTR(pt01, 0444, syna_testing_pt01_show, NULL); + +/** + * syna_testing_pt05() + * + * Sample code to perform PT05 testing + * + * @param + * [ in] tcm: the driver handle + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_testing_pt05(struct syna_tcm *tcm) +{ + int retval; + bool result = false; + struct tcm_buffer test_data; + + syna_tcm_buf_init(&test_data); + + LOGI("Start testing\n"); + + retval = syna_tcm_run_production_test(tcm->tcm_dev, + TEST_PID05_FULL_RAW_CAP, + &test_data); + if (retval < 0) { + LOGE("Fail to run test %d\n", TEST_PID05_FULL_RAW_CAP); + result = false; + goto exit; + } + + result = syna_testing_compare_frame(test_data.buf, + test_data.data_length, + tcm->tcm_dev->rows, + tcm->tcm_dev->cols, + (const short *)&pt05_hi_limits[0], + (const short *)&pt05_lo_limits[0]); + +exit: + LOGI("Result = %s\n", (result)?"pass":"fail"); + + syna_tcm_buf_release(&test_data); + + return ((result) ? 0 : -1); +} + +/** + * syna_testing_pt05_show() + * + * Attribute to show the result of PT05 test to the console. + * + * @param + * [ in] kobj: an instance of kobj + * [ in] attr: an instance of kobj attribute structure + * [out] buf: string buffer shown on console + * + * @return + * on success, number of characters being output; + * otherwise, negative value on error. + */ +static ssize_t syna_testing_pt05_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int retval; + unsigned int count = 0; + struct syna_tcm *tcm = g_tcm_ptr; + + if (!tcm->is_connected) { + count = snprintf(buf, PAGE_SIZE, + "Device is NOT connected\n"); + goto exit; + } + + retval = syna_testing_pt05(tcm); + + count = snprintf(buf, PAGE_SIZE, + "TEST PT$05: %s\n", (retval < 0) ? "fail" : "pass"); + +exit: + return count; +} + +static struct kobj_attribute kobj_attr_pt05 = + __ATTR(pt05, 0444, syna_testing_pt05_show, NULL); + +/** + * syna_testing_pt0a() + * + * Sample code to perform PT0A testing + * + * @param + * [ in] tcm: the driver handle + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_testing_pt0a(struct syna_tcm *tcm) +{ + int retval; + bool result = false; + struct tcm_buffer test_data; + + syna_tcm_buf_init(&test_data); + + LOGI("Start testing\n"); + + retval = syna_tcm_run_production_test(tcm->tcm_dev, + TEST_PID10_DELTA_NOISE, + &test_data); + if (retval < 0) { + LOGE("Fail to run test %d\n", TEST_PID10_DELTA_NOISE); + result = false; + goto exit; + } + + result = syna_testing_compare_frame(test_data.buf, + test_data.data_length, + tcm->tcm_dev->rows, + tcm->tcm_dev->cols, + (const short *)&pt0a_hi_limits[0], + (const short *)&pt0a_lo_limits[0]); + +exit: + LOGI("Result = %s\n", (result)?"pass":"fail"); + + syna_tcm_buf_release(&test_data); + + return ((result) ? 0 : -1); +} + +/** + * syna_testing_pt0a_show() + * + * Attribute to show the result of PT0A test to the console. + * + * @param + * [ in] kobj: an instance of kobj + * [ in] attr: an instance of kobj attribute structure + * [out] buf: string buffer shown on console + * + * @return + * on success, number of characters being output; + * otherwise, negative value on error. + */ +static ssize_t syna_testing_pt0a_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int retval; + unsigned int count = 0; + struct syna_tcm *tcm = g_tcm_ptr; + + if (!tcm->is_connected) { + count = snprintf(buf, PAGE_SIZE, + "Device is NOT connected\n"); + goto exit; + } + + retval = syna_testing_pt0a(tcm); + + count = snprintf(buf, PAGE_SIZE, + "TEST PT$0A: %s\n", (retval < 0) ? "fail" : "pass"); + +exit: + return count; +} + +static struct kobj_attribute kobj_attr_pt0a = + __ATTR(pt0a, 0444, syna_testing_pt0a_show, NULL); + +/* + * declaration of sysfs attributes + */ +static struct attribute *attrs[] = { + &kobj_attr_check_id.attr, + &kobj_attr_pt01.attr, + &kobj_attr_pt05.attr, + &kobj_attr_pt0a.attr, + NULL, +}; + +static struct attribute_group attr_testing_group = { + .attrs = attrs, +}; + +/** + * syna_testing_create_dir() + * + * Create a directory and register it with sysfs. + * Then, create all defined sysfs files. + * + * @param + * [ in] tcm: the driver handle + * [ in] sysfs_dir: root directory of sysfs nodes + * + * @return + * on success, 0; otherwise, negative value on error. + */ +int syna_testing_create_dir(struct syna_tcm *tcm, + struct kobject *sysfs_dir) +{ + int retval = 0; + + g_testing_dir = kobject_create_and_add("testing", + sysfs_dir); + if (!g_testing_dir) { + LOGE("Fail to create testing directory\n"); + return -EINVAL; + } + + retval = sysfs_create_group(g_testing_dir, &attr_testing_group); + if (retval < 0) { + LOGE("Fail to create sysfs group\n"); + + kobject_put(g_testing_dir); + return retval; + } + + g_tcm_ptr = tcm; + + return 0; +} +/** + *syna_testing_remove_dir() + * + * Remove the allocate sysfs directory + * + * @param + * none + * + * @return + * on success, 0; otherwise, negative value on error. + */ +void syna_testing_remove_dir(void) +{ + if (g_testing_dir) { + sysfs_remove_group(g_testing_dir, &attr_testing_group); + + kobject_put(g_testing_dir); + } +} diff --git a/syna_tcm2_testing.h b/syna_tcm2_testing.h new file mode 100644 index 0000000..bbc7254 --- /dev/null +++ b/syna_tcm2_testing.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Synaptics TouchCom touchscreen driver + * + * Copyright (C) 2017-2018 Synaptics Incorporated. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#ifndef _SYNAPTICS_TCM2_TESTING_H_ +#define _SYNAPTICS_TCM2_TESTING_H_ + +#include "syna_tcm2.h" + +/** + * syna_testing_create_dir() + * + * Create a directory and register it with sysfs. + * Then, create all defined sysfs files. + * + * @param + * [ in] tcm: the driver handle + * [ in] sysfs_dir: root directory of sysfs nodes + * + * @return + * on success, 0; otherwise, negative value on error. + */ +int syna_testing_create_dir(struct syna_tcm *tcm, + struct kobject *sysfs_dir); +/** + *syna_testing_remove_dir() + * + * Remove the allocate sysfs directory + * + * @param + * none + * + * @return + * on success, 0; otherwise, negative value on error. + */ +void syna_testing_remove_dir(void); + + +#endif /* end of _SYNAPTICS_TCM2_TESTING_H_ */ diff --git a/syna_tcm2_testing_limits.h b/syna_tcm2_testing_limits.h new file mode 100644 index 0000000..46b32bb --- /dev/null +++ b/syna_tcm2_testing_limits.h @@ -0,0 +1,234 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Synaptics TouchCom touchscreen driver + * + * Copyright (C) 2017-2018 Synaptics Incorporated. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#ifndef _SYNAPTICS_TCM2_TESTING_LIMITS_H_ +#define _SYNAPTICS_TCM2_TESTING_LIMITS_H_ + +#define LIMIT_BOUNDARY (40) + +/* test limit for the device id checking */ +static const char *device_id_limit = "3908"; + +/* test limit for the config id checking */ +static const unsigned char config_id_limit[16] = { + 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +/** + * @section test limit for PT01 testing + */ +static const unsigned char pt01_limits[16] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +/** + * @section test limit for the PT05 testing + */ +static const short pt05_hi_limits[LIMIT_BOUNDARY * LIMIT_BOUNDARY] = { +/* 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 */ +/* 00 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 01 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 02 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 03 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 04 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 05 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 06 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 07 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 08 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 09 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 10 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 11 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 12 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 13 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 14 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 15 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 16 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 17 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 18 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 19 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 20 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 21 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 22 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 23 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 24 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 25 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 26 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 27 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 28 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 29 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 30 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 31 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 32 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 33 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 34 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 35 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 36 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 37 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 38 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +/* 39 */ 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, +}; + +static const short pt05_lo_limits[LIMIT_BOUNDARY * LIMIT_BOUNDARY] = { +/* 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 */ +/* 00 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 01 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 02 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 03 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 04 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 05 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 06 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 07 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 08 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 09 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 10 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 11 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 12 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 13 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 14 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 15 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 16 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 17 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 18 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 19 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 20 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 21 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 22 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 23 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 24 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 25 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 26 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 27 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 28 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 29 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 30 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 31 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 32 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 33 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 34 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 35 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 36 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 37 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 38 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +/* 39 */ 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, +}; + +/** + * @section test limit for the PT0A testing + */ +static const short pt0a_hi_limits[LIMIT_BOUNDARY * LIMIT_BOUNDARY] = { +/* 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 */ +/* 00 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 01 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 02 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 03 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 04 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 05 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 06 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 07 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 08 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 09 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 10 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 11 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 12 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 13 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 14 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 15 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 16 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 17 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 18 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 19 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 20 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 21 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 22 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 23 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 24 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 25 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 26 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 27 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 28 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 29 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 30 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 31 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 32 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 33 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 34 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 35 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 36 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 37 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 38 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +/* 39 */ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +}; + +static const short pt0a_lo_limits[LIMIT_BOUNDARY * LIMIT_BOUNDARY] = { +/* 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 */ +/* 00 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 01 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 02 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 03 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 04 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 05 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 06 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 07 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 08 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 09 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 10 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 11 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 12 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 13 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 14 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 15 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 16 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 17 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 18 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 19 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 20 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 21 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 22 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 23 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 24 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 25 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 26 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 27 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 28 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 29 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 30 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 31 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 32 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 33 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 34 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 35 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 36 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 37 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 38 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +/* 39 */ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, +}; + +#endif /* end of _SYNAPTICS_TCM2_TESTING_LIMITS_H_ */ diff --git a/tcm/synaptics_touchcom_core_dev.h b/tcm/synaptics_touchcom_core_dev.h new file mode 100644 index 0000000..9ac2ee8 --- /dev/null +++ b/tcm/synaptics_touchcom_core_dev.h @@ -0,0 +1,1096 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Synaptics TouchCom touchscreen driver + * + * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +/** + * @file: synaptics_touchcom_core_dev.h + * + * This file is the topmost header file for Synaptics TouchComm device, also + * defines the TouchComm device context structure which will be passed to + * all other functions that expect a device handle. + */ + +#ifndef _SYNAPTICS_TOUCHCOM_CORE_DEV_H_ +#define _SYNAPTICS_TOUCHCOM_CORE_DEV_H_ + + +#include "syna_tcm2_platform.h" + + +#define SYNA_TCM_CORE_LIB_VERSION 0x0111 + + +/** + * @section: Parameters pre-defined + * + * @brief: MAX_NUM_OBJECTS + * Maximum number of objects being detected + * + * @brief: MAX_SIZE_GESTURE_DATA + * Maximum size of gesture data + * + * @brief: MAX_SIZE_CONFIG_ID + * Maximum size of customer configuration ID + */ +#define MAX_NUM_OBJECTS (10) + +#define MAX_SIZE_GESTURE_DATA (8) + +#define MAX_SIZE_CONFIG_ID (16) + +/** + * @section: Command-handling relevant definitions + * + * @brief: MESSAGE_HEADER_SIZE + * The size of message header + * + * @brief: CMD_RESPONSE_TIMEOUT_MS + * Time frame for a command execution + * + * @brief: CMD_RESPONSE_POLLING_DELAY_MS + * Generic time frame to check the response in polling + * + * @brief: RD_RETRY_US + * For retry reading, delay time range in microsecond + * + * @brief: WR_DELAY_US + * For continued writes, delay time range in microsecond + * + * @brief: TAT_DELAY_US + * For bus turn-around, delay time range in microsecond + * + * @brief: FW_MODE_SWITCH_DELAY_MS + * The default time for fw mode switching + * + * @brief: RESET_DELAY_MS + * The default time after reset in case it's not set properly + * + * @brief: FORCE_ATTN_DRIVEN + * Special flag to read in resp packet in ISR function + */ +#define MESSAGE_HEADER_SIZE (4) + +#define CMD_RESPONSE_TIMEOUT_MS (3000) + +#define CMD_RESPONSE_POLLING_DELAY_MS (10) + +#define RD_RETRY_US_MIN (5000) +#define RD_RETRY_US_MAX (10000) + +#define WR_DELAY_US_MIN (500) +#define WR_DELAY_US_MAX (1000) + +#define TAT_DELAY_US_MIN (100) +#define TAT_DELAY_US_MAX (200) + +#define FW_MODE_SWITCH_DELAY_MS (200) + +#define RESET_DELAY_MS (200) + +#define DEFAULT_FLASH_ERASE_DELAY (~0) +#define DEFAULT_FLASH_WRITE_DELAY (~0) +#define DEFAULT_FLASH_READ_DELAY (~0) + +#define RESP_IN_ATTN (0) +#define RESP_IN_POLLING (CMD_RESPONSE_POLLING_DELAY_MS) + +/** + * @section: Macro to show string in log + */ +#define STR(x) #x + +/** + * @section: Helpers to check the device mode + */ +#define IS_APP_FW_MODE(mode) \ + (mode == MODE_APPLICATION_FIRMWARE) + +#define IS_NOT_APP_FW_MODE(mode) \ + (!IS_APP_FW_MODE(mode)) + +#define IS_BOOTLOADER_MODE(mode) \ + ((mode == MODE_BOOTLOADER) || \ + (mode == MODE_TDDI_BOOTLOADER) || \ + (mode == MODE_TDDI_HDL_BOOTLOADER) || \ + (mode == MODE_MULTICHIP_TDDI_BOOTLOADER)) + +#define IS_ROM_BOOTLOADER_MODE(mode) \ + (mode == MODE_ROMBOOTLOADER) + + +/** + * @section: Types for lower-level bus being used + */ +enum bus_connection { + BUS_TYPE_NONE, + BUS_TYPE_I2C, + BUS_TYPE_SPI, + BUS_TYPE_I3C, +}; + +/** + * @section: TouchComm Firmware Modes + * + * The current mode running is defined in Identify Info Packet. + */ +enum tcm_firmware_mode { + MODE_UNKNOWN = 0x00, + MODE_APPLICATION_FIRMWARE = 0x01, + MODE_HOSTDOWNLOAD_FIRMWARE = 0x02, + MODE_ROMBOOTLOADER = 0x04, + MODE_BOOTLOADER = 0x0b, + MODE_TDDI_BOOTLOADER = 0x0c, + MODE_TDDI_HDL_BOOTLOADER = 0x0d, + MODE_PRODUCTIONTEST_FIRMWARE = 0x0e, + MODE_MULTICHIP_TDDI_BOOTLOADER = 0xab, +}; + +/** + * @section: Status of Application Firmware + * + * The current status is defined in Application Info Packet. + */ +enum tcm_app_status { + APP_STATUS_OK = 0x00, + APP_STATUS_BOOTING = 0x01, + APP_STATUS_UPDATING = 0x02, + APP_STATUS_BAD_APP_CONFIG = 0xff, +}; + +/** + * @section: Field IDs in Dynamic Configuration + * + * The codes specify the generic dynamic configuration options. + */ +enum dynamic_tcm_config_id { + DC_UNKNOWN = 0x00, + DC_DISABLE_DOZE = 0x01, + DC_DISABLE_NOISE_MITIGATION = 0x02, + DC_DISABLE_FREQUENCY_SHIFT = 0x03, + DC_REQUEST_FREQUENCY_INDEX = 0x04, + DC_DISABLE_HSYNC = 0x05, + DC_REZERO_ON_EXIT_DEEP_SLEEP = 0x06, + DC_ENABLE_CHARGER_CONNECTED = 0x07, + DC_DISABLE_BASELINE_RELAXATION = 0x08, + DC_ENABLE_WAKEUP_GESTURE_MODE = 0x09, + DC_REQUEST_TESTING_FINGERS = 0x0a, + DC_ENABLE_GRIP_SUPPRESSION = 0x0b, + DC_ENABLE_THICK_GLOVE = 0x0c, + DC_ENABLE_GLOVE = 0x0d, + DC_ENABLE_FACE_DETECTION = 0x0e, + DC_INHIBIT_ACTIVE_GESTURE = 0x0f, + DC_DISABLE_PROXIMITY = 0x10, +}; + +/** + * @section: TouchComm Commands + * + * List the generic commands supported in TouchComm command-response protocol. + */ +enum tcm_command { + CMD_NONE = 0x00, + CMD_CONTINUE_WRITE = 0x01, + CMD_IDENTIFY = 0x02, + CMD_RESET = 0x04, + CMD_ENABLE_REPORT = 0x05, + CMD_DISABLE_REPORT = 0x06, + CMD_TCM2_ACK = 0x07, + CMD_TCM2_RETRY = 0x08, + CMD_TCM2_SET_MAX_READ_LENGTH = 0x09, + CMD_TCM2_GET_REPORT = 0x0a, + CMD_GET_BOOT_INFO = 0x10, + CMD_ERASE_FLASH = 0x11, + CMD_WRITE_FLASH = 0x12, + CMD_READ_FLASH = 0x13, + CMD_RUN_APPLICATION_FIRMWARE = 0x14, + CMD_SPI_MASTER_WRITE_THEN_READ = 0x15, + CMD_REBOOT_TO_ROM_BOOTLOADER = 0x16, + CMD_RUN_BOOTLOADER_FIRMWARE = 0x1f, + CMD_GET_APPLICATION_INFO = 0x20, + CMD_GET_STATIC_CONFIG = 0x21, + CMD_SET_STATIC_CONFIG = 0x22, + CMD_GET_DYNAMIC_CONFIG = 0x23, + CMD_SET_DYNAMIC_CONFIG = 0x24, + CMD_GET_TOUCH_REPORT_CONFIG = 0x25, + CMD_SET_TOUCH_REPORT_CONFIG = 0x26, + CMD_REZERO = 0x27, + CMD_COMMIT_CONFIG = 0x28, + CMD_DESCRIBE_DYNAMIC_CONFIG = 0x29, + CMD_PRODUCTION_TEST = 0x2a, + CMD_SET_CONFIG_ID = 0x2b, + CMD_ENTER_DEEP_SLEEP = 0x2c, + CMD_EXIT_DEEP_SLEEP = 0x2d, + CMD_GET_TOUCH_INFO = 0x2e, + CMD_GET_DATA_LOCATION = 0x2f, + CMD_DOWNLOAD_CONFIG = 0x30, + CMD_ENTER_PRODUCTION_TEST_MODE = 0x31, + CMD_GET_FEATURES = 0x32, + CMD_GET_ROMBOOT_INFO = 0x40, + CMD_WRITE_PROGRAM_RAM = 0x41, + CMD_ROMBOOT_RUN_BOOTLOADER_FIRMWARE = 0x42, + CMD_SPI_MASTER_WRITE_THEN_READ_EXTENDED = 0x43, + CMD_ENTER_IO_BRIDGE_MODE = 0x44, + CMD_ROMBOOT_DOWNLOAD = 0x45, +}; + +/** + * @section: TouchComm Status Codes + * + * Define the following status codes for all command responses. + * 0x00: (v1) no commands are pending and no reports are available. + * 0x01: (v1 & v2) the previous command succeeded. + * 0x03: (v1 & v2) the payload continues a previous response. + * 0x04: (v2) command was written, but no reports were available. + * 0x07: (v2) the previous write was successfully received. + * 0x08: (v2) the previous write was corrupt. The host should resend. + * 0x09: (v2) the previous command failed. + * 0x0c: (v1 & v2) write was larger than the device's receive buffer. + * 0x0d: (v1 & v2) a command was sent before the previous command completed. + * 0x0e: (v1 & v2) the requested command is not implemented. + * 0x0f: (v1 & v2) generic communication error, probably incorrect payload. + * + * 0xfe: self-defined status for a corrupted packet. + * 0xff: self-defined status for an invalid data. + */ +enum tcm_status_code { + STATUS_IDLE = 0x00, + STATUS_OK = 0x01, + STATUS_CONTINUED_READ = 0x03, + STATUS_NO_REPORT_AVAILABLE = 0x04, + STATUS_ACK = 0x07, + STATUS_RETRY_REQUESTED = 0x08, + STATUS_CMD_FAILED = 0x09, + STATUS_RECEIVE_BUFFER_OVERFLOW = 0x0c, + STATUS_PREVIOUS_COMMAND_PENDING = 0x0d, + STATUS_NOT_IMPLEMENTED = 0x0e, + STATUS_ERROR = 0x0f, + STATUS_PACKET_CORRUPTED = 0xfe, + STATUS_INVALID = 0xff, +}; + +/** + * @section: TouchComm Report Codes + * + * Define the following report codes generated by TouchComm firmware. + * 0x10: Identify Info Packet + * 0x11: Touch Report + * 0x12: Delta Cap. Image + * 0x13: Raw Cap. Image + */ +enum tcm_report_type { + REPORT_IDENTIFY = 0x10, + REPORT_TOUCH = 0x11, + REPORT_DELTA = 0x12, + REPORT_RAW = 0x13, +}; + +/** + * @section: States in Command Processing + * + * List the states in command processing. + */ +enum tcm_command_status { + CMD_STATE_IDLE = 0, + CMD_STATE_BUSY = 1, + CMD_STATE_ERROR = -1, +}; + +/** + * @section: Production Test Items + * + * List the generic production test items + */ +enum tcm_test_code { + TEST_NOT_IMPLEMENTED = 0x00, + + TEST_PID01_TRX_TRX_SHORTS = 0x01, + TEST_PID02_TRX_SENSOR_OPENS = 0x02, + TEST_PID03_TRX_GROUND_SHORTS = 0x03, + TEST_PID05_FULL_RAW_CAP = 0x05, + TEST_PID06_EE_SHORT = 0x06, + TEST_PID07_DYNAMIC_RANGE = 0x07, + TEST_PID08_HIGH_RESISTANCE = 0x08, + TEST_PID10_DELTA_NOISE = 0x0a, + TEST_PID11_OPEN_DETECTION = 0x0b, + TEST_PID12 = 0x0c, + TEST_PID13 = 0x0d, + TEST_PID14_DOZE_DYNAMIC_RANGE = 0x0e, + TEST_PID15_DOZE_NOISE = 0x0f, + TEST_PID16_SENSOR_SPEED = 0x10, + TEST_PID17_ADC_RANGE = 0x11, + TEST_PID18_HYBRID_ABS_RAW = 0x12, + TEST_PID29_HYBRID_ABS_NOISE = 0x1D, + + TEST_PID_MAX, +}; + + +/** + * @section: Internal Buffer Structure + * + * This structure is taken as the internal common buffer. + */ +struct tcm_buffer { + unsigned char *buf; + unsigned int buf_size; + unsigned int data_length; + syna_pal_mutex_t buf_mutex; + unsigned char ref_cnt; +}; + +/** + * @section: TouchComm Identify Info Packet + * Ver.1: size is 24 (0x18) bytes + * Ver.2: size is extended to 32 (0x20) bytes + * + * The identify packet provides the basic TouchComm information and indicate + * that the device is ready to receive commands. + * + * The report is received whenever the device initially powers up, resets, + * or switches fw between bootloader and application modes. + */ +struct tcm_identification_info { + unsigned char version; + unsigned char mode; + unsigned char part_number[16]; + unsigned char build_id[4]; + unsigned char max_write_size[2]; + /* extension in ver.2 */ + unsigned char max_read_size[2]; + unsigned char reserved[6]; +}; + + +/** + * @section: TouchComm Application Information Packet + * + * The application info packet provides the information about the application + * firmware as well as the touch controller. + */ +struct tcm_application_info { + unsigned char version[2]; + unsigned char status[2]; + unsigned char static_config_size[2]; + unsigned char dynamic_config_size[2]; + unsigned char app_config_start_write_block[2]; + unsigned char app_config_size[2]; + unsigned char max_touch_report_config_size[2]; + unsigned char max_touch_report_payload_size[2]; + unsigned char customer_config_id[MAX_SIZE_CONFIG_ID]; + unsigned char max_x[2]; + unsigned char max_y[2]; + unsigned char max_objects[2]; + unsigned char num_of_buttons[2]; + unsigned char num_of_image_rows[2]; + unsigned char num_of_image_cols[2]; + unsigned char has_hybrid_data[2]; + unsigned char num_of_force_elecs[2]; +}; + +/** + * @section: TouchComm boot information packet + * + * The boot info packet provides the information of TouchBoot. + */ +struct tcm_boot_info { + unsigned char version; + unsigned char status; + unsigned char asic_id[2]; + unsigned char write_block_size_words; + unsigned char erase_page_size_words[2]; + unsigned char max_write_payload_size[2]; + unsigned char last_reset_reason; + unsigned char pc_at_time_of_last_reset[2]; + unsigned char boot_config_start_block[2]; + unsigned char boot_config_size_blocks[2]; + /* extension in ver.2 */ + unsigned char display_config_start_block[4]; + unsigned char display_config_length_blocks[2]; + unsigned char backup_display_config_start_block[4]; + unsigned char backup_display_config_length_blocks[2]; + unsigned char custom_otp_start_block[2]; + unsigned char custom_otp_length_blocks[2]; +}; + +/** + * @section: TouchComm ROMboot information packet + * + * The ROMboot info packet provides the information of ROM bootloader. + */ +struct tcm_romboot_info { + unsigned char version; + unsigned char status; + unsigned char asic_id[2]; + unsigned char write_block_size_words; + unsigned char max_write_payload_size[2]; + unsigned char last_reset_reason; + unsigned char pc_at_time_of_last_reset[2]; +}; + +/** + * @section: TouchComm features description packet + * + * The features description packet tells which features are supported. + */ +struct tcm_features_info { + unsigned char byte[16]; +}; + +/** + * @section: Data blob for touch data reported + * + * Once receiving a touch report generated by firmware, the touched data + * will be parsed and converted to touch_data_blob structure as a data blob. + * + * @subsection: tcm_touch_data_blob + * The touch_data_blob contains all sorts of touched data entities. + * + * @subsection tcm_objects_data_blob + * The objects_data_blob includes the data for each active objects. + * + * @subsection tcm_gesture_data_blob + * The gesture_data_blob contains the gesture data if detected. + */ +struct tcm_objects_data_blob { + unsigned char status; + unsigned int x_pos; + unsigned int y_pos; + unsigned int x_width; + unsigned int y_width; + unsigned int z; + unsigned int tx_pos; + unsigned int rx_pos; +}; +struct tcm_gesture_data_blob { + union { + struct { + unsigned char tap_x[2]; + unsigned char tap_y[2]; + }; + struct { + unsigned char swipe_x[2]; + unsigned char swipe_y[2]; + unsigned char swipe_direction[2]; + }; + unsigned char data[MAX_SIZE_GESTURE_DATA]; + }; +}; +struct tcm_touch_data_blob { + + /* for each active objects */ + unsigned int num_of_active_objects; + struct tcm_objects_data_blob object_data[MAX_NUM_OBJECTS]; + + /* for gesture */ + unsigned int gesture_id; + struct tcm_gesture_data_blob gesture_data; + + /* various data */ + unsigned int timestamp; + unsigned int buttons_state; + unsigned int frame_rate; + unsigned int power_im; + unsigned int cid_im; + unsigned int rail_im; + unsigned int cid_variance_im; + unsigned int nsm_frequency; + unsigned int nsm_state; + unsigned int num_of_cpu_cycles; + unsigned int fd_data; + unsigned int force_data; + unsigned int fingerprint_area_meet; + unsigned int sensing_mode; +}; + +/** + * @section: Callback function used for custom entity parsing in touch report + * + * Allow to implement the custom handling for"new" custom entity in touch report + * + * @param + * [ in] code: the code of current touch entity + * [ in] config: the report configuration stored + * [in/out] config_offset: offset of current position in report config, + * the updated position should be returned. + * [ in] report: touch report given + * [in/out] report_offset: offset of current position in touch report, + * the updated position should be returned. + * [ in] report_size: size of given touch report + * [ in] callback_data: pointer to caller data passed to callback function + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +typedef int (*tcm_touch_data_parse_callback_t) (const unsigned char code, + const unsigned char *config, unsigned int *config_offset, + const unsigned char *report, unsigned int *report_offset, + unsigned int report_size, void *callback_data); + +/** + * @section: Callback function used for custom gesture parsing in touch report + * + * Allow to implement the custom handling for gesture data. + * + * @param + * [ in] code: the code of current touch entity + * [ in] config: the report configuration stored + * [in/out] config_offset: offset of current position in report config, + * the updated position should be returned. + * [ in] report: touch report given + * [in/out] report_offset: offset of current position in touch report, + * the updated position should be returned. + * [ in] report_size: size of given touch report + * [ in] callback_data: pointer to caller data passed to callback function + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +typedef int (*tcm_gesture_parse_callback_t) (const unsigned char code, + const unsigned char *config, unsigned int *config_offset, + const unsigned char *report, unsigned int *report_offset, + unsigned int report_size, void *callback_data); + +/** + * @section: TouchComm Message Handling Wrapper + * + * The structure contains the essential buffers and parameters to implement + * the command-response protocol for both TouchCom ver.1 and TouchCom ver.2. + */ +struct tcm_message_data_blob { + + /* parameters for command processing */ + syna_pal_atomic_t command_status; + unsigned char command; + unsigned char status_report_code; + unsigned int payload_length; + unsigned char response_code; + unsigned char report_code; + unsigned char seq_toggle; + unsigned int default_resp_reading; + + /* completion event command processing */ + syna_pal_completion_t cmd_completion; + + /* internal buffers + * in : buffer storing the data being read 'in' + * out : buffer storing the data being sent 'out' + * temp: 'temp' buffer used for continued read operation + */ + struct tcm_buffer in; + struct tcm_buffer out; + struct tcm_buffer temp; + + /* mutex for the protection of command processing */ + syna_pal_mutex_t cmd_mutex; + + /* mutex for the read/write protection */ + syna_pal_mutex_t rw_mutex; + +}; + +/** + * @section: TouchComm core device context structure + * + * The device context contains parameters and internal buffers, that will + * be passed to all other functions that expect a device handle. + * + * Calling syna_tcm_allocate_device() can allocate this structure, and + * syna_tcm_remove_device() releases the structure if no longer needed. + */ +struct tcm_dev { + + /* basic device information */ + unsigned char dev_mode; + unsigned int packrat_number; + unsigned int max_x; + unsigned int max_y; + unsigned int max_objects; + unsigned int rows; + unsigned int cols; + unsigned char config_id[MAX_SIZE_CONFIG_ID]; + + /* capability of read/write transferred + * being assigned through syna_hw_interface + */ + unsigned int max_wr_size; + unsigned int max_rd_size; + + /* hardware-specific data structure + * defined in syna_touchcom_platform.h + */ + struct syna_hw_interface *hw_if; + + /* TouchComm defined structures */ + struct tcm_identification_info id_info; + struct tcm_application_info app_info; + struct tcm_boot_info boot_info; + + /* internal buffers + * report: record the TouchComm report to caller + * resp : record the command response to caller + */ + struct tcm_buffer report_buf; + struct tcm_buffer resp_buf; + struct tcm_buffer external_buf; + + /* touch report configuration */ + struct tcm_buffer touch_config; + + /* TouchComm message handling wrapper */ + struct tcm_message_data_blob msg_data; + + /* indicate that fw update is on-going */ + syna_pal_atomic_t firmware_flashing; + + /* abstraction to read a TouchComm message from device. + * Function will be assigned by syna_tcm_detect_device(). + * + * After read_message() returned, the retrieved data will be available + * and stored either in buffer.report or buffer.resp based on the + * code returned. + * + * @param + * [ in] tcm_dev: the device handle + * [out] status_report_code: status code or report code received + * + * @return + * 0 or positive value on success; otherwise, on error. + */ + int (*read_message)(struct tcm_dev *tcm_dev, + unsigned char *status_report_code); + + /* abstraction to write a TouchComm message to device and retrieve the + * response to command. + * Function will be assigned by syna_tcm_detect_device(). + * + * After calling write_message(), the response code is returned + * and the response data is stored in buffer.resp. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] command: TouchComm command to write + * [ in] payload: data payload, if any + * [ in] payload_length: length of data payload, if any + * [out] resp_code: response code returned + * [ in] delay_ms_resp: delay time for response reading. + * a positive value presents the polling time; + * or, set '0' (RESP_IN_ATTN) for ATTN driven + * @return + * 0 or positive value on success; otherwise, on error. + */ + int (*write_message)(struct tcm_dev *tcm_dev, + unsigned char command, unsigned char *payload, + unsigned int payload_length, unsigned char *resp_code, + unsigned int delay_ms_resp); + + + /* callbacks + * custom_touch_data_parse_func: custom touch data entity parsing + * custom_gesture_parse_func : custom gesture data entity parsing + */ + tcm_touch_data_parse_callback_t custom_touch_data_parse_func; + void *cbdata_touch_data_parse; + tcm_gesture_parse_callback_t custom_gesture_parse_func; + void *cbdata_gesture_parse; + +}; +/* end of structure syna_tcm_dev */ + + +/** + * @section: Protocol detection + * + * @brief: syna_tcm_v1_detect + * Check whether TouchComm ver.1 firmware is running + * + * @brief: syna_tcm_v2_detect + * Check whether TouchComm ver.2 firmware is running + */ + +/* syna_tcm_v1_detect() + * + * Check whether TouchComm ver.1 firmware is running. + * Function is implemented in synaptics_tcm2_core_v1.c. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] data: raw 4-byte data + * [ in] size: length of input data in bytes + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_v1_detect(struct tcm_dev *tcm_dev, unsigned char *data, + unsigned int data_len); + +/* syna_tcm_v2_detect() + * + * Check whether TouchComm ver.2 firmware is running. + * Function is implemented in synaptics_tcm2_core_v2.c. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] data: raw 4-byte data + * [ in] size: length of input data in bytes + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_v2_detect(struct tcm_dev *tcm_dev, unsigned char *data, + unsigned int data_len); + +/** + * @section: Buffers Management helpers + * + * @brief: syna_tcm_buf_alloc + * Allocate the requested memory space for the buffer structure + * + * @brief: syna_tcm_buf_realloc + * Extend the requested memory space for the buffer structure + * + * @brief: syna_tcm_buf_init + * Initialize the buffer structure + * + * @brief: syna_tcm_buf_lock + * Protect the access of current buffer structure + * + * @brief: syna_tcm_buf_unlock + * Open the access of current buffer structure + * + * @brief: syna_tcm_buf_release + * Release the buffer structure + */ + +/** + * syna_tcm_buf_alloc() + * + * Allocate the requested memory space for the given buffer only if + * the existed buffer is not enough for the requirement. + * + * @param + * [ in] pbuf: pointer to an internal buffer + * [ in] size: required size to be allocated + * + * @return + * 0 or positive value on success; otherwise, on error. + */ +static inline int syna_tcm_buf_alloc(struct tcm_buffer *pbuf, + unsigned int size) +{ + if (!pbuf) { + LOGE("Invalid buffer structure\n"); + return -1; + } + + if (size > pbuf->buf_size) { + if (pbuf->buf) + syna_pal_mem_free((void *)pbuf->buf); + + pbuf->buf = syna_pal_mem_alloc(size, sizeof(unsigned char)); + if (!(pbuf->buf)) { + LOGE("Fail to allocate memory (size = %d)\n", + (int)(size*sizeof(unsigned char))); + pbuf->buf_size = 0; + pbuf->data_length = 0; + return -1; + } + pbuf->buf_size = size; + } + + syna_pal_mem_set(pbuf->buf, 0x00, pbuf->buf_size); + pbuf->data_length = 0; + + return 0; +} + +/** + * syna_tcm_buf_realloc() + * + * Extend the requested memory space for the given buffer only if + * the existed buffer is not enough for the requirement. + * Then, move the content to the new memory space. + * + * @param + * [ in] pbuf: pointer to an internal buffer + * [ in] size: required size to be extended + * + * @return + * 0 or positive value on success; otherwise, on error. + */ +static inline int syna_tcm_buf_realloc(struct tcm_buffer *pbuf, + unsigned int size) +{ + int retval; + unsigned char *temp_src; + unsigned int temp_size = 0; + + if (!pbuf) { + LOGE("Invalid buffer structure\n"); + return -1; + } + + if (size > pbuf->buf_size) { + temp_src = pbuf->buf; + temp_size = pbuf->buf_size; + + pbuf->buf = syna_pal_mem_alloc(size, sizeof(unsigned char)); + if (!(pbuf->buf)) { + LOGE("Fail to allocate memory (size = %d)\n", + (int)(size * sizeof(unsigned char))); + syna_pal_mem_free((void *)temp_src); + pbuf->buf_size = 0; + return -1; + } + + retval = syna_pal_mem_cpy(pbuf->buf, + size, + temp_src, + temp_size, + temp_size); + if (retval < 0) { + LOGE("Fail to copy data\n"); + syna_pal_mem_free((void *)temp_src); + syna_pal_mem_free((void *)pbuf->buf); + pbuf->buf_size = 0; + return retval; + } + + syna_pal_mem_free((void *)temp_src); + pbuf->buf_size = size; + } + + return 0; +} +/** + * syna_tcm_buf_init() + * + * Initialize the buffer structure. + * + * @param + * [ in] pbuf: pointer to an internal buffer + * + * @return + * none + */ +static inline void syna_tcm_buf_init(struct tcm_buffer *pbuf) +{ + pbuf->buf_size = 0; + pbuf->data_length = 0; + pbuf->ref_cnt = 0; + pbuf->buf = NULL; + syna_pal_mutex_alloc(&pbuf->buf_mutex); +} +/** + * syna_tcm_buf_lock() + * + * Protect the access of current buffer structure. + * + * @param + * [ in] pbuf: pointer to an internal buffer + * + * @return + * none + */ +static inline void syna_tcm_buf_lock(struct tcm_buffer *pbuf) +{ + if (pbuf->ref_cnt != 0) { + LOGE("Buffer access out-of balance, %d\n", pbuf->ref_cnt); + return; + } + + syna_pal_mutex_lock(&pbuf->buf_mutex); + pbuf->ref_cnt++; +} +/** + * syna_tcm_buf_unlock() + * + * Open the access of current buffer structure. + * + * @param + * [ in] pbuf: pointer to an internal buffer + * + * @return + * none + */ +static inline void syna_tcm_buf_unlock(struct tcm_buffer *pbuf) +{ + if (pbuf->ref_cnt != 1) { + LOGE("Buffer access out-of balance, %d\n", pbuf->ref_cnt); + return; + } + + pbuf->ref_cnt--; + syna_pal_mutex_unlock(&pbuf->buf_mutex); +} +/** + * syna_tcm_buf_release() + * + * Release the buffer structure. + * + * @param + * [ in] pbuf: pointer to an internal buffer + * + * @return + * none + */ +static inline void syna_tcm_buf_release(struct tcm_buffer *pbuf) +{ + if (pbuf->ref_cnt != 0) + LOGE("Buffer access hold, %d\n", pbuf->ref_cnt); + + syna_pal_mutex_free(&pbuf->buf_mutex); + syna_pal_mem_free((void *)pbuf->buf); + pbuf->buf_size = 0; + pbuf->data_length = 0; + pbuf->ref_cnt = 0; +} +/** + * syna_tcm_buf_copy() + * + * Helper to copy data from the source buffer to the destination buffer. + * The size of destination buffer may be reallocated, if the size is + * smaller than the actual data size to be copied. + * + * @param + * [out] dest: pointer to an internal buffer + * [ in] src: required size to be extended + * + * @return + * 0 or positive value on success; otherwise, on error. + */ +static inline int syna_tcm_buf_copy(struct tcm_buffer *dest, + struct tcm_buffer *src) +{ + int retval = 0; + + if (dest->buf_size < src->data_length) { + retval = syna_tcm_buf_alloc(dest, src->data_length + 1); + if (retval < 0) { + LOGE("Fail to reallocate the given buffer, size: %d\n", + src->data_length + 1); + return retval; + } + } + + /* copy data content to the destination */ + retval = syna_pal_mem_cpy(dest->buf, + dest->buf_size, + src->buf, + src->buf_size, + src->data_length); + if (retval < 0) { + LOGE("Fail to copy data to caller, size: %d\n", + src->data_length); + return retval; + } + + dest->data_length = src->data_length; + + return retval; +} +/** + * @section: Reads / Writes Abstraction Function + * + * @brief: syna_tcm_read + * Read the data from bus directly + * + * @brief: syna_tcm_write + * Write the data to bus directly + */ + +/** + * syna_tcm_read() + * + * The bare read function, reading in the requested data bytes + * from bus directly. + * + * @param + * [ in] tcm_dev: the device handle + * [out] rd_data: buffer for storing data retrieved from device + * [ in] rd_len: length of reading data in bytes + * + * @return + * the number of data bytes retrieved; + * otherwise, negative value, on error. + */ +static inline int syna_tcm_read(struct tcm_dev *tcm_dev, + unsigned char *rd_data, unsigned int rd_len) +{ + struct syna_hw_interface *hw_if; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + hw_if = tcm_dev->hw_if; + if (!hw_if->ops_read_data) { + LOGE("Invalid hw ops_read function\n"); + return _ENODEV; + } + + return hw_if->ops_read_data(hw_if, rd_data, rd_len); +} + +/** + * syna_tcm_write() + * + * The bare write function, writing the given data bytes to bus directly. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] wr_data: written data + * [ in] wr_len: length of written data in bytes + * + * @return + * the number of data bytes retrieved; + * otherwise, negative value, on error. + */ +static inline int syna_tcm_write(struct tcm_dev *tcm_dev, + unsigned char *wr_data, unsigned int wr_len) +{ + struct syna_hw_interface *hw_if; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + hw_if = tcm_dev->hw_if; + if (!hw_if->ops_write_data) { + LOGE("Invalid hw ops_write function\n"); + return _ENODEV; + } + + return hw_if->ops_write_data(hw_if, wr_data, wr_len); +} + + +#endif /* end of _SYNAPTICS_TOUCHCOM_CORE_DEV_H_ */ diff --git a/tcm/synaptics_touchcom_core_v1.c b/tcm/synaptics_touchcom_core_v1.c new file mode 100644 index 0000000..d10380f --- /dev/null +++ b/tcm/synaptics_touchcom_core_v1.c @@ -0,0 +1,1078 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +/** + * @file synaptics_touchcom_core_v1.c + * + * This file implements the TouchComm version 1 command-response protocol. + */ + +#include "synaptics_touchcom_core_dev.h" + +#define TCM_V1_MESSAGE_MARKER 0xa5 +#define TCM_V1_MESSAGE_PADDING 0x5a + +/** + * @section: Header of TouchComm v1 Message Packet + * + * The 4-byte header in the TouchComm v1 packet + */ +struct tcm_v1_message_header { + union { + struct { + unsigned char marker; + unsigned char code; + unsigned char length[2]; + }; + unsigned char data[MESSAGE_HEADER_SIZE]; + }; +}; + +/** + * syna_tcm_v1_parse_idinfo() + * + * Copy the given data to the identification info structure + * and parse the basic information, e.g. fw build id. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] data: data buffer + * [ in] size: size of given data buffer + * [ in] data_len: length of actual data + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_v1_parse_idinfo(struct tcm_dev *tcm_dev, + unsigned char *data, unsigned int size, unsigned int data_len) +{ + int retval; + unsigned int wr_size = 0; + unsigned int build_id = 0; + struct tcm_identification_info *id_info; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if ((!data) || (data_len == 0)) { + LOGE("Invalid given data buffer\n"); + return _EINVAL; + } + + id_info = &tcm_dev->id_info; + + retval = syna_pal_mem_cpy((unsigned char *)id_info, + sizeof(struct tcm_identification_info), + data, + size, + MIN(sizeof(*id_info), data_len)); + if (retval < 0) { + LOGE("Fail to copy identification info\n"); + return retval; + } + + build_id = syna_pal_le4_to_uint(id_info->build_id); + + wr_size = syna_pal_le2_to_uint(id_info->max_write_size); + tcm_dev->max_wr_size = MIN(wr_size, WR_CHUNK_SIZE); + if (tcm_dev->max_wr_size == 0) { + tcm_dev->max_wr_size = wr_size; + LOGD("max_wr_size = %d\n", tcm_dev->max_wr_size); + } + + LOGI("TCM Fw mode: 0x%02x\n", id_info->mode); + + if (tcm_dev->packrat_number != build_id) + tcm_dev->packrat_number = build_id; + + tcm_dev->dev_mode = id_info->mode; + + return 0; +} + +/** + * syna_tcm_v1_dispatch_report() + * + * Handle the TouchCom report packet being received. + * + * If it's an identify report, parse the identification packet and signal + * the command completion just in case. + * Otherwise, copy the data from internal buffer.in to internal buffer.report + * + * @param + * [ in] tcm_dev: the device handle + * + * @return + * none. + */ +static void syna_tcm_v1_dispatch_report(struct tcm_dev *tcm_dev) +{ + int retval; + struct tcm_message_data_blob *tcm_msg = NULL; + syna_pal_completion_t *cmd_completion = NULL; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return; + } + + tcm_msg = &tcm_dev->msg_data; + cmd_completion = &tcm_msg->cmd_completion; + + tcm_msg->report_code = tcm_msg->status_report_code; + + if (tcm_msg->payload_length == 0) { + tcm_dev->report_buf.data_length = 0; + goto exit; + } + + /* The identify report may be resulted from reset or fw mode switching + */ + if (tcm_msg->report_code == REPORT_IDENTIFY) { + + syna_tcm_buf_lock(&tcm_msg->in); + + retval = syna_tcm_v1_parse_idinfo(tcm_dev, + &tcm_msg->in.buf[MESSAGE_HEADER_SIZE], + tcm_msg->in.buf_size - MESSAGE_HEADER_SIZE, + tcm_msg->payload_length); + if (retval < 0) { + LOGE("Fail to identify device\n"); + syna_tcm_buf_unlock(&tcm_msg->in); + return; + } + + syna_tcm_buf_unlock(&tcm_msg->in); + + /* in case, the identify info packet is caused by the command */ + if (ATOMIC_GET(tcm_msg->command_status) == CMD_STATE_BUSY) { + switch (tcm_msg->command) { + case CMD_RESET: + LOGD("Reset by CMD_RESET\n"); + case CMD_REBOOT_TO_ROM_BOOTLOADER: + case CMD_RUN_BOOTLOADER_FIRMWARE: + case CMD_RUN_APPLICATION_FIRMWARE: + case CMD_ENTER_PRODUCTION_TEST_MODE: + case CMD_ROMBOOT_RUN_BOOTLOADER_FIRMWARE: + tcm_msg->status_report_code = STATUS_OK; + tcm_msg->response_code = STATUS_OK; + ATOMIC_SET(tcm_msg->command_status, + CMD_STATE_IDLE); + syna_pal_completion_complete(cmd_completion); + goto exit; + default: + LOGN("Device has been reset\n"); + ATOMIC_SET(tcm_msg->command_status, + CMD_STATE_ERROR); + syna_pal_completion_complete(cmd_completion); + goto exit; + } + } + } + + /* store the received report into the internal buffer.report */ + syna_tcm_buf_lock(&tcm_dev->report_buf); + + retval = syna_tcm_buf_alloc(&tcm_dev->report_buf, + tcm_msg->payload_length); + if (retval < 0) { + LOGE("Fail to allocate memory for internal buf.report\n"); + syna_tcm_buf_unlock(&tcm_dev->report_buf); + goto exit; + } + + syna_tcm_buf_lock(&tcm_msg->in); + + retval = syna_pal_mem_cpy(tcm_dev->report_buf.buf, + tcm_dev->report_buf.buf_size, + &tcm_msg->in.buf[MESSAGE_HEADER_SIZE], + tcm_msg->in.buf_size - MESSAGE_HEADER_SIZE, + tcm_msg->payload_length); + if (retval < 0) { + LOGE("Fail to copy payload to buf_report\n"); + syna_tcm_buf_unlock(&tcm_msg->in); + syna_tcm_buf_unlock(&tcm_dev->report_buf); + goto exit; + } + + tcm_dev->report_buf.data_length = tcm_msg->payload_length; + + syna_tcm_buf_unlock(&tcm_msg->in); + syna_tcm_buf_unlock(&tcm_dev->report_buf); + +exit: + return; +} + +/** + * syna_tcm_v1_dispatch_response() + * + * Handle the response packet. + * + * Copy the data from internal buffer.in to internal buffer.resp, + * and then signal the command completion. + * + * @param + * [ in] tcm_dev: the device handle + * + * @return + * none. + */ +static void syna_tcm_v1_dispatch_response(struct tcm_dev *tcm_dev) +{ + int retval; + struct tcm_message_data_blob *tcm_msg = NULL; + syna_pal_completion_t *cmd_completion = NULL; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return; + } + + tcm_msg = &tcm_dev->msg_data; + cmd_completion = &tcm_msg->cmd_completion; + + tcm_msg->response_code = tcm_msg->status_report_code; + + if (ATOMIC_GET(tcm_msg->command_status) != CMD_STATE_BUSY) + return; + + if (tcm_msg->payload_length == 0) { + tcm_dev->resp_buf.data_length = tcm_msg->payload_length; + ATOMIC_SET(tcm_msg->command_status, CMD_STATE_IDLE); + goto exit; + } + + /* copy the received resp data into the internal buffer.resp */ + syna_tcm_buf_lock(&tcm_dev->resp_buf); + + retval = syna_tcm_buf_alloc(&tcm_dev->resp_buf, + tcm_msg->payload_length); + if (retval < 0) { + LOGE("Fail to allocate memory for internal buf.resp\n"); + syna_tcm_buf_unlock(&tcm_dev->resp_buf); + goto exit; + } + + syna_tcm_buf_lock(&tcm_msg->in); + + retval = syna_pal_mem_cpy(tcm_dev->resp_buf.buf, + tcm_dev->resp_buf.buf_size, + &tcm_msg->in.buf[MESSAGE_HEADER_SIZE], + tcm_msg->in.buf_size - MESSAGE_HEADER_SIZE, + tcm_msg->payload_length); + if (retval < 0) { + LOGE("Fail to copy payload to internal resp_buf\n"); + syna_tcm_buf_unlock(&tcm_msg->in); + syna_tcm_buf_unlock(&tcm_dev->resp_buf); + goto exit; + } + + tcm_dev->resp_buf.data_length = tcm_msg->payload_length; + + syna_tcm_buf_unlock(&tcm_msg->in); + syna_tcm_buf_unlock(&tcm_dev->resp_buf); + + ATOMIC_SET(tcm_msg->command_status, CMD_STATE_IDLE); + +exit: + syna_pal_completion_complete(cmd_completion); +} + + +/** + * syna_tcm_v1_read() + * + * Read in a TouchCom packet from device. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] rd_length: number of reading bytes; + * '0' means to read the message header only + * [in/out] buf: pointer to a buffer which is stored the retrieved data + * [out] buf_size: size of the buffer pointed + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_v1_read(struct tcm_dev *tcm_dev, unsigned int rd_length, + unsigned char *buf, unsigned int buf_size) +{ + int retval; + unsigned int max_rd_size; + int retry; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if (rd_length == 0) + return 0; + + if (rd_length > buf_size) { + LOGE("Invalid read length, len: %d, buf_size: %d\n", + rd_length, buf_size); + return _EINVAL; + } + + max_rd_size = tcm_dev->max_rd_size; + + if ((max_rd_size != 0) && (rd_length > max_rd_size)) { + LOGE("Invalid read length, len: %d, max_rd_size: %d\n", + rd_length, max_rd_size); + return _EINVAL; + } + + /* read in the message header from device + * will do retry if the packet is not expected + */ + for (retry = 0; retry < 10; retry++) { + retval = syna_tcm_read(tcm_dev, + buf, + rd_length + ); + if (retval < 0) { + LOGE("Fail to read %d bytes to device\n", rd_length); + goto exit; + } + + /* check the message header */ + if (buf[0] == TCM_V1_MESSAGE_MARKER) + break; + + LOGE("Incorrect header marker, 0x%02x (retry:%d)\n", + buf[0], retry); + + retval = _EIO; + syna_pal_sleep_us(RD_RETRY_US_MIN, RD_RETRY_US_MAX); + } + +exit: + return retval; +} + +/** + * syna_tcm_v1_write() + * + * Construct the TouchCom v1 packet and send it to device. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] command: command code + * [ in] payload: data payload if any + * [ in] payload_len: length of data payload if have any + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_v1_write(struct tcm_dev *tcm_dev, unsigned char command, + unsigned char *payload, unsigned int payload_len) +{ + int retval = 0; + struct tcm_message_data_blob *tcm_msg = NULL; + int size; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + tcm_msg = &tcm_dev->msg_data; + + syna_tcm_buf_lock(&tcm_msg->out); + + /* allocate the space storing the written data */ + retval = syna_tcm_buf_alloc(&tcm_msg->out, payload_len + 3); + if (retval < 0) { + LOGE("Fail to allocate memory for internal buf.out\n"); + goto exit; + } + + if (command != CMD_CONTINUE_WRITE) { + /* construct the command packet + * size = 1-byte command + 2-byte length + payload + */ + size = payload_len + 3; + + tcm_msg->out.buf[0] = command; + tcm_msg->out.buf[1] = (unsigned char)payload_len; + tcm_msg->out.buf[2] = (unsigned char)(payload_len >> 8); + + if (payload_len > 0) { + retval = syna_pal_mem_cpy(&tcm_msg->out.buf[3], + tcm_msg->out.buf_size - 3, + payload, + payload_len, + payload_len + ); + if (retval < 0) { + LOGE("Fail to copy payload\n"); + goto exit; + } + } + } else { + /* construct the continued writes packet + * size = 1-byte continued write + payload + */ + size = payload_len + 1; + + tcm_msg->out.buf[0] = CMD_CONTINUE_WRITE; + + retval = syna_pal_mem_cpy(&tcm_msg->out.buf[1], + tcm_msg->out.buf_size - 1, + payload, + payload_len, + payload_len + ); + if (retval < 0) { + LOGE("Fail to copy continued write\n"); + goto exit; + } + } + + /* write command packet to the device */ + retval = syna_tcm_write(tcm_dev, + tcm_msg->out.buf, + size + ); + if (retval < 0) { + LOGE("Fail to write %d bytes to device\n", size); + goto exit; + } + +exit: + syna_tcm_buf_unlock(&tcm_msg->out); + + return retval; +} + +/** + * syna_tcm_v1_continued_read() + * + * The remaining data payload is read in continuously until the end of data. + * All the retrieved data is appended to the internal buffer.in. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] length: length of payload data + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_v1_continued_read(struct tcm_dev *tcm_dev, + unsigned int length) +{ + int retval = 0; + unsigned char code; + unsigned int idx; + unsigned int offset; + unsigned int chunks; + unsigned int chunk_space; + unsigned int xfer_length; + unsigned int total_length; + unsigned int remaining_length; + struct tcm_message_data_blob *tcm_msg = NULL; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + tcm_msg = &tcm_dev->msg_data; + + /* continued read packet contains the header, payload, and a padding */ + total_length = MESSAGE_HEADER_SIZE + length + 1; + remaining_length = total_length - MESSAGE_HEADER_SIZE; + + syna_tcm_buf_lock(&tcm_msg->in); + + /* in case the current buf.in is smaller than requested size */ + retval = syna_tcm_buf_realloc(&tcm_msg->in, + total_length + 1); + if (retval < 0) { + LOGE("Fail to allocate memory for internal buf_in\n"); + syna_tcm_buf_unlock(&tcm_msg->in); + return retval; + } + + /* available chunk space for payload = + * total chunk size - (header marker byte + header status byte) + */ + if (tcm_dev->max_rd_size == 0) + chunk_space = remaining_length; + else + chunk_space = tcm_dev->max_rd_size - 2; + + chunks = syna_pal_ceil_div(remaining_length, chunk_space); + chunks = chunks == 0 ? 1 : chunks; + + offset = MESSAGE_HEADER_SIZE; + + syna_tcm_buf_lock(&tcm_msg->temp); + + for (idx = 0; idx < chunks; idx++) { + if (remaining_length > chunk_space) + xfer_length = chunk_space; + else + xfer_length = remaining_length; + + if (xfer_length == 1) { + tcm_msg->in.buf[offset] = TCM_V1_MESSAGE_PADDING; + offset += xfer_length; + remaining_length -= xfer_length; + continue; + } + + /* allocate the internal temp buffer */ + retval = syna_tcm_buf_alloc(&tcm_msg->temp, + xfer_length + 2); + if (retval < 0) { + LOGE("Fail to allocate memory for internal buf.temp\n"); + goto exit; + } + /* retrieve data from the bus + * data should include header marker and status code + */ + retval = syna_tcm_v1_read(tcm_dev, + xfer_length + 2, + tcm_msg->temp.buf, + tcm_msg->temp.buf_size + ); + if (retval < 0) { + LOGE("Fail to read %d bytes from device\n", + xfer_length + 2); + goto exit; + } + + /* check the data content */ + code = tcm_msg->temp.buf[1]; + + if (code != STATUS_CONTINUED_READ) { + LOGE("Incorrect status code 0x%02x at %d out of %d\n", + code, idx, chunks); + retval = _EIO; + goto exit; + } + + /* copy data from internal buffer.temp to buffer.in */ + retval = syna_pal_mem_cpy(&tcm_msg->in.buf[offset], + tcm_msg->in.buf_size - offset, + &tcm_msg->temp.buf[2], + tcm_msg->temp.buf_size - 2, + xfer_length); + if (retval < 0) { + LOGE("Fail to copy payload\n"); + goto exit; + } + + offset += xfer_length; + remaining_length -= xfer_length; + } + +exit: + syna_tcm_buf_unlock(&tcm_msg->temp); + syna_tcm_buf_unlock(&tcm_msg->in); + + return retval; +} + +/** + * syna_tcm_v1_read_message() + * + * Read in a TouchCom packet from device. + * The packet including its payload is read in from device and stored in + * the internal buffer.resp or buffer.report based on the code received. + * + * @param + * [ in] tcm_dev: the device handle + * [out] status_report_code: status code or report code received + * + * @return + * 0 or positive value on success; otherwise, on error. + */ +static int syna_tcm_v1_read_message(struct tcm_dev *tcm_dev, + unsigned char *status_report_code) +{ + int retval = 0; + struct tcm_v1_message_header *header; + struct tcm_message_data_blob *tcm_msg = NULL; + syna_pal_mutex_t *rw_mutex = NULL; + syna_pal_completion_t *cmd_completion = NULL; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + tcm_msg = &tcm_dev->msg_data; + rw_mutex = &tcm_msg->rw_mutex; + cmd_completion = &tcm_msg->cmd_completion; + + if (status_report_code) + *status_report_code = STATUS_INVALID; + + syna_pal_mutex_lock(rw_mutex); + + syna_tcm_buf_lock(&tcm_msg->in); + + /* read in the message header from device */ + retval = syna_tcm_v1_read(tcm_dev, + MESSAGE_HEADER_SIZE, + tcm_msg->in.buf, + tcm_msg->in.buf_size + ); + if (retval < 0) { + LOGE("Fail to read message header from device\n"); + syna_tcm_buf_unlock(&tcm_msg->in); + + tcm_msg->status_report_code = STATUS_INVALID; + tcm_msg->payload_length = 0; + goto exit; + } + + /* check the message header */ + header = (struct tcm_v1_message_header *)tcm_msg->in.buf; + + tcm_msg->status_report_code = header->code; + + tcm_msg->payload_length = syna_pal_le2_to_uint(header->length); + + if (tcm_msg->status_report_code != STATUS_IDLE) + LOGD("Status code: 0x%02x, length: %d (%02x %02x %02x %02x)\n", + tcm_msg->status_report_code, tcm_msg->payload_length, + header->data[0], header->data[1], header->data[2], + header->data[3]); + + syna_tcm_buf_unlock(&tcm_msg->in); + + if ((tcm_msg->status_report_code <= STATUS_ERROR) || + (tcm_msg->status_report_code == STATUS_INVALID)) { + switch (tcm_msg->status_report_code) { + case STATUS_OK: + break; + case STATUS_CONTINUED_READ: + LOGE("Out-of-sync continued read\n"); + case STATUS_IDLE: + tcm_msg->payload_length = 0; + retval = 0; + goto exit; + default: + LOGE("Incorrect Status code, 0x%02x\n", + tcm_msg->status_report_code); + tcm_msg->payload_length = 0; + goto do_dispatch; + } + } + + if (tcm_msg->payload_length == 0) + goto do_dispatch; + + /* retrieve the remaining data, if any */ + retval = syna_tcm_v1_continued_read(tcm_dev, + tcm_msg->payload_length); + if (retval < 0) { + LOGE("Fail to do continued read\n"); + goto exit; + } + + /* refill the header for dispatching */ + syna_tcm_buf_lock(&tcm_msg->in); + + tcm_msg->in.buf[0] = TCM_V1_MESSAGE_MARKER; + tcm_msg->in.buf[1] = tcm_msg->status_report_code; + tcm_msg->in.buf[2] = (unsigned char)tcm_msg->payload_length; + tcm_msg->in.buf[3] = (unsigned char)(tcm_msg->payload_length >> 8); + + syna_tcm_buf_unlock(&tcm_msg->in); + +do_dispatch: + /* duplicate the data to external buffer */ + syna_tcm_buf_lock(&tcm_dev->external_buf); + if (tcm_msg->payload_length > 0) { + retval = syna_tcm_buf_alloc(&tcm_dev->external_buf, + tcm_msg->payload_length); + if (retval < 0) { + LOGE("Fail to allocate memory, external_buf invalid\n"); + } else { + retval = syna_pal_mem_cpy(&tcm_dev->external_buf.buf[0], + tcm_msg->payload_length, + &tcm_msg->in.buf[MESSAGE_HEADER_SIZE], + tcm_msg->in.buf_size - MESSAGE_HEADER_SIZE, + tcm_msg->payload_length); + if (retval < 0) + LOGE("Fail to copy data to external buffer\n"); + } + } + tcm_dev->external_buf.data_length = tcm_msg->payload_length; + syna_tcm_buf_unlock(&tcm_dev->external_buf); + + /* process the retrieved packet */ + if (tcm_msg->status_report_code >= REPORT_IDENTIFY) + syna_tcm_v1_dispatch_report(tcm_dev); + else + syna_tcm_v1_dispatch_response(tcm_dev); + + /* copy the status report code to caller */ + if (status_report_code) + *status_report_code = tcm_msg->status_report_code; + + retval = 0; + +exit: + /* raise the completion event when errors out */ + if (retval < 0) { + if (ATOMIC_GET(tcm_msg->command_status) == CMD_STATE_BUSY) { + ATOMIC_SET(tcm_msg->command_status, CMD_STATE_ERROR); + syna_pal_completion_complete(cmd_completion); + } + } + + syna_pal_mutex_unlock(rw_mutex); + + return retval; +} + +/** + * syna_tcm_v1_write_message() + * + * Write message including command and its payload to TouchCom device. + * Then, the response of the command generated by the device will be + * read in and stored in internal buffer.resp. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] command: TouchComm command + * [ in] payload: data payload, if any + * [ in] payload_len: length of data payload, if any + * [out] resp_code: response code returned + * [ in] delay_ms_resp: delay time for response reading. + * a positive value presents the time for polling; + * or, set '0' or 'RESP_IN_ATTN' for ATTN driven + * + * @return + * 0 or positive value on success; otherwise, on error. + */ +static int syna_tcm_v1_write_message(struct tcm_dev *tcm_dev, + unsigned char command, unsigned char *payload, + unsigned int payload_len, unsigned char *resp_code, + unsigned int delay_ms_resp) +{ + int retval = 0; + unsigned int idx; + unsigned int chunks; + unsigned int chunk_space; + unsigned int xfer_length; + unsigned int remaining_length; + int timeout = 0; + int polling_ms = 0; + struct tcm_message_data_blob *tcm_msg = NULL; + syna_pal_mutex_t *cmd_mutex = NULL; + syna_pal_mutex_t *rw_mutex = NULL; + syna_pal_completion_t *cmd_completion = NULL; + bool has_irq_ctrl = false; + bool in_polling = false; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + tcm_msg = &tcm_dev->msg_data; + cmd_mutex = &tcm_msg->cmd_mutex; + rw_mutex = &tcm_msg->rw_mutex; + cmd_completion = &tcm_msg->cmd_completion; + + if (resp_code) + *resp_code = STATUS_INVALID; + + /* indicate which mode is used */ + in_polling = (delay_ms_resp != RESP_IN_ATTN); + + /* irq control is enabled only when the operations is implemented + * and the current status of irq is enabled. + * do not enable irq if it is disabled by someone. + */ + has_irq_ctrl = (bool)(tcm_dev->hw_if->ops_enable_irq != NULL); + has_irq_ctrl &= tcm_dev->hw_if->bdata_attn.irq_enabled; + + /* disable irq when using polling mode */ + if (has_irq_ctrl && in_polling && tcm_dev->hw_if->ops_enable_irq) + tcm_dev->hw_if->ops_enable_irq(tcm_dev->hw_if, false); + + syna_pal_mutex_lock(cmd_mutex); + + syna_pal_mutex_lock(rw_mutex); + + ATOMIC_SET(tcm_msg->command_status, CMD_STATE_BUSY); + + /* reset the command completion */ + syna_pal_completion_reset(cmd_completion); + + tcm_msg->command = command; + + remaining_length = payload_len; + + /* available space for payload = total size - command byte */ + if (tcm_dev->max_wr_size == 0) + chunk_space = remaining_length; + else + chunk_space = tcm_dev->max_wr_size - 1; + + chunks = syna_pal_ceil_div(remaining_length, chunk_space); + chunks = chunks == 0 ? 1 : chunks; + + LOGD("Command: 0x%02x, payload len: %d\n", command, payload_len); + + /* send out command packets + * + * separate into several sub-packets if the overall size is over + * than the maximum write size. + */ + for (idx = 0; idx < chunks; idx++) { + if (remaining_length > chunk_space) + xfer_length = chunk_space; + else + xfer_length = remaining_length; + + if (idx == 0) { + retval = syna_tcm_v1_write(tcm_dev, + tcm_msg->command, + &payload[0], + xfer_length); + } else { + retval = syna_tcm_v1_write(tcm_dev, + CMD_CONTINUE_WRITE, + &payload[idx * chunk_space], + xfer_length); + } + + if (retval < 0) { + LOGE("Fail to write %d bytes to device\n", + xfer_length); + syna_pal_mutex_unlock(rw_mutex); + goto exit; + } + + remaining_length -= xfer_length; + + if (chunks > 1) + syna_pal_sleep_us(WR_DELAY_US_MIN, WR_DELAY_US_MAX); + } + + syna_pal_mutex_unlock(rw_mutex); + + /* handle the command response + * + * assuming to select the polling mode, the while-loop below will + * repeatedly read in the respose data based on the given polling + * time; otherwise, wait until receiving a completion event from + * interupt thread. + */ + timeout = 0; + if (!in_polling) + polling_ms = CMD_RESPONSE_TIMEOUT_MS; + else + polling_ms = delay_ms_resp; + + do { + /* wait for the completion event triggered by read_message */ + retval = syna_pal_completion_wait_for(cmd_completion, + polling_ms); + /* reset the status when times out and keep in polling */ + if (retval < 0) + ATOMIC_SET(tcm_msg->command_status, CMD_STATE_BUSY); + + /* break when geting a valid resp; otherwise, keep in polling */ + if (ATOMIC_GET(tcm_msg->command_status) == CMD_STATE_IDLE) + goto check_response; + + if (in_polling) { + /* retrieve the message packet back */ + retval = syna_tcm_v1_read_message(tcm_dev, NULL); + /* keep in polling if still not having a valid resp */ + if (retval < 0) + syna_pal_completion_reset(cmd_completion); + } + + timeout += polling_ms + 10; + + } while (timeout < CMD_RESPONSE_TIMEOUT_MS); + + /* check the status of response data + * according to the touchcomm spec, each command message + * should have an associated reponse message. + */ +check_response: + if (ATOMIC_GET(tcm_msg->command_status) != CMD_STATE_IDLE) { + if (timeout >= CMD_RESPONSE_TIMEOUT_MS) { + LOGE("Timed out wait for response of command 0x%02x\n", + command); + retval = _ETIMEDOUT; + goto exit; + } else { + LOGE("Fail to get valid response of command 0x%02x\n", + command); + retval = _EIO; + goto exit; + } + } + + /* copy response code to the caller */ + if (resp_code) + *resp_code = tcm_msg->response_code; + + if (tcm_msg->response_code != STATUS_OK) { + LOGE("Error code 0x%02x of command 0x%02x\n", + tcm_msg->response_code, tcm_msg->command); + retval = _EIO; + } else { + retval = 0; + } + +exit: + tcm_msg->command = CMD_NONE; + + ATOMIC_SET(tcm_msg->command_status, CMD_STATE_IDLE); + + syna_pal_mutex_unlock(cmd_mutex); + + /* recovery the irq if using polling mode */ + if (has_irq_ctrl && in_polling && tcm_dev->hw_if->ops_enable_irq) + tcm_dev->hw_if->ops_enable_irq(tcm_dev->hw_if, true); + + return retval; +} + +/** + * syna_tcm_v1_detect() + * + * For TouchCom v1 protocol, the given raw data must start with a specific + * maker code. If so, read the remaining packet from TouchCom device. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] data: raw 4-byte data + * [ in] size: length of input data in bytes + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_v1_detect(struct tcm_dev *tcm_dev, unsigned char *data, + unsigned int size) +{ + int retval; + struct tcm_v1_message_header *header; + struct tcm_message_data_blob *tcm_msg = NULL; + unsigned int payload_length = 0; + unsigned char resp_code = 0; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if ((!data) || (size < MESSAGE_HEADER_SIZE)) { + LOGE("Invalid parameters\n"); + return _EINVAL; + } + + tcm_msg = &tcm_dev->msg_data; + + header = (struct tcm_v1_message_header *)data; + + if (header->marker != TCM_V1_MESSAGE_MARKER) + return _ENODEV; + + /* after initially powering on, the identify report should be the + * first packet + */ + if (header->code == REPORT_IDENTIFY) { + + payload_length = syna_pal_le2_to_uint(header->length); + + /* retrieve the identify info packet */ + retval = syna_tcm_v1_continued_read(tcm_dev, + payload_length); + if (retval < 0) { + LOGE("Fail to read in identify info packet\n"); + return retval; + } + } else { + /* if not, send an identify command instead */ + retval = syna_tcm_v1_write_message(tcm_dev, + CMD_IDENTIFY, + NULL, + 0, + &resp_code, + RESP_IN_POLLING); + if (retval < 0) { + /* in case the identify command is not working, + * send a rest command as the workaround + */ + retval = syna_tcm_v1_write_message(tcm_dev, + CMD_RESET, + NULL, + 0, + &resp_code, + RESET_DELAY_MS); + if (retval < 0) { + LOGE("Fail to identify the device\n"); + return _ENODEV; + } + } + + payload_length = tcm_msg->payload_length; + } + + /* parse the identify info packet */ + syna_tcm_buf_lock(&tcm_msg->in); + + retval = syna_tcm_v1_parse_idinfo(tcm_dev, + &tcm_msg->in.buf[MESSAGE_HEADER_SIZE], + tcm_msg->in.buf_size - MESSAGE_HEADER_SIZE, + payload_length); + if (retval < 0) { + LOGE("Fail to identify device\n"); + syna_tcm_buf_unlock(&tcm_msg->in); + return retval; + } + + syna_tcm_buf_unlock(&tcm_msg->in); + + /* expose the read / write operations */ + tcm_dev->read_message = syna_tcm_v1_read_message; + tcm_dev->write_message = syna_tcm_v1_write_message; + + return retval; +} diff --git a/tcm/synaptics_touchcom_core_v2.c b/tcm/synaptics_touchcom_core_v2.c new file mode 100644 index 0000000..c3f0d56 --- /dev/null +++ b/tcm/synaptics_touchcom_core_v2.c @@ -0,0 +1,1445 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +/** + * @file synaptics_touchcom_core_v2.c + * + * This file implements the TouchComm version 2 command-response protocol + */ + +#include "synaptics_touchcom_core_dev.h" + +#define BITS_IN_MESSAGE_HEADER (MESSAGE_HEADER_SIZE * 8) + +#define HOST_PRIMARY (0) + +#define COMMAND_RETRY_TIMES (5) + +#define CHECK_PACKET_CRC + +/** + * @section: Header of TouchComm v2 Message Packet + * + * The 4-byte header in the TouchComm v2 packet + */ +struct tcm_v2_message_header { + union { + struct { + unsigned char code; + unsigned char length[2]; + unsigned char byte3; + }; + unsigned char data[MESSAGE_HEADER_SIZE]; + }; +}; + +/* helper to execute a tcm v2 command + */ +static int syna_tcm_v2_execute_cmd_request(struct tcm_dev *tcm_dev, + unsigned char command, unsigned char *payload, + unsigned int payload_length); + +/** + * @section: Lookup table for checksum calculation + * + * @subsection: crc6_table + * lookup table for crc6 calculation + * + * @subsection: crc16_table + * lookup table for crc16 calculation + */ +static unsigned short crc6_table[16] = { + 0, 268, 536, 788, 1072, 1340, 1576, 1828, + 2144, 2412, 2680, 2932, 3152, 3420, 3656, 3908 +}; + +static unsigned short crc16_table[256] = { + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, + 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, + 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, + 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, + 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, + 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, + 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, + 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, + 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, + 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, + 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, + 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, + 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, + 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, + 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, + 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, + 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, + 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, + 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, + 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, + 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, + 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, + 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0 +}; + +/** + * syna_tcm_v2_crc6() + * + * Calculate the crc-6 with polynomial for TouchCom v2 header. + * + * @param + * [ in] p: byte array for the calculation + * [ in] bits: number of bits + * + * @return + * the crc-6 value + */ +static unsigned char syna_tcm_v2_crc6(unsigned char *p, unsigned int bits) +{ + unsigned short r = 0x003F << 2; + unsigned short x; + + for (; bits > 8; bits -= 8) { + r ^= *p++; + r = (r << 4) ^ crc6_table[r >> 4]; + r = (r << 4) ^ crc6_table[r >> 4]; + } + + if (bits > 0) { + x = *p; + while (bits--) { + if (x & 0x80) + r ^= 0x80; + + x <<= 1; + r <<= 1; + if (r & 0x100) + r ^= (0x03 << 2); + } + } + + return (unsigned char)((r >> 2) & 0x3F); +} +/** + * syna_tcm_v2_crc6() + * + * Calculate the crc-16 for TouchCom v2 packet. + * + * @param + * [ in] p: byte array for the calculation + * [ in] len: length in bytes + * + * @return + * the crc-16 value + */ +static unsigned short syna_tcm_v2_crc16(unsigned char *p, unsigned int len) +{ + unsigned short r = 0xFFFF; + + while (len--) + r = (r << 8) ^ crc16_table[(r >> 8) ^ *p++]; + + return r; +} + +/** + * syna_tcm_v2_set_max_read_size() + * + * Configure the max length for message reading. + * + * @param + * [ in] tcm_dev: the device handle + * + * @return + * none. + */ +static int syna_tcm_v2_set_max_read_size(struct tcm_dev *tcm_dev) +{ + int retval; + unsigned int rd_size; + struct tcm_identification_info *id_info; + unsigned char data[2] = { 0 }; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + id_info = &tcm_dev->id_info; + + rd_size = syna_pal_le2_to_uint(id_info->max_read_size); + + if (rd_size == 0) { + LOGE("Invalid max_read_length: %d\n", rd_size); + return 0; + } + + if (rd_size == tcm_dev->max_rd_size) + return 0; + + tcm_dev->max_rd_size = MIN(rd_size, tcm_dev->max_rd_size); + + LOGD("max_rd_size = %d\n", tcm_dev->max_rd_size); + + data[0] = (unsigned char)tcm_dev->max_rd_size; + data[1] = (unsigned char)(tcm_dev->max_rd_size >> 8); + + retval = syna_tcm_v2_execute_cmd_request(tcm_dev, + CMD_TCM2_SET_MAX_READ_LENGTH, + data, + sizeof(data)); + if (retval < 0) { + LOGE("Fail to set max_read_length\n"); + return retval; + } + + return 0; +} + +/** + * syna_tcm_v2_parse_idinfo() + * + * Copy the given data to the identification info structure + * and parse the basic information, e.g. fw build id. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] data: data buffer + * [ in] size: size of given data buffer + * [ in] data_len: length of actual data + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_v2_parse_idinfo(struct tcm_dev *tcm_dev, + unsigned char *data, unsigned int size, unsigned int data_len) +{ + int retval; + unsigned int wr_size = 0; + unsigned int build_id = 0; + struct tcm_identification_info *id_info; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if ((!data) || (data_len == 0)) { + LOGE("Invalid given data buffer\n"); + return _EINVAL; + } + + id_info = &tcm_dev->id_info; + + retval = syna_pal_mem_cpy((unsigned char *)id_info, + sizeof(struct tcm_identification_info), + data, + size, + MIN(sizeof(*id_info), data_len)); + if (retval < 0) { + LOGE("Fail to copy identification info\n"); + return retval; + } + + build_id = syna_pal_le4_to_uint(id_info->build_id); + + wr_size = syna_pal_le2_to_uint(id_info->max_write_size); + tcm_dev->max_wr_size = MIN(wr_size, WR_CHUNK_SIZE); + if (tcm_dev->max_wr_size == 0) { + tcm_dev->max_wr_size = wr_size; + LOGD("max_wr_size = %d\n", tcm_dev->max_wr_size); + } + + LOGI("TCM Fw mode: 0x%02x\n", id_info->mode); + + if (tcm_dev->packrat_number != build_id) + tcm_dev->packrat_number = build_id; + + /* set up the max. reading length */ + retval = syna_tcm_v2_set_max_read_size(tcm_dev); + if (retval < 0) { + LOGE("Fail to setup the max reading length\n"); + return retval; + } + + tcm_dev->dev_mode = id_info->mode; + + return 0; +} + +/** + * syna_tcm_v2_dispatch_report() + * + * Handle the TouchCom report packet being received. + * + * If it's an identify report, parse the identification packet and signal + * the command completion just in case. + * Otherwise, copy the data from internal buffer.in to internal buffer.report + * + * @param + * [ in] tcm_dev: the device handle + * + * @return + * none. + */ +static void syna_tcm_v2_dispatch_report(struct tcm_dev *tcm_dev) +{ + int retval; + struct tcm_message_data_blob *tcm_msg = NULL; + syna_pal_completion_t *cmd_completion = NULL; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return; + } + + tcm_msg = &tcm_dev->msg_data; + cmd_completion = &tcm_msg->cmd_completion; + + tcm_msg->report_code = tcm_msg->status_report_code; + + if (tcm_msg->payload_length == 0) { + tcm_dev->resp_buf.data_length = tcm_msg->payload_length; + ATOMIC_SET(tcm_msg->command_status, CMD_STATE_IDLE); + goto exit; + } + + /* The identify report may be resulted from reset or fw mode switching + */ + if (tcm_msg->report_code == REPORT_IDENTIFY) { + + syna_tcm_buf_lock(&tcm_msg->in); + + retval = syna_tcm_v2_parse_idinfo(tcm_dev, + &tcm_msg->in.buf[MESSAGE_HEADER_SIZE], + tcm_msg->in.buf_size - MESSAGE_HEADER_SIZE, + tcm_msg->payload_length); + if (retval < 0) { + LOGE("Fail to identify device\n"); + syna_tcm_buf_unlock(&tcm_msg->in); + return; + } + + syna_tcm_buf_unlock(&tcm_msg->in); + + /* in case, the identify info packet is caused by the command */ + if (ATOMIC_GET(tcm_msg->command_status) == CMD_STATE_BUSY) { + switch (tcm_msg->command) { + case CMD_RESET: + LOGD("Reset by CMD_RESET\n"); + case CMD_REBOOT_TO_ROM_BOOTLOADER: + case CMD_RUN_BOOTLOADER_FIRMWARE: + case CMD_RUN_APPLICATION_FIRMWARE: + case CMD_ENTER_PRODUCTION_TEST_MODE: + case CMD_ROMBOOT_RUN_BOOTLOADER_FIRMWARE: + tcm_msg->status_report_code = STATUS_OK; + tcm_msg->response_code = STATUS_OK; + ATOMIC_SET(tcm_msg->command_status, + CMD_STATE_IDLE); + syna_pal_completion_complete(cmd_completion); + goto exit; + default: + LOGN("Device has been reset\n"); + ATOMIC_SET(tcm_msg->command_status, + CMD_STATE_ERROR); + syna_pal_completion_complete(cmd_completion); + goto exit; + } + } + } + + /* store the received report into the internal buffer.report */ + syna_tcm_buf_lock(&tcm_dev->report_buf); + + retval = syna_tcm_buf_alloc(&tcm_dev->report_buf, + tcm_msg->payload_length); + if (retval < 0) { + LOGE("Fail to allocate memory for internal buf.report\n"); + syna_tcm_buf_unlock(&tcm_dev->report_buf); + goto exit; + } + + syna_tcm_buf_lock(&tcm_msg->in); + + retval = syna_pal_mem_cpy(tcm_dev->report_buf.buf, + tcm_dev->report_buf.buf_size, + &tcm_msg->in.buf[MESSAGE_HEADER_SIZE], + tcm_msg->in.buf_size - MESSAGE_HEADER_SIZE, + tcm_msg->payload_length); + if (retval < 0) { + LOGE("Fail to copy payload to buf_report\n"); + syna_tcm_buf_unlock(&tcm_msg->in); + syna_tcm_buf_unlock(&tcm_dev->report_buf); + goto exit; + } + + tcm_dev->report_buf.data_length = tcm_msg->payload_length; + + syna_tcm_buf_unlock(&tcm_msg->in); + syna_tcm_buf_unlock(&tcm_dev->report_buf); + +exit: + return; +} + +/** + * syna_tcm_v2_dispatch_response() + * + * Handle the response packet. + * + * Copy the data from internal buffer.in to internal buffer.resp, + * and then signal the command completion. + * + * @param + * [ in] tcm_dev: the device handle + * + * @return + * none. + */ +static void syna_tcm_v2_dispatch_response(struct tcm_dev *tcm_dev) +{ + int retval; + unsigned int resp_data_length; + struct tcm_message_data_blob *tcm_msg = NULL; + syna_pal_completion_t *cmd_completion = NULL; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return; + } + + tcm_msg = &tcm_dev->msg_data; + cmd_completion = &tcm_msg->cmd_completion; + + if (ATOMIC_GET(tcm_msg->command_status) != CMD_STATE_BUSY) + return; + + resp_data_length = tcm_msg->payload_length; + + if (resp_data_length == 0) { + ATOMIC_SET(tcm_msg->command_status, CMD_STATE_IDLE); + goto exit; + } + + /* store the received report into the temporary buffer */ + syna_tcm_buf_lock(&tcm_dev->resp_buf); + + retval = syna_tcm_buf_alloc(&tcm_dev->resp_buf, + resp_data_length + 1); + if (retval < 0) { + LOGE("Fail to allocate memory for internal buf.resp\n"); + syna_tcm_buf_unlock(&tcm_dev->resp_buf); + ATOMIC_SET(tcm_msg->command_status, CMD_STATE_ERROR); + goto exit; + } + + syna_tcm_buf_lock(&tcm_msg->in); + + retval = syna_pal_mem_cpy(tcm_dev->resp_buf.buf, + tcm_dev->resp_buf.buf_size, + &tcm_msg->in.buf[MESSAGE_HEADER_SIZE], + tcm_msg->in.buf_size - MESSAGE_HEADER_SIZE, + resp_data_length); + if (retval < 0) { + LOGE("Fail to copy payload to internal resp_buf\n"); + syna_tcm_buf_unlock(&tcm_msg->in); + syna_tcm_buf_unlock(&tcm_dev->resp_buf); + ATOMIC_SET(tcm_msg->command_status, CMD_STATE_ERROR); + goto exit; + } + + tcm_dev->resp_buf.data_length = resp_data_length; + + syna_tcm_buf_unlock(&tcm_msg->in); + syna_tcm_buf_unlock(&tcm_dev->resp_buf); + + ATOMIC_SET(tcm_msg->command_status, CMD_STATE_IDLE); + +exit: + syna_pal_completion_complete(cmd_completion); +} + +/** + * syna_tcm_v2_read() + * + * Read in a TouchCom packet from device. + * Checking the CRC is necessary to ensure a valid message received. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] rd_length: number of reading bytes; + * '0' means to read the message header only + * [out] buf: pointer to a buffer which is stored the retrieved data + * [out] buf_size: size of the buffer pointed + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_v2_read(struct tcm_dev *tcm_dev, unsigned int rd_length, + unsigned char **buf, unsigned int *buf_size) +{ + int retval; + struct tcm_v2_message_header *header; + int max_rd_size; + int xfer_len; + unsigned char crc6 = 0; + struct tcm_message_data_blob *tcm_msg = NULL; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + tcm_msg = &tcm_dev->msg_data; + max_rd_size = tcm_dev->max_rd_size; + + /* continued packet crc if containing payload data */ + xfer_len = (rd_length > 0) ? (rd_length + 2) : rd_length; + xfer_len += sizeof(struct tcm_v2_message_header); + + if ((max_rd_size != 0) && (xfer_len > max_rd_size)) { + LOGE("Invalid xfer length, len: %d, max_rd_size: %d\n", + xfer_len, max_rd_size); + tcm_msg->status_report_code = STATUS_INVALID; + return _EINVAL; + } + + syna_tcm_buf_lock(&tcm_msg->temp); + + /* allocate the internal temp buffer */ + retval = syna_tcm_buf_alloc(&tcm_msg->temp, xfer_len); + if (retval < 0) { + LOGE("Fail to allocate memory for internal buf.temp\n"); + goto exit; + } + /* read data from the bus */ + retval = syna_tcm_read(tcm_dev, + tcm_msg->temp.buf, + xfer_len); + if (retval < 0) { + LOGE("Fail to read from device\n"); + goto exit; + } + + header = (struct tcm_v2_message_header *)tcm_msg->temp.buf; + + /* check header crc always */ + crc6 = syna_tcm_v2_crc6(header->data, BITS_IN_MESSAGE_HEADER); + if (crc6 != 0) { + LOGE("Invalid header crc: 0x%02x\n", (header->byte3 & 0x3f)); + + tcm_msg->status_report_code = STATUS_PACKET_CORRUPTED; + goto exit; + } + +#ifdef CHECK_PACKET_CRC + /* check packet crc */ + if (rd_length > 0) { + if (syna_tcm_v2_crc16(&tcm_msg->temp.buf[0], xfer_len) != 0) { + LOGE("Invalid packet crc: %02x %02x\n", + tcm_msg->temp.buf[xfer_len - 2], + tcm_msg->temp.buf[xfer_len - 1]); + + tcm_msg->status_report_code = STATUS_PACKET_CORRUPTED; + goto exit; + } + } +#endif + + tcm_msg->status_report_code = header->code; + + tcm_msg->payload_length = syna_pal_le2_to_uint(header->length); + + if (tcm_msg->status_report_code != STATUS_IDLE) + LOGD("Status code: 0x%02x, length: %d (%02x %02x %02x %02x)\n", + tcm_msg->status_report_code, tcm_msg->payload_length, + header->data[0], header->data[1], header->data[2], + header->data[3]); + + *buf = tcm_msg->temp.buf; + *buf_size = tcm_msg->temp.buf_size; + +exit: + syna_tcm_buf_unlock(&tcm_msg->temp); + + return retval; +} + +/** + * syna_tcm_v2_write() + * + * Construct the TouchCom v2 packet and send it to device. + * Add 4-byte header at the beginning of a message and appended crc if needed. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] command: command code + * [ in] payload: data payload if any + * [ in] payload_len: length of data payload if have any + * [ in] resend: flag for re-sending the packet + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_v2_write(struct tcm_dev *tcm_dev, unsigned char command, + unsigned char *payload, unsigned int payload_len, bool resend) +{ + int retval; + struct tcm_v2_message_header *header; + unsigned char bits = BITS_IN_MESSAGE_HEADER - 6; + int xfer_len; + int size = MESSAGE_HEADER_SIZE + payload_len; + unsigned short crc16; + int max_wr_size; + struct tcm_message_data_blob *tcm_msg = NULL; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + tcm_msg = &tcm_dev->msg_data; + max_wr_size = tcm_dev->max_wr_size; + + /* continued packet crc if containing payload data */ + xfer_len = (payload_len > 0) ? (payload_len + 2) : payload_len; + xfer_len += sizeof(struct tcm_v2_message_header); + + if ((max_wr_size != 0) && (xfer_len > max_wr_size)) { + LOGE("Invalid xfer length, len: %d, max_wr_size: %d\n", + xfer_len, max_wr_size); + tcm_msg->status_report_code = STATUS_INVALID; + return _EINVAL; + } + + syna_tcm_buf_lock(&tcm_msg->out); + + /* allocate the internal out buffer */ + retval = syna_tcm_buf_alloc(&tcm_msg->out, xfer_len); + if (retval < 0) { + LOGE("Fail to allocate memory for internal buf.out\n"); + goto exit; + } + + /* construct packet header */ + header = (struct tcm_v2_message_header *)tcm_msg->out.buf; + + if (resend) + tcm_msg->seq_toggle -= 1; + + header->code = command; + header->length[0] = (unsigned char)payload_len; + header->length[1] = (unsigned char)(payload_len >> 8); + header->byte3 = ((HOST_PRIMARY & 0x01) << 7); + header->byte3 |= ((tcm_msg->seq_toggle++ & 0x01) << 6); + header->byte3 |= syna_tcm_v2_crc6(header->data, bits); + + /* copy payload, if any */ + if (payload_len) { + retval = syna_pal_mem_cpy( + &tcm_msg->out.buf[MESSAGE_HEADER_SIZE], + tcm_msg->out.buf_size - MESSAGE_HEADER_SIZE, + payload, + payload_len, + payload_len); + if (retval < 0) { + LOGE("Fail to copy payload data\n"); + goto exit; + } + + /* append packet crc */ + crc16 = syna_tcm_v2_crc16(&tcm_msg->out.buf[0], size); + tcm_msg->out.buf[size] = (unsigned char)((crc16 >> 8) & 0xFF); + tcm_msg->out.buf[size + 1] = (unsigned char)(crc16 & 0xFF); + } + + /* write command packet to the bus */ + retval = syna_tcm_write(tcm_dev, + tcm_msg->out.buf, + xfer_len); + if (retval < 0) { + LOGE("Fail to write to device\n"); + goto exit; + } + +exit: + syna_tcm_buf_unlock(&tcm_msg->out); + + return retval; +} + +/** + * syna_tcm_v2_continued_read() + * + * Write a CMD_ACK to read in the remaining data payload continuously + * until the end of data. All the retrieved data is appended to the + * internal buffer.in. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] length: remaining data length in bytes + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_v2_continued_read(struct tcm_dev *tcm_dev, + unsigned int length) +{ + int retval; + unsigned char *tmp_buf; + unsigned int tmp_buf_size; + int retry_cnt = 0; + unsigned int idx; + unsigned int offset; + unsigned int chunks; + unsigned int chunk_space; + unsigned int xfer_length; + unsigned int total_length; + unsigned int remaining_length; + unsigned char command; + struct tcm_message_data_blob *tcm_msg = NULL; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + tcm_msg = &tcm_dev->msg_data; + + /* continued read packet contains the header and its payload */ + total_length = MESSAGE_HEADER_SIZE + tcm_msg->payload_length; + + remaining_length = length; + + offset = tcm_msg->payload_length - length; + + syna_tcm_buf_lock(&tcm_msg->in); + + /* extend the internal buf_in if needed */ + retval = syna_tcm_buf_realloc(&tcm_msg->in, + total_length + 1); + if (retval < 0) { + LOGE("Fail to allocate memory for internal buf_in\n"); + goto exit; + } + + /* available space for payload = total chunk size - header - crc */ + chunk_space = tcm_dev->max_rd_size; + if (chunk_space == 0) + chunk_space = remaining_length; + else + chunk_space = chunk_space - (MESSAGE_HEADER_SIZE + 2); + + chunks = syna_pal_ceil_div(remaining_length, chunk_space); + chunks = chunks == 0 ? 1 : chunks; + + offset += MESSAGE_HEADER_SIZE; + + /* send CMD_ACK for a continued read */ + command = CMD_TCM2_ACK; + + for (idx = 0; idx < chunks; idx++) { +retry: + LOGD("Command: 0x%02x\n", command); + + /* construct the command packet */ + retval = syna_tcm_v2_write(tcm_dev, + command, + NULL, + 0, + false); + if (retval < 0) { + LOGE("Fail to send CMD_TCM2_ACK in continued read\n"); + goto exit; + } + + if (remaining_length > chunk_space) + xfer_length = chunk_space; + else + xfer_length = remaining_length; + + /* read in the requested size of data */ + retval = syna_tcm_v2_read(tcm_dev, + xfer_length, + &tmp_buf, + &tmp_buf_size); + if (retval < 0) { + LOGE("Fail to read %d bytes from device\n", + xfer_length); + goto exit; + } + + /* If see an error, retry the previous read transaction + * Send RETRY instead of CMD_ACK + */ + if (tcm_msg->status_report_code == STATUS_PACKET_CORRUPTED) { + if (retry_cnt > COMMAND_RETRY_TIMES) { + LOGE("Continued read packet corrupted\n"); + goto exit; + } + + retry_cnt += 1; + command = CMD_TCM2_RETRY; + + LOGW("Read corrupted, retry %d\n", retry_cnt); + goto retry; + } + + retry_cnt = 0; + command = CMD_TCM2_ACK; + + /* append data from temporary buffer to in_buf */ + syna_tcm_buf_lock(&tcm_msg->temp); + + /* copy data from internal buffer.temp to buffer.in */ + retval = syna_pal_mem_cpy(&tcm_msg->in.buf[offset], + tcm_msg->in.buf_size - offset, + &tmp_buf[MESSAGE_HEADER_SIZE], + tmp_buf_size - MESSAGE_HEADER_SIZE, + xfer_length); + if (retval < 0) { + LOGE("Fail to copy payload to internal buf_in\n"); + syna_tcm_buf_unlock(&tcm_msg->temp); + goto exit; + } + + syna_tcm_buf_unlock(&tcm_msg->temp); + + remaining_length -= xfer_length; + + offset += xfer_length; + } + + retval = 0; + +exit: + syna_tcm_buf_unlock(&tcm_msg->in); + + return retval; +} + +/** + * syna_tcm_v2_get_response() + * + * Read in the response packet from device. + * If containing payload data, use continued_read() function and read the + * remaining payload data. + * + * @param + * [ in] tcm_dev: the device handle + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_v2_get_response(struct tcm_dev *tcm_dev) +{ + int retval; + struct tcm_v2_message_header *header; + unsigned char *tmp_buf; + unsigned int tmp_buf_size; + struct tcm_message_data_blob *tcm_msg = NULL; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + tcm_msg = &tcm_dev->msg_data; + + /* read in the message header at first */ + retval = syna_tcm_v2_read(tcm_dev, + 0, + &tmp_buf, + &tmp_buf_size); + if (retval < 0) { + LOGE("Fail to read message header from device\n"); + return retval; + } + + /* error out once the response packet is corrupted */ + if (tcm_msg->status_report_code == STATUS_PACKET_CORRUPTED) + return 0; + + + /* allocate the required space = header + payload */ + syna_tcm_buf_lock(&tcm_msg->in); + + retval = syna_tcm_buf_alloc(&tcm_msg->in, + MESSAGE_HEADER_SIZE + tcm_msg->payload_length); + if (retval < 0) { + LOGE("Fail to reallocate memory for internal buf.in\n"); + syna_tcm_buf_unlock(&tcm_msg->in); + return retval; + } + + retval = syna_pal_mem_cpy(tcm_msg->in.buf, + tcm_msg->in.buf_size, + tmp_buf, + tmp_buf_size, + MESSAGE_HEADER_SIZE); + if (retval < 0) { + LOGE("Fail to copy data to internal buf_in\n"); + syna_tcm_buf_unlock(&tcm_msg->in); + return retval; + } + + syna_tcm_buf_unlock(&tcm_msg->in); + + /* read in payload, if any */ + if (tcm_msg->payload_length > 0) { + + retval = syna_tcm_v2_continued_read(tcm_dev, + tcm_msg->payload_length); + if (retval < 0) { + LOGE("Fail to read in payload data, size: %d)\n", + tcm_msg->payload_length); + return retval; + } + } + + syna_tcm_buf_lock(&tcm_msg->in); + + header = (struct tcm_v2_message_header *)tcm_msg->in.buf; + + tcm_msg->payload_length = syna_pal_le2_to_uint(header->length); + tcm_msg->status_report_code = header->code; + + syna_tcm_buf_unlock(&tcm_msg->in); + + return retval; +} + +/** + * syna_tcm_v2_send_cmd() + * + * Forward the given command and payload to syna_tcm_v2_write(). + * + * @param + * [ in] tcm_dev: the device handle + * [ in] command: command code + * [ in] payload: data payload if any + * [ in] payload_len: length of data payload if have any + * [ in] resend: flag for re-sending the packet + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static inline int syna_tcm_v2_send_cmd(struct tcm_dev *tcm_dev, + unsigned char command, unsigned char *payload, + unsigned int length, bool resend) +{ + int retval; + + retval = syna_tcm_v2_write(tcm_dev, + command, + payload, + length, + resend); + if (retval < 0) + LOGE("Fail to write Command 0x%02x to device\n", command); + + return retval; +} + +/** + * syna_tcm_v2_execute_cmd_request() + * + * Process the command message. + * The helper is responsible for sending the given command and its payload, + * to device. Once the total size of message is over the wr_chunk, divide + * into continued writes + * + * In addition, the response to the command generated by the device will be + * read in immediately. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] command: command code + * [ in] payload: data payload if any + * [ in] payload_length: length of payload in bytes + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_v2_execute_cmd_request(struct tcm_dev *tcm_dev, + unsigned char command, unsigned char *payload, + unsigned int payload_length) +{ + int retval; + unsigned int idx; + unsigned int offset; + unsigned int chunks; + unsigned int xfer_length; + unsigned int remaining_length; + int retry_cnt = 0; + unsigned int chunk_space; + struct tcm_message_data_blob *tcm_msg = NULL; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + tcm_msg = &tcm_dev->msg_data; + chunk_space = tcm_dev->max_wr_size; + + remaining_length = payload_length; + + /* available space for payload = total size - header - crc */ + if (chunk_space == 0) + chunk_space = remaining_length; + else + chunk_space = chunk_space - (MESSAGE_HEADER_SIZE + 2); + + chunks = syna_pal_ceil_div(remaining_length, chunk_space); + + chunks = chunks == 0 ? 1 : chunks; + + offset = 0; + + /* process the command message and handle the response + * to the command + */ + for (idx = 0; idx < chunks; idx++) { + if (remaining_length > chunk_space) + xfer_length = chunk_space; + else + xfer_length = remaining_length; + +retry: + /* send command to device */ + command = (idx == 0) ? command : CMD_CONTINUE_WRITE; + + LOGD("Command: 0x%02x\n", command); + + retval = syna_tcm_v2_send_cmd(tcm_dev, + command, + &payload[offset], + xfer_length, + (retry_cnt > 0)); + if (retval < 0) + goto exit; + + /* bus turnaround delay */ + syna_pal_sleep_us(TAT_DELAY_US_MIN, TAT_DELAY_US_MAX); + + /* get the response to the command immediately */ + retval = syna_tcm_v2_get_response(tcm_dev); + if (retval < 0) { + LOGE("Fail to get the response to command 0x%02x\n", + command); + goto exit; + } + + /* check the response code */ + tcm_msg->response_code = tcm_msg->status_report_code; + + LOGD("Response code: 0x%x\n", tcm_msg->response_code); + + if (tcm_msg->status_report_code >= REPORT_IDENTIFY) + goto next; + + switch (tcm_msg->status_report_code) { + case STATUS_NO_REPORT_AVAILABLE: + case STATUS_OK: + case STATUS_ACK: + retry_cnt = 0; + break; + case STATUS_PACKET_CORRUPTED: + case STATUS_RETRY_REQUESTED: + retry_cnt += 1; + break; + default: + LOGE("Incorrect status code 0x%02x of command 0x%02x\n", + tcm_msg->status_report_code, command); + goto exit; + } + + if (retry_cnt > 0) { + if (command == CMD_RESET) { + LOGE("Command CMD_RESET corrupted, exit\n"); + /* assume ACK and wait for interrupt assertion + * once the response of reset is corrupted + */ + tcm_msg->response_code = STATUS_ACK; + goto exit; + } else if (retry_cnt > COMMAND_RETRY_TIMES) { + LOGE("Command 0x%02x corrupted\n", command); + goto exit; + } + + LOGN("Command 0x%02x, retry %d\n", command, retry_cnt); + syna_pal_sleep_us(WR_DELAY_US_MIN, WR_DELAY_US_MAX); + + goto retry; + } +next: + offset += xfer_length; + + remaining_length -= xfer_length; + + if (chunks > 1) + syna_pal_sleep_us(WR_DELAY_US_MIN, WR_DELAY_US_MAX); + } + +exit: + return retval; +} + +/** + * syna_tcm_v2_read_message() + * + * Send a CMD_GET_REPORT to acquire a TouchCom v2 report packet from device. + * Meanwhile, the retrieved data will be stored in the internal buffer.resp + * or buffer.report. + * + * @param + * [ in] tcm_dev: the device handle + * [out] status_report_code: status code or report code received + * + * @return + * 0 or positive value on success; otherwise, on error. + */ +static int syna_tcm_v2_read_message(struct tcm_dev *tcm_dev, + unsigned char *status_report_code) +{ + int retval; + struct tcm_message_data_blob *tcm_msg = NULL; + syna_pal_mutex_t *rw_mutex = NULL; + syna_pal_completion_t *cmd_completion = NULL; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + tcm_msg = &tcm_dev->msg_data; + rw_mutex = &tcm_msg->rw_mutex; + cmd_completion = &tcm_msg->cmd_completion; + + if (status_report_code) + *status_report_code = STATUS_INVALID; + + syna_pal_mutex_lock(rw_mutex); + + /* request a command */ + retval = syna_tcm_v2_execute_cmd_request(tcm_dev, + CMD_TCM2_GET_REPORT, + NULL, + 0); + if (retval < 0) { + LOGE("Fail to send command CMD_TCM2_GET_REPORT\n"); + + if (ATOMIC_GET(tcm_msg->command_status) == CMD_STATE_BUSY) { + ATOMIC_SET(tcm_msg->command_status, CMD_STATE_ERROR); + syna_pal_completion_complete(cmd_completion); + } + goto exit; + } + + /* duplicate the data to external buffer */ + syna_tcm_buf_lock(&tcm_dev->external_buf); + if (tcm_msg->payload_length > 0) { + retval = syna_tcm_buf_alloc(&tcm_dev->external_buf, + tcm_msg->payload_length); + if (retval < 0) { + LOGE("Fail to allocate memory, external_buf invalid\n"); + syna_tcm_buf_unlock(&tcm_dev->external_buf); + goto exit; + } else { + retval = syna_pal_mem_cpy(&tcm_dev->external_buf.buf[0], + tcm_msg->payload_length, + &tcm_msg->in.buf[MESSAGE_HEADER_SIZE], + tcm_msg->in.buf_size - MESSAGE_HEADER_SIZE, + tcm_msg->payload_length); + if (retval < 0) { + LOGE("Fail to copy data to external buffer\n"); + syna_tcm_buf_unlock(&tcm_dev->external_buf); + goto exit; + } + } + } + tcm_dev->external_buf.data_length = tcm_msg->payload_length; + syna_tcm_buf_unlock(&tcm_dev->external_buf); + + if (tcm_msg->response_code == STATUS_NO_REPORT_AVAILABLE) + goto exit; + + /* process the retrieved packet */ + if (tcm_msg->status_report_code >= REPORT_IDENTIFY) + syna_tcm_v2_dispatch_report(tcm_dev); + else + syna_tcm_v2_dispatch_response(tcm_dev); + + /* copy the status report code to caller */ + if (status_report_code) + *status_report_code = tcm_msg->status_report_code; + +exit: + syna_pal_mutex_unlock(rw_mutex); + + return retval; +} + +/** + * syna_tcm_v2_write_message() + * + * Write message including command and its payload to TouchCom device. + * Then, the response of the command generated by the device will be + * read in and stored in internal buffer.resp. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] command: TouchComm command + * [ in] payload: data payload, if any + * [ in] payload_length: length of data payload, if any + * [out] resp_code: response code returned + * [ in] delay_ms_resp: delay time for response reading. + * a positive value presents the time for polling; + * or, set '0' or 'RESP_IN_ATTN' for ATTN driven + * + * @return + * 0 or positive value on success; otherwise, on error. + */ +static int syna_tcm_v2_write_message(struct tcm_dev *tcm_dev, + unsigned char command, unsigned char *payload, + unsigned int payload_length, unsigned char *resp_code, + unsigned int delay_ms_resp) +{ + int retval; + int timeout = 0; + int polling_ms = 0; + struct tcm_message_data_blob *tcm_msg = NULL; + syna_pal_mutex_t *cmd_mutex = NULL; + syna_pal_mutex_t *rw_mutex = NULL; + syna_pal_completion_t *cmd_completion = NULL; + bool has_irq_ctrl = false; + bool in_polling = false; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + tcm_msg = &tcm_dev->msg_data; + cmd_mutex = &tcm_msg->cmd_mutex; + rw_mutex = &tcm_msg->rw_mutex; + cmd_completion = &tcm_msg->cmd_completion; + + if (resp_code) + *resp_code = STATUS_INVALID; + + /* indicate which mode is used */ + in_polling = (delay_ms_resp != RESP_IN_ATTN); + + /* irq control is enabled only when the operations is implemented + * and the current status of irq is enabled. + * do not enable irq if it is disabled by someone. + */ + has_irq_ctrl = (bool)(tcm_dev->hw_if->ops_enable_irq != NULL); + has_irq_ctrl &= tcm_dev->hw_if->bdata_attn.irq_enabled; + + /* disable irq when using polling mode */ + if (has_irq_ctrl && in_polling && tcm_dev->hw_if->ops_enable_irq) + tcm_dev->hw_if->ops_enable_irq(tcm_dev->hw_if, false); + + LOGD("write command: 0x%02x, payload size: %d\n", + command, payload_length); + + syna_pal_mutex_lock(cmd_mutex); + + syna_pal_mutex_lock(rw_mutex); + + ATOMIC_SET(tcm_msg->command_status, CMD_STATE_BUSY); + + /* reset the command completion */ + syna_pal_completion_reset(cmd_completion); + + tcm_msg->command = command; + + /* request a command execution */ + retval = syna_tcm_v2_execute_cmd_request(tcm_dev, + command, + payload, + payload_length); + if (retval < 0) { + LOGE("Fail to send command 0x%02x to device\n", command); + goto exit; + } + + syna_pal_mutex_unlock(rw_mutex); + + /* waiting for the resp data only at STATUS_ACK */ + if (tcm_msg->response_code != STATUS_ACK) { + syna_tcm_v2_dispatch_response(tcm_dev); + + goto check_response; + } + + /* handle the report generated by the command + * + * assuming to select the polling mode, the while-loop below will + * repeatedly read in the respose data based on the given polling + * time; otherwise, wait until receiving a completion event from + * interupt thread. + */ + timeout = 0; + if (!in_polling) + polling_ms = CMD_RESPONSE_TIMEOUT_MS; + else + polling_ms = delay_ms_resp; + + do { + /* wait for the completion event triggered by read_message */ + retval = syna_pal_completion_wait_for(cmd_completion, + polling_ms); + /* reset the status when times out and keep in polling */ + if (retval < 0) + ATOMIC_SET(tcm_msg->command_status, CMD_STATE_BUSY); + + /* break when geting a valid resp; otherwise, keep in polling */ + if (ATOMIC_GET(tcm_msg->command_status) == CMD_STATE_IDLE) + goto check_response; + + if (in_polling) { + /* retrieve the message packet back */ + retval = syna_tcm_v2_read_message(tcm_dev, NULL); + /* keep in polling if still not having a valid resp */ + if (retval < 0) + syna_pal_completion_reset(cmd_completion); + } + + timeout += polling_ms + 10; + + } while (timeout < CMD_RESPONSE_TIMEOUT_MS); + + /* check the status of response data + * according to the touchcomm spec, each command message + * should have an associated reponse message. + */ +check_response: + if (ATOMIC_GET(tcm_msg->command_status) != CMD_STATE_IDLE) { + if (timeout >= CMD_RESPONSE_TIMEOUT_MS) { + LOGE("Timed out wait for response of command 0x%02x\n", + command); + retval = _ETIMEDOUT; + goto exit; + } else { + LOGE("Fail to get valid response of command 0x%02x\n", + command); + retval = _EIO; + goto exit; + } + } + + /* copy response code to the caller */ + if (resp_code) + *resp_code = tcm_msg->response_code; + + if (tcm_msg->response_code != STATUS_OK) { + LOGE("Error code 0x%02x of command 0x%02x\n", + tcm_msg->response_code, tcm_msg->command); + retval = _EIO; + } else { + retval = 0; + } + +exit: + tcm_msg->command = CMD_NONE; + + ATOMIC_SET(tcm_msg->command_status, CMD_STATE_IDLE); + + syna_pal_mutex_unlock(cmd_mutex); + + /* recovery the irq if using polling mode */ + if (has_irq_ctrl && in_polling && tcm_dev->hw_if->ops_enable_irq) + tcm_dev->hw_if->ops_enable_irq(tcm_dev->hw_if, true); + + return retval; +} + + /** + * syna_tcm_v2_detect() + * + * For TouchCom v2 protocol, the given data must have a valid crc-6 at the end. + * If so, send an identify command to identify the device. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] data: raw 4-byte data + * [ in] size: length of input data in bytes + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_v2_detect(struct tcm_dev *tcm_dev, unsigned char *data, + unsigned int size) +{ + int retval; + unsigned char resp_code = 0; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if ((!data) || (size < MESSAGE_HEADER_SIZE)) { + LOGE("Invalid parameters\n"); + return _EINVAL; + } + + if (syna_tcm_v2_crc6(data, BITS_IN_MESSAGE_HEADER) != 0) + return _ENODEV; + + /* send an identify command to identify the device */ + retval = syna_tcm_v2_write_message(tcm_dev, + CMD_IDENTIFY, + NULL, + 0, + &resp_code, + RESP_IN_POLLING); + if (retval < 0) { + LOGE("Fail to get identification info from device\n"); + return retval; + } + + /* expose the read / write operations */ + tcm_dev->read_message = syna_tcm_v2_read_message; + tcm_dev->write_message = syna_tcm_v2_write_message; + + return retval; +} diff --git a/tcm/synaptics_touchcom_func_base.c b/tcm/synaptics_touchcom_func_base.c new file mode 100644 index 0000000..8e83c15 --- /dev/null +++ b/tcm/synaptics_touchcom_func_base.c @@ -0,0 +1,1678 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Synaptics TouchCom touchscreen driver + * + * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +/** + * @file synaptics_touchcom_func_base.c + * + * This file implements generic and foundational functions supported in + * Synaptics TouchComm communication protocol. + * + * The declarations are in synaptics_touchcom_func_base.h. + */ + +#include "synaptics_touchcom_func_base.h" +#include "synaptics_touchcom_func_touch.h" + + +/** + * syna_tcm_change_resp_read() + * + * Helper to change the default resp reading method, which was previously set + * when calling syna_tcm_allocate_device + * + * @param + * [in] tcm_dev: the device handle + * [in] request: resp reading method to change + * set '0' or 'RESP_IN_ATTN' for ATTN-driven; otherwise, + * assign a positive value standing for the polling time + * @return + * none. + */ +void syna_tcm_change_resp_read(struct tcm_dev *tcm_dev, unsigned int request) +{ + if (request == RESP_IN_ATTN) { + tcm_dev->msg_data.default_resp_reading = RESP_IN_ATTN; + + LOGI("Change default resp reading method by attn\n"); + } else { + if (request < RESP_IN_POLLING) + request = RESP_IN_POLLING; + + tcm_dev->msg_data.default_resp_reading = request; + + LOGI("Change default resp reading method by polling (%dms)\n", + tcm_dev->msg_data.default_resp_reading); + } +} + +/** + * syna_tcm_init_message_wrap() + * + * Initialize internal buffers and related structures for command processing. + * The function must be called to prepare all essential structures for + * command wrapper. + * + * @param + * [in] tcm_msg: message wrapper structure + * [in] resp_reading: default method to retrieve the resp data + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_init_message_wrap(struct tcm_message_data_blob *tcm_msg, + unsigned int resp_reading) +{ + /* initialize internal buffers */ + syna_tcm_buf_init(&tcm_msg->in); + syna_tcm_buf_init(&tcm_msg->out); + syna_tcm_buf_init(&tcm_msg->temp); + + /* allocate the completion event for command processing */ + if (syna_pal_completion_alloc(&tcm_msg->cmd_completion) < 0) { + LOGE("Fail to allocate cmd completion event\n"); + return _EINVAL; + } + + /* allocate the cmd_mutex for command protection */ + if (syna_pal_mutex_alloc(&tcm_msg->cmd_mutex) < 0) { + LOGE("Fail to allocate cmd_mutex\n"); + return _EINVAL; + } + + /* allocate the rw_mutex for rw protection */ + if (syna_pal_mutex_alloc(&tcm_msg->rw_mutex) < 0) { + LOGE("Fail to allocate rw_mutex\n"); + return _EINVAL; + } + + /* set default state of command_status */ + ATOMIC_SET(tcm_msg->command_status, CMD_STATE_IDLE); + + /* allocate the internal buffer.in at first */ + syna_tcm_buf_lock(&tcm_msg->in); + + if (syna_tcm_buf_alloc(&tcm_msg->in, MESSAGE_HEADER_SIZE) < 0) { + LOGE("Fail to allocate memory for buf.in (size = %d)\n", + MESSAGE_HEADER_SIZE); + tcm_msg->in.buf_size = 0; + tcm_msg->in.data_length = 0; + syna_tcm_buf_unlock(&tcm_msg->in); + return _EINVAL; + } + tcm_msg->in.buf_size = MESSAGE_HEADER_SIZE; + + syna_tcm_buf_unlock(&tcm_msg->in); + + tcm_msg->default_resp_reading = resp_reading; + + LOGI("Resp reading method (default): %s\n", + (resp_reading == RESP_IN_ATTN) ? "attn" : "polling"); + + return 0; +} + +/** + * syna_tcm_del_message_wrap() + * + * Remove message wrapper interface and internal buffers. + * Call the function once the message wrapper is no longer needed. + * + * @param + * [in] tcm_msg: message wrapper structure + * + * @return + * none. + */ +static void syna_tcm_del_message_wrap(struct tcm_message_data_blob *tcm_msg) +{ + /* release the mutex */ + syna_pal_mutex_free(&tcm_msg->rw_mutex); + syna_pal_mutex_free(&tcm_msg->cmd_mutex); + + /* release the completion event */ + syna_pal_completion_free(&tcm_msg->cmd_completion); + + /* release internal buffers */ + syna_tcm_buf_release(&tcm_msg->temp); + syna_tcm_buf_release(&tcm_msg->out); + syna_tcm_buf_release(&tcm_msg->in); +} + +/** + * syna_tcm_allocate_device() + * + * Create the TouchCom core device handle. + * This function must be called in order to allocate the main device handle, + * structure syna_tcm_dev, which will be passed to all other operations and + * functions within the entire source code. + * + * Meanwhile, caller has to prepare specific syna_tcm_hw_interface structure, + * so that all the implemented functions can access hardware components + * through syna_tcm_hw_interface. + * + * @param + * [out] ptcm_dev_ptr: a pointer to the device handle returned + * [ in] hw_if: hardware-specific data on target platform + * [ in] resp_reading: default resp reading method + * set 'RESP_IN_ATTN' to apply ATTN-driven method; + * set 'RESP_IN_POLLING' to read in resp by polling + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_allocate_device(struct tcm_dev **ptcm_dev_ptr, + struct syna_hw_interface *hw_if, unsigned int resp_reading) +{ + int retval = 0; + struct tcm_dev *tcm_dev = NULL; + + if (!hw_if) { + LOGE("Invalid parameter of hw_if\n"); + return _EINVAL; + } + + if ((!hw_if->ops_read_data) || (!hw_if->ops_write_data)) { + LOGE("Invalid hw read write operation\n"); + return _EINVAL; + } + + *ptcm_dev_ptr = NULL; + + /* allocate the core device handle */ + tcm_dev = (struct tcm_dev *)syna_pal_mem_alloc( + 1, + sizeof(struct tcm_dev)); + if (!tcm_dev) { + LOGE("Fail to create tcm device handle\n"); + return _ENOMEM; + } + + /* link to the given hardware data */ + tcm_dev->hw_if = hw_if; + + tcm_dev->max_rd_size = hw_if->bdata_io.rd_chunk_size; + tcm_dev->max_wr_size = hw_if->bdata_io.wr_chunk_size; + + tcm_dev->write_message = NULL; + tcm_dev->read_message = NULL; + + /* allocate internal buffers */ + syna_tcm_buf_init(&tcm_dev->report_buf); + syna_tcm_buf_init(&tcm_dev->resp_buf); + syna_tcm_buf_init(&tcm_dev->external_buf); + syna_tcm_buf_init(&tcm_dev->touch_config); + + /* initialize the command wrapper interface */ + retval = syna_tcm_init_message_wrap(&tcm_dev->msg_data, + resp_reading); + if (retval < 0) { + LOGE("Fail to initialize command interface\n"); + goto err_init_message_wrap; + } + + /* return the created device handle */ + *ptcm_dev_ptr = tcm_dev; + + LOGI("TouchComm core module created, ver.: %d.%02d\n", + (unsigned char)(SYNA_TCM_CORE_LIB_VERSION >> 8), + (unsigned char)SYNA_TCM_CORE_LIB_VERSION & 0xff); + + LOGI("Capability: wr_chunk(%d), rd_chunk(%d), irq_control(%s)\n", + tcm_dev->max_wr_size, tcm_dev->max_rd_size, + (hw_if->ops_enable_irq) ? "yes" : "no"); + + return 0; + +err_init_message_wrap: + syna_tcm_buf_release(&tcm_dev->touch_config); + syna_tcm_buf_release(&tcm_dev->external_buf); + syna_tcm_buf_release(&tcm_dev->report_buf); + syna_tcm_buf_release(&tcm_dev->resp_buf); + + tcm_dev->hw_if = NULL; + + syna_pal_mem_free((void *)tcm_dev); + + return retval; +} + +/** + * syna_tcm_remove_device() + * + * Remove the TouchCom core device handler. + * This function must be invoked when the device is no longer needed. + * + * @param + * [ in] tcm_dev: the device handle + * + * @return + * none. + */ +void syna_tcm_remove_device(struct tcm_dev *tcm_dev) +{ + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return; + } + + /* release the command interface */ + syna_tcm_del_message_wrap(&tcm_dev->msg_data); + + /* release buffers */ + syna_tcm_buf_release(&tcm_dev->touch_config); + syna_tcm_buf_release(&tcm_dev->external_buf); + syna_tcm_buf_release(&tcm_dev->report_buf); + syna_tcm_buf_release(&tcm_dev->resp_buf); + + tcm_dev->hw_if = NULL; + + /* release the device handle */ + syna_pal_mem_free((void *)tcm_dev); + + LOGI("tcm device handle removed\n"); +} + +/** + * syna_tcm_detect_protocol() + * + * Helper to distinguish which TouchCom firmware is running. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] data: raw data from device + * [ in] data_len: length of input data in bytes + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_detect_protocol(struct tcm_dev *tcm_dev, + unsigned char *data, unsigned int data_len) +{ + int retval; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + retval = syna_tcm_v2_detect(tcm_dev, data, data_len); + if (retval < 0) + retval = syna_tcm_v1_detect(tcm_dev, data, data_len); + + return retval; +} + +/** + * syna_tcm_detect_device() + * + * Determine the type of device being connected, and distinguish which + * version of TouchCom firmware running on the device. + * This function must be called before using this TouchComm core library. + * + * @param + * [ in] tcm_dev: the device handle + * + * @return + * on success, the current mode running on the device is returned; + * otherwise, negative value on error. + */ +int syna_tcm_detect_device(struct tcm_dev *tcm_dev) +{ + int retval = 0; + unsigned char data[4] = { 0 }; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + tcm_dev->dev_mode = MODE_UNKNOWN; + + /* get the bare data from the bus directly */ + data[0] = 0x07; + retval = syna_tcm_write(tcm_dev, &data[0], 1); + if (retval < 0) { + LOGE("Fail to write magic to bus\n"); + return _EIO; + } + + retval = syna_tcm_read(tcm_dev, + data, (unsigned int)sizeof(data)); + if (retval < 0) { + LOGE("Fail to retrieve 4-byte data from bus\n"); + return _EIO; + } + + LOGD("bare data: %02x %02x %02x %02x\n", + data[0], data[1], data[2], data[3]); + + /* distinguish which tcm version running on the device */ + retval = syna_tcm_detect_protocol(tcm_dev, + data, (unsigned int)sizeof(data)); + if (retval < 0) { + LOGE("Fail to detect TouchCom device, %02x %02x %02x %02x\n", + data[0], data[1], data[2], data[3]); + return retval; + } + + if ((!tcm_dev->write_message) || (!tcm_dev->read_message)) { + LOGE("Invalid TouchCom rw operations\n"); + return _ENODEV; + } + + /* check the running mode */ + switch (tcm_dev->dev_mode) { + case MODE_APPLICATION_FIRMWARE: + LOGI("Device in Application FW, build id: %d, %s\n", + tcm_dev->packrat_number, + tcm_dev->id_info.part_number); + break; + case MODE_BOOTLOADER: + case MODE_TDDI_BOOTLOADER: + LOGI("Device in Bootloader\n"); + break; + case MODE_ROMBOOTLOADER: + LOGI("Device in ROMBoot uBL\n"); + break; + case MODE_MULTICHIP_TDDI_BOOTLOADER: + LOGI("Device in multi-chip TDDI Bootloader\n"); + break; + default: + LOGW("Found TouchCom device, but unsupported mode: 0x%02x\n", + tcm_dev->dev_mode); + break; + } + + retval = tcm_dev->dev_mode; + return retval; +} + +/** + * syna_tcm_get_event_data() + * + * Helper to read TouchComm messages when ATTN signal is asserted. + * After returning, the ATTN signal should be no longer asserted. + * + * The 'code' returned will guide the caller on the next action. + * For example, do touch reporting once returned code is equal to REPORT_TOUCH. + * + * @param + * [ in] tcm_dev: the device handle + * [out] code: received report code + * [out] data: a user buffer for data returned + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_get_event_data(struct tcm_dev *tcm_dev, + unsigned char *code, struct tcm_buffer *data) +{ + int retval = 0; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if (!code) { + LOGE("Invalid parameter\n"); + return _EINVAL; + } + + /* retrieve the event data */ + retval = tcm_dev->read_message(tcm_dev, + code); + if (retval < 0) { + LOGE("Fail to read messages\n"); + return retval; + } + + /* exit if no buffer provided */ + if (!data) + goto exit; + + /* if gathering a report, copy to the user buffer */ + if ((*code >= REPORT_IDENTIFY) && (*code != STATUS_INVALID)) { + if (tcm_dev->report_buf.data_length == 0) + goto exit; + + syna_tcm_buf_lock(&tcm_dev->report_buf); + + retval = syna_tcm_buf_copy(data, &tcm_dev->report_buf); + if (retval < 0) { + LOGE("Fail to copy data, report type: %x\n", *code); + syna_tcm_buf_unlock(&tcm_dev->report_buf); + goto exit; + } + + syna_tcm_buf_unlock(&tcm_dev->report_buf); + } + + /* if gathering a response, copy to the user buffer */ + if ((*code > STATUS_IDLE) && (*code <= STATUS_ERROR)) { + if (tcm_dev->resp_buf.data_length == 0) + goto exit; + + syna_tcm_buf_lock(&tcm_dev->resp_buf); + + retval = syna_tcm_buf_copy(data, &tcm_dev->resp_buf); + if (retval < 0) { + LOGE("Fail to copy data, status code: %x\n", *code); + syna_tcm_buf_unlock(&tcm_dev->resp_buf); + goto exit; + } + + syna_tcm_buf_unlock(&tcm_dev->resp_buf); + } + +exit: + return retval; +} + +/** + * syna_tcm_identify() + * + * Implement the standard command code, which is used to request + * an IDENTIFY report packet. + * + * @param + * [ in] tcm_dev: the device handle + * [out] id_info: the identification info packet returned + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_identify(struct tcm_dev *tcm_dev, + struct tcm_identification_info *id_info) +{ + int retval = 0; + unsigned char resp_code; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + retval = tcm_dev->write_message(tcm_dev, + CMD_IDENTIFY, + NULL, + 0, + &resp_code, + tcm_dev->msg_data.default_resp_reading); + if (retval < 0) { + LOGE("Fail to send command 0x%02x\n", CMD_IDENTIFY); + goto exit; + } + + tcm_dev->dev_mode = tcm_dev->id_info.mode; + + if (id_info == NULL) + goto show_info; + + /* copy identify info to caller */ + retval = syna_pal_mem_cpy((unsigned char *)id_info, + sizeof(struct tcm_identification_info), + tcm_dev->resp_buf.buf, + tcm_dev->resp_buf.buf_size, + MIN(sizeof(*id_info), tcm_dev->resp_buf.data_length)); + if (retval < 0) { + LOGE("Fail to copy identify info to caller\n"); + goto exit; + } + +show_info: + LOGI("TCM Fw mode: 0x%02x, TCM ver.: %d\n", + tcm_dev->id_info.mode, tcm_dev->id_info.version); + +exit: + return retval; +} + +/** + * syna_tcm_reset() + * + * Implement the standard command code, which is used to perform a sw reset + * immediately. After a successful reset, an IDENTIFY report to indicate that + * device is ready. + * + * Caller shall be aware that the firmware will be reloaded after reset. + * Therefore, if expecting that a different firmware version is loaded, please + * do app firmware setup after reset. + * + * @param + * [ in] tcm_dev: the device handle + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_reset(struct tcm_dev *tcm_dev) +{ + int retval = 0; + unsigned char resp_code; + unsigned int board_setting; + unsigned int resp_handling; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + resp_handling = tcm_dev->msg_data.default_resp_reading; + + /* gather the board settings of reset delay time */ + board_setting = tcm_dev->hw_if->bdata_rst.reset_delay_ms; + if (board_setting == 0) + board_setting = RESET_DELAY_MS; + + /* select the proper period to handle the resp of reset */ + if (resp_handling != RESP_IN_ATTN) { + if (board_setting > resp_handling) { + resp_handling = board_setting; + LOGI("Use board settings %dms to poll resp of reset\n", + resp_handling); + } + } + + retval = tcm_dev->write_message(tcm_dev, + CMD_RESET, + NULL, + 0, + &resp_code, + resp_handling); + if (retval < 0) { + LOGE("Fail to send command 0x%02x\n", CMD_RESET); + goto exit; + } + + /* current device mode is expected to be updated + * because identification report will be received after reset + */ + tcm_dev->dev_mode = tcm_dev->id_info.mode; + if (IS_NOT_APP_FW_MODE(tcm_dev->dev_mode)) { + LOGI("Device mode 0x%02X running after reset\n", + tcm_dev->dev_mode); + } + + retval = 0; +exit: + return retval; +} + +/** + * syna_tcm_enable_report() + * + * Implement the application fw command code to enable or disable a report. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] report_code: the requested report code being generated + * [ in] en: '1' for enabling; '0' for disabling + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_enable_report(struct tcm_dev *tcm_dev, + unsigned char report_code, bool en) +{ + int retval = 0; + unsigned char resp_code; + unsigned char command; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if (IS_NOT_APP_FW_MODE(tcm_dev->dev_mode)) { + LOGE("Device is not in application fw mode, mode: %x\n", + tcm_dev->dev_mode); + return _EINVAL; + } + + command = (en) ? CMD_ENABLE_REPORT : CMD_DISABLE_REPORT; + + retval = tcm_dev->write_message(tcm_dev, + command, + &report_code, + 1, + &resp_code, + tcm_dev->msg_data.default_resp_reading); + if (retval < 0) { + LOGE("Fail to send command 0x%02x to %s 0x%02x report\n", + command, (en)?"enable":"disable", report_code); + goto exit; + } + + if (resp_code != STATUS_OK) { + LOGE("Fail to %s 0x%02x report, resp_code:%x\n", + (en) ? "enable" : "disable", report_code, resp_code); + } else { + LOGD("Report 0x%x %s\n", report_code, + (en) ? "enabled" : "disabled"); + } + +exit: + return retval; +} + +/** + * syna_tcm_run_rom_bootloader_fw() + * + * Requests that the rombootloader firmware be run. + * Once the rombootloader firmware has finished starting, an IDENTIFY report + * to indicate that it is in the new mode. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] fw_switch_delay: delay time for fw mode switching. + * a positive value presents the time for polling; + * or, set '0' or 'RESP_IN_ATTN' for ATTN driven + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_run_rom_bootloader_fw(struct tcm_dev *tcm_dev, + unsigned int fw_switch_delay) +{ + int retval = 0; + unsigned char resp_code; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + retval = tcm_dev->write_message(tcm_dev, + CMD_REBOOT_TO_ROM_BOOTLOADER, + NULL, + 0, + &resp_code, + fw_switch_delay); + if (retval < 0) { + LOGE("Fail to send command 0x%02x\n", + CMD_REBOOT_TO_ROM_BOOTLOADER); + goto exit; + } + + if (!IS_ROM_BOOTLOADER_MODE(tcm_dev->dev_mode)) { + LOGE("Fail to enter rom bootloader, mode: %x\n", + tcm_dev->dev_mode); + retval = _ENODEV; + goto exit; + } + + LOGI("ROM Bootloader (mode 0x%x) activated\n", + tcm_dev->dev_mode); + + retval = 0; + +exit: + return retval; +} + +/** + * syna_tcm_run_bootloader_fw() + * + * Requests that the bootloader firmware be run. + * Once the bootloader firmware has finished starting, an IDENTIFY report + * to indicate that it is in the new mode. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] fw_switch_delay: delay time for fw mode switching. + * a positive value presents the time for polling; + * or, set '0' or 'RESP_IN_ATTN' for ATTN driven + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_run_bootloader_fw(struct tcm_dev *tcm_dev, + unsigned int fw_switch_delay) +{ + int retval = 0; + unsigned char resp_code; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + retval = tcm_dev->write_message(tcm_dev, + CMD_RUN_BOOTLOADER_FIRMWARE, + NULL, + 0, + &resp_code, + fw_switch_delay); + if (retval < 0) { + LOGE("Fail to send command 0x%02x\n", + CMD_RUN_BOOTLOADER_FIRMWARE); + goto exit; + } + + if (!IS_BOOTLOADER_MODE(tcm_dev->dev_mode)) { + LOGE("Fail to enter bootloader, mode: %x\n", + tcm_dev->dev_mode); + retval = _ENODEV; + goto exit; + } + + LOGI("Bootloader Firmware (mode 0x%x) activated\n", + tcm_dev->dev_mode); + + retval = 0; + +exit: + return retval; +} + +/** + * syna_tcm_run_application_fw() + * + * Requests that the application firmware be run. + * Once the application firmware has finished starting, an IDENTIFY report + * to indicate that it is in the new mode. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] fw_switch_delay: delay time for fw mode switching. + * a positive value presents the time for polling; + * or, set '0' or 'RESP_IN_ATTN' for ATTN driven + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_run_application_fw(struct tcm_dev *tcm_dev, + unsigned int fw_switch_delay) +{ + int retval = 0; + unsigned char resp_code; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + retval = tcm_dev->write_message(tcm_dev, + CMD_RUN_APPLICATION_FIRMWARE, + NULL, + 0, + &resp_code, + fw_switch_delay); + if (retval < 0) { + LOGE("Fail to send command 0x%02x\n", + CMD_RUN_APPLICATION_FIRMWARE); + goto exit; + } + + if (IS_NOT_APP_FW_MODE(tcm_dev->dev_mode)) { + LOGW("Fail to enter application fw, mode: %x\n", + tcm_dev->dev_mode); + retval = _ENODEV; + goto exit; + } + + LOGI("Application Firmware (mode 0x%x) activated\n", + tcm_dev->dev_mode); + + retval = 0; + +exit: + return retval; +} + +/** + * syna_tcm_switch_fw_mode() + * + * Implement the command code to switch the firmware mode. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] mode: target firmware mode + * [ in] fw_switch_delay: delay time for fw mode switching. + * a positive value presents the time for polling; + * or, set '0' or 'RESP_IN_ATTN' for ATTN driven + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_switch_fw_mode(struct tcm_dev *tcm_dev, + unsigned char mode, unsigned int fw_switch_delay) +{ + int retval = 0; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + switch (mode) { + case MODE_APPLICATION_FIRMWARE: + retval = syna_tcm_run_application_fw(tcm_dev, + fw_switch_delay); + if (retval < 0) { + LOGE("Fail to switch to application mode\n"); + goto exit; + } + break; + case MODE_BOOTLOADER: + case MODE_TDDI_BOOTLOADER: + case MODE_TDDI_HDL_BOOTLOADER: + case MODE_MULTICHIP_TDDI_BOOTLOADER: + retval = syna_tcm_run_bootloader_fw(tcm_dev, + fw_switch_delay); + if (retval < 0) { + LOGE("Fail to switch to bootloader mode\n"); + goto exit; + } + break; + case MODE_ROMBOOTLOADER: + retval = syna_tcm_run_rom_bootloader_fw(tcm_dev, + fw_switch_delay); + if (retval < 0) { + LOGE("Fail to switch to rom bootloader mode\n"); + goto exit; + } + break; + default: + LOGE("Invalid firmware mode requested\n"); + retval = _EINVAL; + goto exit; + } + + retval = 0; + +exit: + return retval; +} + +/** + * syna_tcm_get_boot_info() + * + * Implement the bootloader command code, which is used to request a + * boot information packet. + * + * @param + * [ in] tcm_dev: the device handle + * [out] boot_info: the boot info packet returned + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_get_boot_info(struct tcm_dev *tcm_dev, + struct tcm_boot_info *boot_info) +{ + int retval = 0; + unsigned char resp_code; + unsigned int resp_data_len = 0; + unsigned int copy_size; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + retval = tcm_dev->write_message(tcm_dev, + CMD_GET_BOOT_INFO, + NULL, + 0, + &resp_code, + tcm_dev->msg_data.default_resp_reading); + if (retval < 0) { + LOGE("Fail to send command 0x%02x\n", + CMD_GET_BOOT_INFO); + goto exit; + } + + resp_data_len = tcm_dev->resp_buf.data_length; + copy_size = MIN(sizeof(struct tcm_boot_info), resp_data_len); + + /* save the boot_info */ + retval = syna_pal_mem_cpy((unsigned char *)&tcm_dev->boot_info, + sizeof(struct tcm_boot_info), + tcm_dev->resp_buf.buf, + tcm_dev->resp_buf.buf_size, + copy_size); + if (retval < 0) { + LOGE("Fail to copy boot info\n"); + goto exit; + } + + if (boot_info == NULL) + goto exit; + + /* copy boot_info to caller */ + retval = syna_pal_mem_cpy((unsigned char *)boot_info, + sizeof(struct tcm_boot_info), + tcm_dev->resp_buf.buf, + tcm_dev->resp_buf.buf_size, + copy_size); + if (retval < 0) { + LOGE("Fail to copy boot info to caller\n"); + goto exit; + } + +exit: + return retval; +} + +/** + * syna_tcm_get_app_info() + * + * Implement the application fw command code to request an application + * info packet from device. + * + * @param + * [ in] tcm_dev: the device handle + * [out] app_info: the application info packet returned + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_get_app_info(struct tcm_dev *tcm_dev, + struct tcm_application_info *app_info) +{ + int retval = 0; + unsigned char resp_code; + unsigned int app_status; + unsigned int resp_data_len = 0; + unsigned int copy_size; + struct tcm_application_info *info; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if (IS_NOT_APP_FW_MODE(tcm_dev->dev_mode)) { + LOGE("Device is not in application fw mode, mode: %x\n", + tcm_dev->dev_mode); + return _EINVAL; + } + + retval = tcm_dev->write_message(tcm_dev, + CMD_GET_APPLICATION_INFO, + NULL, + 0, + &resp_code, + tcm_dev->msg_data.default_resp_reading); + if (retval < 0) { + LOGE("Fail to send command 0x%02x\n", + CMD_GET_APPLICATION_INFO); + goto exit; + } + + resp_data_len = tcm_dev->resp_buf.data_length; + copy_size = MIN(sizeof(tcm_dev->app_info), resp_data_len); + + info = &tcm_dev->app_info; + + /* save the app_info */ + retval = syna_pal_mem_cpy((unsigned char *)info, + sizeof(struct tcm_application_info), + tcm_dev->resp_buf.buf, + tcm_dev->resp_buf.buf_size, + copy_size); + if (retval < 0) { + LOGE("Fail to copy application info\n"); + goto exit; + } + + if (app_info == NULL) + goto show_info; + + /* copy app_info to caller */ + retval = syna_pal_mem_cpy((unsigned char *)app_info, + sizeof(struct tcm_application_info), + tcm_dev->resp_buf.buf, + tcm_dev->resp_buf.buf_size, + copy_size); + if (retval < 0) { + LOGE("Fail to copy application info to caller\n"); + goto exit; + } + +show_info: + app_status = syna_pal_le2_to_uint(tcm_dev->app_info.status); + + if (app_status == APP_STATUS_BAD_APP_CONFIG) { + LOGE("Bad application firmware, status: 0x%x\n", app_status); + retval = _ENODEV; + goto exit; + } else if (app_status != APP_STATUS_OK) { + LOGE("Incorrect application status, 0x%x\n", app_status); + retval = _ENODEV; + goto exit; + } + + tcm_dev->max_objects = syna_pal_le2_to_uint(info->max_objects); + tcm_dev->max_x = syna_pal_le2_to_uint(info->max_x); + tcm_dev->max_y = syna_pal_le2_to_uint(info->max_y); + + tcm_dev->cols = syna_pal_le2_to_uint(info->num_of_image_cols); + tcm_dev->rows = syna_pal_le2_to_uint(info->num_of_image_rows); + syna_pal_mem_cpy((unsigned char *)tcm_dev->config_id, + MAX_SIZE_CONFIG_ID, + info->customer_config_id, + MAX_SIZE_CONFIG_ID, + MAX_SIZE_CONFIG_ID); + + LOGD("App info version: %d, status: %d\n", + syna_pal_le2_to_uint(info->version), app_status); + LOGD("App info: max_objs: %d, max_x:%d, max_y: %d, img: %dx%d\n", + tcm_dev->max_objects, tcm_dev->max_x, tcm_dev->max_y, + tcm_dev->rows, tcm_dev->cols); + +exit: + return retval; +} + +/** + * syna_tcm_get_static_config() + * + * Implement the application fw command code to retrieve the contents of + * the static configuration. + * + * The size of static configuration is available in app info packet. + * + * @param + * [ in] tcm_dev: the device handle + * [out] buf: buffer stored the static configuration + * [ in] buf_size: the size of given buffer + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_get_static_config(struct tcm_dev *tcm_dev, + unsigned char *buf, unsigned int buf_size) +{ + int retval = 0; + unsigned char resp_code; + unsigned int size; + struct tcm_application_info *app_info; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if (IS_NOT_APP_FW_MODE(tcm_dev->dev_mode)) { + LOGE("Device is not in application fw mode, mode: %x\n", + tcm_dev->dev_mode); + return _EINVAL; + } + + app_info = &tcm_dev->app_info; + + size = syna_pal_le2_to_uint(app_info->static_config_size); + + if (size > buf_size) { + LOGE("Invalid buffer input, given size: %d (actual: %d)\n", + buf_size, size); + return _EINVAL; + } + + retval = tcm_dev->write_message(tcm_dev, + CMD_GET_STATIC_CONFIG, + NULL, + 0, + &resp_code, + tcm_dev->msg_data.default_resp_reading); + if (retval < 0) { + LOGE("Fail to send command 0x%02x\n", + CMD_GET_STATIC_CONFIG); + goto exit; + } + + if (buf == NULL) + goto exit; + + /* copy app_info to caller */ + retval = syna_pal_mem_cpy((unsigned char *)buf, + buf_size, + tcm_dev->resp_buf.buf, + tcm_dev->resp_buf.buf_size, + tcm_dev->resp_buf.data_length); + if (retval < 0) { + LOGE("Fail to copy static config data to caller\n"); + goto exit; + } + +exit: + return retval; +} + +/** + * syna_tcm_set_static_config() + * + * Implement the application fw command code to set the contents of + * the static configuration. When the write is completed, the device will + * restart touch sensing with the new settings. + * + * The size of static configuration is available in app info packet. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] config_data: the data of static configuration + * [ in] config_data_size: the size of given data + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_set_static_config(struct tcm_dev *tcm_dev, + unsigned char *config_data, unsigned int config_data_size) +{ + int retval = 0; + unsigned char resp_code; + unsigned int size; + struct tcm_application_info *app_info; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if (IS_NOT_APP_FW_MODE(tcm_dev->dev_mode)) { + LOGE("Device is not in application fw mode, mode: %x\n", + tcm_dev->dev_mode); + return _EINVAL; + } + + app_info = &tcm_dev->app_info; + + size = syna_pal_le2_to_uint(app_info->static_config_size); + + if (size != config_data_size) { + LOGE("Invalid static config size, given: %d (actual: %d)\n", + config_data_size, size); + return _EINVAL; + } + + retval = tcm_dev->write_message(tcm_dev, + CMD_SET_STATIC_CONFIG, + config_data, + config_data_size, + &resp_code, + tcm_dev->msg_data.default_resp_reading); + if (retval < 0) { + LOGE("Fail to send command 0x%02x\n", + CMD_SET_STATIC_CONFIG); + goto exit; + } + + retval = 0; +exit: + return retval; +} + +/** + * syna_tcm_get_dynamic_config() + * + * Implement the application fw command code to get the value from the a single + * field of the dynamic configuration. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] id: target field id + * [out] value: the value returned + * [ in] delay_ms_resp: delay time for response reading. + * a positive value presents the time for polling; + * or, set '0' or 'RESP_IN_ATTN' for ATTN driven + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_get_dynamic_config(struct tcm_dev *tcm_dev, + unsigned char id, unsigned short *value, + unsigned int delay_ms_resp) +{ + int retval = 0; + unsigned char out; + unsigned char resp_code; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if (IS_NOT_APP_FW_MODE(tcm_dev->dev_mode)) { + LOGE("Device is not in application fw mode, mode: %x\n", + tcm_dev->dev_mode); + return _EINVAL; + } + + out = (unsigned char)id; + + retval = tcm_dev->write_message(tcm_dev, + CMD_GET_DYNAMIC_CONFIG, + &out, + sizeof(out), + &resp_code, + delay_ms_resp); + if (retval < 0) { + LOGE("Fail to send command 0x%02x to get dynamic field 0x%x\n", + CMD_GET_DYNAMIC_CONFIG, (unsigned char)id); + goto exit; + } + + /* return dynamic config data */ + if (tcm_dev->resp_buf.data_length < 2) { + LOGE("Invalid resp data size, %d\n", + tcm_dev->resp_buf.data_length); + goto exit; + } + + *value = (unsigned short)syna_pal_le2_to_uint(tcm_dev->resp_buf.buf); + + LOGD("Get %d from dynamic field 0x%x\n", *value, id); + + retval = 0; + +exit: + return retval; +} + +/** + * syna_tcm_set_dynamic_config() + * + * Implement the application fw command code to set the specified value to + * the selected field of the dynamic configuration. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] id: target field id + * [ in] value: the value to the selected field + * [ in] delay_ms_resp: delay time for response reading. + * a positive value presents the time for polling; + * or, set '0' or 'RESP_IN_ATTN' for ATTN driven + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_set_dynamic_config(struct tcm_dev *tcm_dev, + unsigned char id, unsigned short value, + unsigned int delay_ms_resp) +{ + int retval = 0; + unsigned char out[3]; + unsigned char resp_code; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if (IS_NOT_APP_FW_MODE(tcm_dev->dev_mode)) { + LOGE("Device is not in application fw mode, mode: %x\n", + tcm_dev->dev_mode); + return _EINVAL; + } + + LOGD("Set %d to dynamic field 0x%x\n", value, id); + + out[0] = (unsigned char)id; + out[1] = (unsigned char)value; + out[2] = (unsigned char)(value >> 8); + + retval = tcm_dev->write_message(tcm_dev, + CMD_SET_DYNAMIC_CONFIG, + out, + sizeof(out), + &resp_code, + delay_ms_resp); + if (retval < 0) { + LOGE("Fail to send command 0x%02x to set %d to field 0x%x\n", + CMD_SET_DYNAMIC_CONFIG, value, (unsigned char)id); + goto exit; + } + + retval = 0; + +exit: + return retval; +} + +/** + * syna_tcm_rezero() + * + * Implement the application fw command code to force the device to rezero its + * baseline estimate. + * + * @param + * [ in] tcm_dev: the device handle + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_rezero(struct tcm_dev *tcm_dev) +{ + int retval = 0; + unsigned char resp_code; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if (IS_NOT_APP_FW_MODE(tcm_dev->dev_mode)) { + LOGE("Device is not in application fw mode, mode: %x\n", + tcm_dev->dev_mode); + return _EINVAL; + } + + retval = tcm_dev->write_message(tcm_dev, + CMD_REZERO, + NULL, + 0, + &resp_code, + tcm_dev->msg_data.default_resp_reading); + if (retval < 0) { + LOGE("Fail to send command 0x%02x\n", + CMD_REZERO); + goto exit; + } + + retval = 0; +exit: + return retval; +} + +/** + * syna_tcm_set_config_id() + * + * Implement the application fw command code to set the 16-byte config id, + * which can be read in the app info packet. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] config_id: config id to be set + * [ in] size: size of input data + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_set_config_id(struct tcm_dev *tcm_dev, + unsigned char *config_id, unsigned int size) +{ + int retval = 0; + unsigned char resp_code; + unsigned int config_id_len = 0; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if (IS_NOT_APP_FW_MODE(tcm_dev->dev_mode)) { + LOGE("Device is not in application fw mode, mode: %x\n", + tcm_dev->dev_mode); + return _EINVAL; + } + + config_id_len = sizeof(tcm_dev->app_info.customer_config_id); + + if (size != config_id_len) { + LOGE("Invalid config id input, given size: %d (%d)\n", + size, config_id_len); + return _EINVAL; + } + + retval = tcm_dev->write_message(tcm_dev, + CMD_SET_CONFIG_ID, + config_id, + size, + &resp_code, + tcm_dev->msg_data.default_resp_reading); + if (retval < 0) { + LOGE("Fail to send command 0x%02x\n", + CMD_SET_CONFIG_ID); + goto exit; + } + + retval = 0; +exit: + return retval; +} + +/** + * syna_tcm_sleep() + * + * Implement the application fw command code to put the device into low power + * deep sleep mode or set to normal active mode. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] en: '1' to low power deep sleep mode; '0' to active mode + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_sleep(struct tcm_dev *tcm_dev, bool en) +{ + int retval = 0; + unsigned char resp_code; + unsigned char command; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + command = (en) ? CMD_ENTER_DEEP_SLEEP : CMD_EXIT_DEEP_SLEEP; + + retval = tcm_dev->write_message(tcm_dev, + command, + NULL, + 0, + &resp_code, + tcm_dev->msg_data.default_resp_reading); + if (retval < 0) { + LOGE("Fail to send command 0x%x\n", command); + goto exit; + } + + retval = 0; +exit: + return retval; +} + +/** + * syna_tcm_get_features() + * + * Implement the application fw command code to query the supported features. + * + * @param + * [ in] tcm_dev: the device handle + * [out] info: the features description packet returned + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_get_features(struct tcm_dev *tcm_dev, + struct tcm_features_info *info) +{ + int retval = 0; + unsigned char resp_code; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if (IS_NOT_APP_FW_MODE(tcm_dev->dev_mode)) { + LOGE("Device is not in application fw mode, mode: %x\n", + tcm_dev->dev_mode); + return _EINVAL; + } + + retval = tcm_dev->write_message(tcm_dev, + CMD_GET_FEATURES, + NULL, + 0, + &resp_code, + tcm_dev->msg_data.default_resp_reading); + if (retval < 0) { + LOGE("Fail to send command 0x%02x\n", + CMD_GET_FEATURES); + goto exit; + } + + if (info == NULL) + goto exit; + + /* copy features_info to caller */ + retval = syna_pal_mem_cpy((unsigned char *)info, + sizeof(struct tcm_features_info), + tcm_dev->resp_buf.buf, + tcm_dev->resp_buf.buf_size, + MIN(sizeof(*info), tcm_dev->resp_buf.data_length)); + if (retval < 0) { + LOGE("Fail to copy features_info to caller\n"); + goto exit; + } + +exit: + return retval; +} + +/** + * syna_tcm_run_production_test() + * + * Implement the appplication fw command code to request the device to run + * the production test. + * + * Production tests are listed at enum test_code (PID$). + * + * @param + * [ in] tcm_dev: the device handle + * [ in] test_item: the requested testing item + * [out] tdata: testing data returned + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_run_production_test(struct tcm_dev *tcm_dev, + unsigned char test_item, struct tcm_buffer *tdata) +{ + int retval = 0; + unsigned char resp_code; + unsigned char test_code; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if (IS_NOT_APP_FW_MODE(tcm_dev->dev_mode)) { + LOGE("Device is not in application fw mode, mode: %x\n", + tcm_dev->dev_mode); + return _EINVAL; + } + + test_code = (unsigned char)test_item; + + retval = tcm_dev->write_message(tcm_dev, + CMD_PRODUCTION_TEST, + &test_code, + 1, + &resp_code, + tcm_dev->msg_data.default_resp_reading); + if (retval < 0) { + LOGE("Fail to send command 0x%02x\n", + CMD_PRODUCTION_TEST); + goto exit; + } + + if (tdata == NULL) + goto exit; + + /* copy testing data to caller */ + retval = syna_tcm_buf_copy(tdata, &tcm_dev->resp_buf); + if (retval < 0) { + LOGE("Fail to copy testing data\n"); + goto exit; + } +exit: + return retval; +} +/** + * syna_tcm_send_command() + * + * Helper to forward the custom commnd to the device + * + * @param + * [ in] tcm_dev: the device handle + * [ in] command: TouchComm command + * [ in] payload: data payload, if any + * [ in] payload_length: length of data payload, if any + * [out] resp_code: response code returned + * [out] resp: buffer to store the response data + * [ in] delay_ms_resp: delay time for response reading. + * a positive value presents the time for polling; + * or, set '0' or 'RESP_IN_ATTN' for ATTN driven + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_send_command(struct tcm_dev *tcm_dev, + unsigned char command, unsigned char *payload, + unsigned int payload_length, unsigned char *resp_code, + struct tcm_buffer *resp, unsigned int delay_ms_resp) +{ + int retval = 0; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if (!resp_code) { + LOGE("Invalid parameter\n"); + return _EINVAL; + } + + retval = tcm_dev->write_message(tcm_dev, + command, + payload, + payload_length, + resp_code, + delay_ms_resp); + if (retval < 0) { + LOGE("Fail to send command 0x%02x\n", + command); + goto exit; + } + + /* response data returned */ + if ((resp != NULL) && (tcm_dev->resp_buf.data_length > 0)) { + retval = syna_tcm_buf_copy(resp, &tcm_dev->resp_buf); + if (retval < 0) { + LOGE("Fail to copy resp data\n"); + goto exit; + } + } + +exit: + return retval; +} + diff --git a/tcm/synaptics_touchcom_func_base.h b/tcm/synaptics_touchcom_func_base.h new file mode 100644 index 0000000..d0a155b --- /dev/null +++ b/tcm/synaptics_touchcom_func_base.h @@ -0,0 +1,418 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Synaptics TouchCom touchscreen driver + * + * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +/** + * @file synaptics_touchcom_func_base.h + * + * This file declares generic and foundational APIs being used to communicate + * with Synaptics touch controller through TouchComm communication protocol. + */ + +#ifndef _SYNAPTICS_TOUCHCOM_BASE_FUNCS_H_ +#define _SYNAPTICS_TOUCHCOM_BASE_FUNCS_H_ + +#include "synaptics_touchcom_core_dev.h" + +/** + * syna_tcm_allocate_device() + * + * Create the TouchCom core device handle. + * This function must be called in order to allocate the main device handle, + * structure syna_tcm_dev, which will be passed to all other operations and + * functions within the entire source code. + * + * Meanwhile, caller has to prepare specific syna_tcm_hw_interface structure, + * so that all the implemented functions can access hardware components + * through syna_tcm_hw_interface. + * + * @param + * [out] ptcm_dev_ptr: a pointer to the device handle returned + * [ in] hw_if: hardware-specific data on target platform + * [ in] resp_reading: default resp reading method + * set 'RESP_IN_ATTN' to apply ATTN-driven method; + * set 'RESP_IN_POLLING' to read in resp by polling + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_allocate_device(struct tcm_dev **ptcm_dev_ptr, + struct syna_hw_interface *hw_if, unsigned int resp_reading); + +/** + * syna_tcm_remove_device() + * + * Remove the TouchCom core device handle. + * This function must be invoked when the device is no longer needed. + * + * @param + * [ in] tcm_dev: the device handle + * + * @return + * none. + */ +void syna_tcm_remove_device(struct tcm_dev *tcm_dev); + +/** + * syna_tcm_detect_device() + * + * Determine the type of device being connected and distinguish which + * version of TouchCom firmware running on the device. + * This function should be called before using this TouchComm core library. + * + * @param + * [ in] tcm_dev: the device handle + * + * @return + * on success, the current mode running on the device is returned; + * otherwise, negative value on error. + */ +int syna_tcm_detect_device(struct tcm_dev *tcm_dev); + +/** + * syna_tcm_get_event_data() + * + * Helper to read TouchComm messages when ATTN signal is asserted. + * After returning, the ATTN signal should be no longer asserted. + * + * The 'code' returned will guide the caller on the next action. + * For example, do touch reporting once returned code is equal to REPORT_TOUCH. + * + * @param + * [ in] tcm_dev: the device handle + * [out] code: received report code + * [out] report: report data returned + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_get_event_data(struct tcm_dev *tcm_dev, + unsigned char *code, + struct tcm_buffer *report); + +/** + * syna_tcm_change_resp_read() + * + * Helper to change the default resp reading method, which was previously set + * when calling syna_tcm_allocate_device + * + * @param + * [in] tcm_dev: the device handle + * [in] request: resp reading method to change + * set '0' or 'RESP_IN_ATTN' for ATTN-driven; otherwise, + * assign a positive value standing for the polling time + * @return + * none. + */ +void syna_tcm_change_resp_read(struct tcm_dev *tcm_dev, unsigned int request); + +/** + * syna_tcm_identify() + * + * Implement the standard command code, which is used to request + * an IDENTIFY report packet + * + * @param + * [ in] tcm_dev: the device handle + * [out] id_info: the identification info packet returned + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_identify(struct tcm_dev *tcm_dev, + struct tcm_identification_info *id_info); + +/** + * syna_tcm_reset() + * + * Implement the standard command code, which is used to perform a sw reset + * immediately. After a successful reset, an IDENTIFY report to indicate that + * device is ready. + * + * Caller shall be aware that the firmware will be reloaded after reset. + * Therefore, if expecting that a different firmware version is loaded, please + * do app firmware setup after reset. + * + * @param + * [ in] tcm_dev: the device handle + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_reset(struct tcm_dev *tcm_dev); + +/** + * syna_tcm_enable_report() + * + * Implement the application fw command code to enable or disable a report. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] report_code: the requested report code being generated + * [ in] en: '1' for enabling; '0' for disabling + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_enable_report(struct tcm_dev *tcm_dev, + unsigned char report_code, bool en); + +/** + * syna_tcm_switch_fw_mode() + * + * Implement the command code to switch the firmware mode. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] mode: target firmware mode + * [ in] fw_switch_delay: delay time for fw mode switching. + * a positive value presents the time for polling; + * or, set '0' or 'RESP_IN_ATTN' for ATTN driven + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_switch_fw_mode(struct tcm_dev *tcm_dev, + unsigned char mode, unsigned int fw_switch_delay); + +/** + * syna_tcm_get_boot_info() + * + * Implement the bootloader command code, which is used to request a + * boot information packet. + * + * @param + * [ in] tcm_dev: the device handle + * [out] boot_info: the boot info packet returned + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_get_boot_info(struct tcm_dev *tcm_dev, + struct tcm_boot_info *boot_info); + +/** + * syna_tcm_get_app_info() + * + * Implement the application fw command code to request an application + * info packet from device + * + * @param + * [ in] tcm_dev: the device handle + * [out] app_info: the application info packet returned + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_get_app_info(struct tcm_dev *tcm_dev, + struct tcm_application_info *app_info); + +/** + * syna_tcm_get_static_config() + * + * Implement the application fw command code to retrieve the contents of + * the static configuration. + * + * The size of static configuration is available in app info packet. + * + * @param + * [ in] tcm_dev: the device handle + * [out] buf: buffer stored the static configuration + * [ in] buf_size: the size of given buffer + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_get_static_config(struct tcm_dev *tcm_dev, + unsigned char *buf, unsigned int buf_size); + +/** + * syna_tcm_set_static_config() + * + * Implement the application fw command code to set the contents of + * the static configuration. When the write is completed, the device will + * restart touch sensing with the new settings + * + * The size of static configuration is available in app info packet. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] config_data: the data of static configuration + * [ in] config_data_size: the size of given data + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_set_static_config(struct tcm_dev *tcm_dev, + unsigned char *config_data, unsigned int config_data_size); + +/** + * syna_tcm_get_dynamic_config() + * + * Implement the application fw command code to get the value from the a single + * field of the dynamic configuration + * + * @param + * [ in] tcm_dev: the device handle + * [ in] id: target field id + * [out] value: the value returned + * [ in] delay_ms_resp: delay time for response reading. + * a positive value presents the time for polling; + * or, set '0' or 'RESP_IN_ATTN' for ATTN driven + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_get_dynamic_config(struct tcm_dev *tcm_dev, + unsigned char id, unsigned short *value, + unsigned int delay_ms_resp); + +/** + * syna_tcm_set_dynamic_config() + * + * Implement the application fw command code to set the specified value to + * the selected field of the dynamic configuration + * + * @param + * [ in] tcm_dev: the device handle + * [ in] id: target field id + * [ in] value: the value to the selected field + * [ in] delay_ms_resp: delay time for response reading. + * a positive value presents the time for polling; + * or, set '0' or 'RESP_IN_ATTN' for ATTN driven + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_set_dynamic_config(struct tcm_dev *tcm_dev, + unsigned char id, unsigned short value, + unsigned int delay_ms_resp); + +/** + * syna_tcm_rezero() + * + * Implement the application fw command code to force the device to rezero its + * baseline estimate. + * + * @param + * [ in] tcm_dev: the device handle + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_rezero(struct tcm_dev *tcm_dev); + +/** + * syna_tcm_set_config_id() + * + * Implement the application fw command code to set the 16-byte config id, + * which can be read in the app info packet. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] config_id: config id to be set + * [ in] size: size of input data + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_set_config_id(struct tcm_dev *tcm_dev, + unsigned char *config_id, unsigned int size); + +/** + * syna_tcm_sleep() + * + * Implement the application fw command code to put the device into low power + * deep sleep mode or set to normal active mode + * + * @param + * [ in] tcm_dev: the device handle + * [ in] en: '1' to low power deep sleep mode; '0' to active mode + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_sleep(struct tcm_dev *tcm_dev, bool en); + +/** + * syna_tcm_get_features() + * + * Implement the application fw command code to query the supported features. + * + * @param + * [ in] tcm_dev: the device handle + * [out] info: the features description packet returned + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_get_features(struct tcm_dev *tcm_dev, + struct tcm_features_info *info); + +/** + * syna_tcm_run_production_test() + * + * Implement the appplication fw command code to request the device to run + * the production test. + * + * Production tests are listed at enum test_code (PID$). + * + * @param + * [ in] tcm_dev: the device handle + * [ in] test_item: the requested testing item + * [out] tdata: testing data returned + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_run_production_test(struct tcm_dev *tcm_dev, + unsigned char test_item, struct tcm_buffer *tdata); + +/** + * syna_tcm_send_command() + * + * Helper to forward the custom commnd to the device + * + * @param + * [ in] tcm_dev: the device handle + * [ in] command: TouchComm command + * [ in] payload: data payload, if any + * [ in] payload_length: length of data payload, if any + * [out] resp_code: response code returned + * [out] resp: buffer to store the response data + * [ in] delay_ms_resp: delay time for response reading. + * a positive value presents the time for polling; + * or, set '0' or 'RESP_IN_ATTN' for ATTN driven + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_send_command(struct tcm_dev *tcm_dev, + unsigned char command, unsigned char *payload, + unsigned int payload_length, unsigned char *resp_code, + struct tcm_buffer *resp, unsigned int delay_ms_resp); + + +#endif /* end of _SYNAPTICS_TOUCHCOM_BASE_FUNCS_H_ */ diff --git a/tcm/synaptics_touchcom_func_base_flash.h b/tcm/synaptics_touchcom_func_base_flash.h new file mode 100644 index 0000000..87d0a4e --- /dev/null +++ b/tcm/synaptics_touchcom_func_base_flash.h @@ -0,0 +1,569 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Synaptics TouchCom touchscreen driver + * + * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +/** + * @file synaptics_touchcom_func_base_flash.h + * + * This file declares the common functions and structures being used in relevant + * functions of fw update. + */ + +#ifndef _SYNAPTICS_TOUCHCOM_PARSE_FW_FILES_H_ +#define _SYNAPTICS_TOUCHCOM_PARSE_FW_FILES_H_ + +#include "synaptics_touchcom_core_dev.h" + +/** + * @section: Some specific definition in the firmware file + */ +#define ID_STRING_SIZE (32) + +#define SIZE_WORDS (8) + +#define IHEX_RECORD_SIZE (14) + +#define IHEX_MAX_BLOCKS (64) + +#define IMAGE_FILE_MAGIC_VALUE (0x4818472b) + +#define FLASH_AREA_MAGIC_VALUE (0x7c05e516) + + +/** + * @section: Helper macros for firmware file parsing + */ +#define CRC32(data, length) \ + (syna_pal_crc32(~0, data, length) ^ ~0) + +#define VALUE(value) \ + (syna_pal_le2_to_uint(value)) + +#define AREA_ID_STR(area) \ + (syna_tcm_get_flash_area_string(area)) + + +/** + * @section: Area Partitions in firmware + */ +enum flash_area { + AREA_NONE = 0, + /* please add the declarations below */ + + AREA_BOOT_CODE, + AREA_BOOT_CONFIG, + AREA_APP_CODE, + AREA_APP_CODE_COPRO, + AREA_APP_CONFIG, + AREA_PROD_TEST, + AREA_DISP_CONFIG, + AREA_F35_APP_CODE, + AREA_FORCE_TUNING, + AREA_GAMMA_TUNING, + AREA_TEMPERATURE_GAMM_TUNING, + AREA_CUSTOM_LCM, + AREA_LOOKUP, + AREA_CUSTOM_OEM, + AREA_OPEN_SHORT_TUNING, + AREA_CUSTOM_OTP, + AREA_PPDT, + AREA_ROMBOOT_APP_CODE, + AREA_TOOL_BOOT_CONFIG, + + /* please add the declarations above */ + AREA_MAX, +}; +/** + * @section: String of Area Partitions in firmware + */ +static char *flash_area_str[] = { + NULL, + /* please add the declarations below */ + + "BOOT_CODE", /* AREA_BOOT_CODE */ + "BOOT_CONFIG", /* AREA_BOOT_CONFIG */ + "APP_CODE", /* AREA_APP_CODE */ + "APP_CODE_COPRO", /* AREA_APP_CODE_COPRO */ + "APP_CONFIG", /* AREA_APP_CONFIG */ + "APP_PROD_TEST", /* AREA_PROD_TEST */ + "DISPLAY", /* AREA_DISP_CONFIG */ + "F35_APP_CODE", /* AREA_F35_APP_CODE */ + "FORCE", /* AREA_FORCE_TUNING */ + "GAMMA", /* AREA_GAMMA_TUNING */ + "TEMPERATURE_GAMM",/* AREA_TEMPERATURE_GAMM_TUNING */ + "LCM", /* AREA_CUSTOM_LCM */ + "LOOKUP", /* AREA_LOOKUP */ + "OEM", /* AREA_CUSTOM_OEM */ + "OPEN_SHORT", /* AREA_OPEN_SHORT_TUNING */ + "OTP", /* AREA_CUSTOM_OTP */ + "PPDT", /* AREA_PPDT */ + "ROMBOOT_APP_CODE",/* AREA_ROMBOOT_APP_CODE */ + "TOOL_BOOT_CONFIG",/* AREA_TOOL_BOOT_CONFIG */ + + /* please add the declarations above */ + NULL +}; +/** + * @section: Header Content of app config defined + * in firmware file + */ +struct app_config_header { + unsigned short magic_value[4]; + unsigned char checksum[4]; + unsigned char length[2]; + unsigned char build_id[4]; + unsigned char customer_config_id[16]; +}; +/** + * @section: The Partition Descriptor defined + * in firmware file + */ +struct area_descriptor { + unsigned char magic_value[4]; + unsigned char id_string[16]; + unsigned char flags[4]; + unsigned char flash_addr_words[4]; + unsigned char length[4]; + unsigned char checksum[4]; +}; +/** + * @section: Structure for the Data Block defined + * in firmware file + */ +struct block_data { + bool available; + const unsigned char *data; + unsigned int size; + unsigned int flash_addr; + unsigned char id; +}; +/** + * @section: Structure for the Parsed Image File + */ +struct image_info { + struct block_data data[AREA_MAX]; +}; +/** + * @section: Header of Image File + * + * Define the header of firmware image file + */ +struct image_header { + unsigned char magic_value[4]; + unsigned char num_of_areas[4]; +}; +/** + * @section: Structure for the Parsed iHex File + */ +struct ihex_info { + unsigned int records; + unsigned char *bin; + unsigned int bin_size; + struct block_data block[IHEX_MAX_BLOCKS]; +}; + +/** + * syna_tcm_get_flash_area_string() + * + * Return the string ID of target area in the flash memory + * + * @param + * [ in] area: target flash area + * + * @return + * the string ID + */ +static inline char *syna_tcm_get_flash_area_string(enum flash_area area) +{ + if (area < AREA_MAX) + return (char *)flash_area_str[area]; + else + return ""; +} + +/** + * syna_tcm_save_flash_block_data() + * + * Save the block data of flash memory to the corresponding structure. + * + * @param + * [out] image_info: image info used for storing the block data + * [ in] area: target area + * [ in] content: content of data + * [ in] flash_addr: offset of block data + * [ in] size: size of block data + * [ in] checksum: checksum of block data + * + * @return + * on success, return 0; otherwise, negative value on error. + */ +static int syna_tcm_save_flash_block_data(struct image_info *image_info, + enum flash_area area, const unsigned char *content, + unsigned int offset, unsigned int size, unsigned int checksum) +{ + if (!image_info) { + LOGE("Invalid image_info\n"); + return _EINVAL; + } + + if (area >= AREA_MAX) { + LOGE("Invalid flash area\n"); + return _EINVAL; + } + + if (checksum != CRC32((const char *)content, size)) { + LOGE("%s checksum error, in image: 0x%x (0x%x)\n", + AREA_ID_STR(area), checksum, + CRC32((const char *)content, size)); + return _EINVAL; + } + image_info->data[area].size = size; + image_info->data[area].data = content; + image_info->data[area].flash_addr = offset; + image_info->data[area].id = (unsigned char)area; + image_info->data[area].available = true; + + LOGI("%s area - address:0x%08x (%d), size:%d\n", + AREA_ID_STR(area), offset, offset, size); + + return 0; +} + +/** + * syna_tcm_get_flash_area_id() + * + * Return the corresponding ID of flash area based on the given string + * + * @param + * [ in] str: string to look for + * + * + * @return + * if matching, return the corresponding ID; otherwise, return AREA_MAX. + */ +static enum flash_area syna_tcm_get_flash_area_id(char *str) +{ + int area; + char *target; + unsigned int len; + + for (area = AREA_MAX - 1; area >= 0; area--) { + target = AREA_ID_STR(area); + len = syna_pal_str_len(target); + + if (syna_pal_str_cmp(str, target, len) == 0) + return area; + } + + LOGW("Un-defined area string, %s\n", str); + return AREA_MAX; +} + +/** + * syna_tcm_parse_fw_image() + * + * Parse and analyze the information of each areas from the given + * firmware image. + * + * @param + * [ in] image: image file given + * [ in] image_info: data blob stored the parsed data from an image file + * + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static inline int syna_tcm_parse_fw_image(const unsigned char *image, + struct image_info *image_info) +{ + int retval = 0; + unsigned int idx; + unsigned int addr; + unsigned int offset; + unsigned int length; + unsigned int checksum; + unsigned int flash_addr; + unsigned int magic_value; + unsigned int num_of_areas; + struct image_header *header; + struct area_descriptor *descriptor; + const unsigned char *content; + enum flash_area target_area; + + if (!image) { + LOGE("No image data\n"); + return _EINVAL; + } + + if (!image_info) { + LOGE("Invalid image_info blob\n"); + return _EINVAL; + } + + syna_pal_mem_set(image_info, 0x00, sizeof(struct image_info)); + + header = (struct image_header *)image; + + magic_value = syna_pal_le4_to_uint(header->magic_value); + if (magic_value != IMAGE_FILE_MAGIC_VALUE) { + LOGE("Invalid image file magic value\n"); + return _EINVAL; + } + + offset = sizeof(struct image_header); + num_of_areas = syna_pal_le4_to_uint(header->num_of_areas); + + for (idx = 0; idx < num_of_areas; idx++) { + addr = syna_pal_le4_to_uint(image + offset); + descriptor = (struct area_descriptor *)(image + addr); + offset += 4; + + magic_value = syna_pal_le4_to_uint(descriptor->magic_value); + if (magic_value != FLASH_AREA_MAGIC_VALUE) + continue; + + length = syna_pal_le4_to_uint(descriptor->length); + content = (unsigned char *)descriptor + sizeof(*descriptor); + flash_addr = syna_pal_le4_to_uint(descriptor->flash_addr_words); + flash_addr = flash_addr * 2; + checksum = syna_pal_le4_to_uint(descriptor->checksum); + + target_area = syna_tcm_get_flash_area_id( + (char *)descriptor->id_string); + + retval = syna_tcm_save_flash_block_data(image_info, + target_area, + content, + flash_addr, + length, + checksum); + if (retval < 0) + return _EINVAL; + } + + return 0; +} + + +/** + * syna_tcm_parse_ihex_line() + * + * Parse a line in the ihex file and convert into an actual data + * + * @param + * [ in] line: a line of string stored in the ihex file + * [out] count: size of actual data + * [out] addr: address of data located + * [out] type: the type of data belonging + * [out] buf: a buffer to store the converted data + * [int] buf_size: size of buffer + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static inline int syna_tcm_parse_ihex_line(char *line, unsigned int *count, + unsigned int *addr, unsigned int *type, unsigned char *buf, + unsigned int buf_size) +{ + const int OFFSET_COUNT = 1; + const int SIZE_COUNT = 2; + const int OFFSET_ADDR = OFFSET_COUNT + SIZE_COUNT; + const int SIZE_ADDR = 4; + const int OFFSET_TYPE = OFFSET_ADDR + SIZE_ADDR; + const int SIZE_TYPE = 2; + const int OFFSET_DATA = OFFSET_TYPE + SIZE_TYPE; + const int SIZE_DATA = 2; + unsigned int pos; + + if (!line) { + LOGE("No string line\n"); + return _EINVAL; + } + + if ((!buf) || (buf_size == 0)) { + LOGE("Invalid temporary data buffer\n"); + return _EINVAL; + } + + *count = syna_pal_hex_to_uint( + line + OFFSET_COUNT, 2); + *addr = syna_pal_hex_to_uint( + line + OFFSET_ADDR, SIZE_ADDR); + *type = syna_pal_hex_to_uint( + line + OFFSET_TYPE, SIZE_TYPE); + + if (*count > buf_size) { + LOGE("Data size mismatched, required:%d, given:%d\n", + *count, buf_size); + return _EINVAL; + } + + for (pos = 0; pos < *count; pos++) + buf[pos] = (unsigned char)syna_pal_hex_to_uint( + line + (((int)(pos << 1)) + OFFSET_DATA), + SIZE_DATA); + + return 0; +} + +/** + * syna_tcm_parse_fw_ihex() + * + * Based on the firmware ihex file given , parse and convert into a binary + * firmware data to update. + * + * @param + * [ in] ihex: original ihex file + * [ in] ihex_size: size of given file + * [ in] ihex_info: data blob stored the parsed data from an ihex file. + * assume the data buffer inside was allocated + * [ in] len_per_line: length for a useful data in a line + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static inline int syna_tcm_parse_fw_ihex(const char *ihex, int ihex_size, + struct ihex_info *ihex_info, const unsigned int len_per_line) +{ + int retval; + unsigned int pos; + unsigned int record; + char *tmp = NULL; + unsigned int count; + unsigned int type; + unsigned char data[32] = { 0 }; + unsigned int addr; + unsigned int offset; + unsigned int prev_addr; + unsigned int block_idx = 0; + + if (!ihex) { + LOGE("No ihex data\n"); + return _EINVAL; + } + + if (!ihex_info) { + LOGE("Invalid ihex_info blob\n"); + return _EINVAL; + } + + if ((!ihex_info->bin) || (ihex_info->bin_size == 0)) { + LOGE("Invalid ihex_info->data\n"); + return _EINVAL; + } + + tmp = syna_pal_mem_alloc(len_per_line + 1, sizeof(char)); + if (!tmp) { + LOGE("Fail to allocate temporary buffer\n"); + return _ENOMEM; + } + + offset = 0; + addr = 0; + pos = 0; + prev_addr = 0; + + ihex_info->records = ihex_size / len_per_line; + LOGD("records = %d\n", ihex_info->records); + + for (record = 0; record < ihex_info->records; record++) { + pos = record * len_per_line; + if ((char)ihex[pos] != ':') { + LOGE("Invalid string maker at pos %d, marker:%c\n", + pos, (char)ihex[pos]); + goto exit; + } + + retval = syna_pal_mem_cpy(tmp, len_per_line, + &ihex[pos], ihex_size - pos, len_per_line); + if (retval < 0) { + LOGE("Fail to copy a line at pos %d\n", pos); + goto exit; + } + + retval = syna_tcm_parse_ihex_line(tmp, &count, &addr, &type, + data, sizeof(data)); + if (retval < 0) { + LOGE("Fail to parse line at pos %d\n", pos); + goto exit; + } + + if ((((prev_addr + 2) & 0xFFFF) != addr) && (type == 0x00)) { + block_idx = (record == 0) ? 0 : block_idx + 1; + if (block_idx >= IHEX_MAX_BLOCKS) { + LOGE("Invalid block index\n"); + goto exit; + } + + ihex_info->block[block_idx].flash_addr = + addr + offset; + ihex_info->block[block_idx].data = + &ihex_info->bin[addr + offset]; + ihex_info->block[block_idx].available = true; + } + + if (type == 0x00) { + + prev_addr = addr; + addr += offset; + + if (addr >= ihex_info->bin_size) { + LOGE("No enough size for data0 addr:0x%x(%d)\n", + addr, addr); + goto exit; + } + ihex_info->bin[addr++] = data[0]; + + if (addr >= ihex_info->bin_size) { + LOGE("No enough size for data1 addr:0x%x(%d)\n", + addr, addr); + goto exit; + } + ihex_info->bin[addr++] = data[1]; + + ihex_info->block[block_idx].size += 2; + + } else if (type == 0x02) { + offset = (data[0] << 8) + data[1]; + offset <<= 4; + } + } + + ihex_info->bin_size = addr; /* the actual size after data reordering */ + LOGN("Size of firmware binary data = %d\n", ihex_info->bin_size); + +exit: + syna_pal_mem_free((void *)tmp); + + return 0; +} + + +#endif /* end of _SYNAPTICS_TOUCHCOM_PARSE_FW_FILES_H_ */ diff --git a/tcm/synaptics_touchcom_func_reflash.c b/tcm/synaptics_touchcom_func_reflash.c new file mode 100644 index 0000000..af67b4a --- /dev/null +++ b/tcm/synaptics_touchcom_func_reflash.c @@ -0,0 +1,2074 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +/** + * @file synaptics_touchcom_func_reflash.c + * + * This file implements the fw reflash related functions of TouchBoot. + * The declarations are available in synaptics_touchcom_func_reflash.h. + */ + +#include "synaptics_touchcom_func_base.h" +#include "synaptics_touchcom_func_reflash.h" + +/** + * @section: Reflash relevant definitions + * + */ +#define FLASH_READ_DELAY_MS (10) +#define FLASH_WRITE_DELAY_MS (20) +#define FLASH_ERASE_DELAY_MS (500) + +#define BOOT_CONFIG_SIZE 8 +#define BOOT_CONFIG_SLOTS 16 + +#define DO_NONE (0) +#define DO_UPDATE (1) + +/** + * syna_tcm_set_up_flash_access() + * + * Enter the bootloader fw if not in the mode. + * Besides, get the necessary parameters in boot info. + * + * @param + * [ in] tcm_dev: the device handle + * [out] reflash_data: data blob for reflash + * + * @return + * Result of image file comparison + */ +static int syna_tcm_set_up_flash_access(struct tcm_dev *tcm_dev, + struct tcm_reflash_data_blob *reflash_data) +{ + int retval; + unsigned int temp; + struct tcm_identification_info id_info; + struct tcm_boot_info *boot_info; + unsigned int wr_chunk; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if (!reflash_data) { + LOGE("Invalid reflash data blob\n"); + return _EINVAL; + } + + LOGI("Set up flash access\n"); + + retval = syna_tcm_identify(tcm_dev, &id_info); + if (retval < 0) { + LOGE("Fail to do identification\n"); + return retval; + } + + /* switch to bootloader mode */ + if (IS_APP_FW_MODE(id_info.mode)) { + LOGI("Prepare to enter bootloader mode\n"); + + retval = syna_tcm_switch_fw_mode(tcm_dev, + MODE_BOOTLOADER, + FW_MODE_SWITCH_DELAY_MS); + if (retval < 0) { + LOGE("Fail to enter bootloader mode\n"); + return retval; + } + } + + if (!IS_BOOTLOADER_MODE(tcm_dev->dev_mode)) { + LOGE("Fail to enter bootloader mode (current: 0x%x)\n", + tcm_dev->dev_mode); + return retval; + } + + boot_info = &tcm_dev->boot_info; + + /* get boot info to set up the flash access */ + retval = syna_tcm_get_boot_info(tcm_dev, boot_info); + if (retval < 0) { + LOGE("Fail to get boot info at mode 0x%x\n", + id_info.mode); + return retval; + } + + wr_chunk = tcm_dev->max_wr_size; + + temp = boot_info->write_block_size_words; + reflash_data->write_block_size = temp * 2; + + LOGI("Write block size: %d (words size: %d)\n", + reflash_data->write_block_size, temp); + + temp = syna_pal_le2_to_uint(boot_info->erase_page_size_words); + reflash_data->page_size = temp * 2; + + LOGI("Erase page size: %d (words size: %d)\n", + reflash_data->page_size, temp); + + temp = syna_pal_le2_to_uint(boot_info->max_write_payload_size); + reflash_data->max_write_payload_size = temp; + + LOGI("Max write flash data size: %d\n", + reflash_data->max_write_payload_size); + + if (reflash_data->write_block_size > (wr_chunk - 9)) { + LOGE("Write block size, %d, greater than chunk space, %d\n", + reflash_data->write_block_size, (wr_chunk - 9)); + return _EINVAL; + } + + if (reflash_data->write_block_size == 0) { + LOGE("Invalid write block size %d\n", + reflash_data->write_block_size); + return _EINVAL; + } + + if (reflash_data->page_size == 0) { + LOGE("Invalid erase page size %d\n", + reflash_data->page_size); + return _EINVAL; + } + + return 0; +} + +/** + * syna_tcm_compare_image_id_info() + * + * Compare the ID information between device and the image file, + * and then determine the area to be updated. + * The function should be called after parsing the image file. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] reflash_data: data blob for reflash + * + * @return + * Blocks to be updated + */ +int syna_tcm_compare_image_id_info(struct tcm_dev *tcm_dev, + struct tcm_reflash_data_blob *reflash_data) +{ + enum update_area result; + unsigned int idx; + unsigned int image_fw_id; + unsigned int device_fw_id; + unsigned char *image_config_id; + unsigned char *device_config_id; + struct app_config_header *header; + const unsigned char *app_config_data; + struct block_data *app_config; + + result = UPDATE_NONE; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if (!reflash_data) { + LOGE("Invalid reflash_data\n"); + return _EINVAL; + } + + app_config = &reflash_data->image_info.data[AREA_APP_CONFIG]; + + if (app_config->size < sizeof(struct app_config_header)) { + LOGE("Invalid application config in image file\n"); + return _EINVAL; + } + + app_config_data = app_config->data; + header = (struct app_config_header *)app_config_data; + + image_fw_id = syna_pal_le4_to_uint(header->build_id); + device_fw_id = tcm_dev->packrat_number; + + LOGN("Device firmware ID: %d, image build id: %d\n", + device_fw_id, image_fw_id); + + if (image_fw_id > device_fw_id) { + LOGN("Image build ID newer than device fw ID\n"); + result = UPDATE_FIRMWARE_CONFIG; + goto exit; + } else { + result = UPDATE_NONE; + goto exit; + } + + image_config_id = header->customer_config_id; + device_config_id = tcm_dev->app_info.customer_config_id; + + for (idx = 0; idx < MAX_SIZE_CONFIG_ID; idx++) { + if (image_config_id[idx] != device_config_id[idx]) { + LOGN("Different Config ID\n"); + result = UPDATE_CONFIG_ONLY; + goto exit; + } + } + + result = UPDATE_NONE; + +exit: + switch (result) { + case UPDATE_FIRMWARE_CONFIG: + LOGN("Update firmware and config\n"); + break; + case UPDATE_CONFIG_ONLY: + LOGN("Update config only\n"); + break; + case UPDATE_NONE: + default: + LOGN("No need to do reflash\n"); + break; + } + + return (int)result; +} + +/** + * syna_tcm_check_flash_boot_config() + * + * Check whether the same flash address of boot config in between the device + * and the image file. + * + * @param + * [ in] boot_config: block data of boot_config from image file + * [ in] boot_info: data of boot info + * [ in] block_size: max size of write block + * + * @return + * '0' represent no need to do reflash; + * '1' represent a positive result of checking; + * otherwise, negative value on error. + */ +static int syna_tcm_check_flash_boot_config(struct block_data *boot_config, + struct tcm_boot_info *boot_info, unsigned int block_size) +{ + unsigned int start_block; + unsigned int image_addr; + unsigned int device_addr; + + if (!boot_config) { + LOGE("Invalid boot_config block data\n"); + return _EINVAL; + } + + if (!boot_info) { + LOGE("Invalid boot_info\n"); + return _EINVAL; + } + + if (boot_config->size < BOOT_CONFIG_SIZE) { + LOGE("No valid BOOT_CONFIG size, %d, in image file\n", + boot_config->size); + return _EINVAL; + } + + image_addr = boot_config->flash_addr; + + LOGD("Boot Config address in image file: 0x%x\n", image_addr); + + start_block = VALUE(boot_info->boot_config_start_block); + device_addr = start_block * block_size; + + LOGD("Boot Config address in device: 0x%x\n", device_addr); + + return DO_NONE; +} + +/** + * syna_tcm_check_flash_app_config() + * + * Check whether the same flash address of app config in between the + * device and the image file. + * + * @param + * [ in] app_config: block data of app_config from image file + * [ in] app_info: data of application info + * [ in] block_size: max size of write block + * + * @return + * '0' represent no need to do reflash; + * '1' represent a positive result of checking; + * otherwise, negative value on error. + */ +static int syna_tcm_check_flash_app_config(struct block_data *app_config, + struct tcm_application_info *app_info, unsigned int block_size) +{ + unsigned int temp; + unsigned int image_addr; + unsigned int image_size; + unsigned int device_addr; + unsigned int device_size; + + if (!app_config) { + LOGE("Invalid app_config block data\n"); + return _EINVAL; + } + + if (!app_info) { + LOGE("Invalid app_info\n"); + return _EINVAL; + } + + if (app_config->size == 0) { + LOGD("No APP_CONFIG in image file\n"); + return DO_NONE; + } + + image_addr = app_config->flash_addr; + image_size = app_config->size; + + LOGD("App Config address in image file: 0x%x, size: %d\n", + image_addr, image_size); + + temp = VALUE(app_info->app_config_start_write_block); + device_addr = temp * block_size; + device_size = VALUE(app_info->app_config_size); + + LOGD("App Config address in device: 0x%x, size: %d\n", + device_addr, device_size); + + if (device_addr == 0 && device_size == 0) + return DO_UPDATE; + + if (image_addr != device_addr) + LOGW("App Config address mismatch, image:0x%x, dev:0x%x\n", + image_addr, device_addr); + + if (image_size != device_size) + LOGW("App Config address size mismatch, image:%d, dev:%d\n", + image_size, device_size); + + return DO_UPDATE; +} + +/** + * syna_tcm_check_flash_disp_config() + * + * Check whether the same flash address of display config in between the + * device and the image file. + * + * @param + * [ in] disp_config: block data of disp_config from image file + * [ in] boot_info: data of boot info + * [ in] block_size: max size of write block + * + * @return + * '0' represent no need to do reflash; + * '1' represent a positive result of checking; + * otherwise, negative value on error. + */ +static int syna_tcm_check_flash_disp_config(struct block_data *disp_config, + struct tcm_boot_info *boot_info, unsigned int block_size) +{ + unsigned int temp; + unsigned int image_addr; + unsigned int image_size; + unsigned int device_addr; + unsigned int device_size; + + if (!disp_config) { + LOGE("Invalid disp_config block data\n"); + return _EINVAL; + } + + if (!boot_info) { + LOGE("Invalid boot_info\n"); + return _EINVAL; + } + + /* disp_config area may not be included in all product */ + if (disp_config->size == 0) { + LOGD("No DISP_CONFIG in image file\n"); + return DO_NONE; + } + + image_addr = disp_config->flash_addr; + image_size = disp_config->size; + + LOGD("Disp Config address in image file: 0x%x, size: %d\n", + image_addr, image_size); + + temp = VALUE(boot_info->display_config_start_block); + device_addr = temp * block_size; + + temp = VALUE(boot_info->display_config_length_blocks); + device_size = temp * block_size; + + LOGD("Disp Config address in device: 0x%x, size: %d\n", + device_addr, device_size); + + if (image_addr != device_addr) + LOGW("Disp Config address mismatch, image:0x%x, dev:0x%x\n", + image_addr, device_addr); + + if (image_size != device_size) + LOGW("Disp Config address size mismatch, image:%d, dev:%d\n", + image_size, device_size); + + return DO_UPDATE; +} + +/** + * syna_tcm_check_flash_app_code() + * + * Check whether the valid size of app firmware in the image file + * + * @param + * [ in] app_code: block data of app_code from image file + * + * @return + * '0' represent no need to do reflash; + * '1' represent a positive result of checking; + * otherwise, negative value on error. + */ +static int syna_tcm_check_flash_app_code(struct block_data *app_code) +{ + if (!app_code) { + LOGE("Invalid app_code block data\n"); + return _EINVAL; + } + + if (app_code->size == 0) { + LOGD("No %s in image file\n", AREA_ID_STR(app_code->id)); + return _EINVAL; + } + + return DO_UPDATE; +} + +/** + * syna_tcm_check_flash_openshort() + * + * Check whether the valid size of openshort area in the image file + * + * @param + * [ in] open_short: block data of open_short from image file + * + * @return + * '0' represent no need to do reflash; + * '1' represent a positive result of checking; + * otherwise, negative value on error. + */ +static int syna_tcm_check_flash_openshort(struct block_data *open_short) +{ + if (!open_short) { + LOGE("Invalid open_short block data\n"); + return _EINVAL; + } + + /* open_short area may not be included in all product */ + if (open_short->size == 0) { + LOGD("No %s in image file\n", AREA_ID_STR(open_short->id)); + return DO_NONE; + } + + return DO_UPDATE; +} + +/** + * syna_tcm_check_flash_app_prod_test() + * + * Check whether the valid size of app prod_test area in the image file + * + * @param + * [ in] prod_test: block data of app_prod_test from image file + * + * @return + * '0' represent no need to do reflash; + * '1' represent a positive result of checking; + * otherwise, negative value on error. + */ +static int syna_tcm_check_flash_app_prod_test(struct block_data *prod_test) +{ + if (!prod_test) { + LOGE("Invalid app_prod_test block data\n"); + return _EINVAL; + } + + /* app_prod_test area may not be included in all product */ + if (prod_test->size == 0) { + LOGD("No %s in image file\n", AREA_ID_STR(prod_test->id)); + return DO_NONE; + } + + return DO_UPDATE; +} + +/** + * syna_tcm_check_flash_ppdt() + * + * Check whether the valid size of ppdt area in the image file + * + * @param + * [ in] ppdt: block data of PPDT from image file + * + * @return + * '0' represent no need to do reflash; + * '1' represent a positive result of checking; + * otherwise, negative value on error. + */ +static int syna_tcm_check_flash_ppdt(struct block_data *ppdt) +{ + if (!ppdt) { + LOGE("Invalid ppdt block data\n"); + return _EINVAL; + } + + /* open_short area may not be included in all product */ + if (ppdt->size == 0) { + LOGD("No %s in image file\n", AREA_ID_STR(ppdt->id)); + return DO_NONE; + } + + return DO_UPDATE; +} + +/** + * syna_tcm_check_flash_block() + * + * Dispatch to the proper helper to ensure the data of associated block area + * is correct in between the device and the image file. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] reflash_data: data blob for reflash + * [ in] block: target block area to check + * + * @return + * '0' represent no need to do reflash; + * '1' represent a positive result of checking; + * otherwise, negative value on error. + */ +static int syna_tcm_check_flash_block(struct tcm_dev *tcm_dev, + struct tcm_reflash_data_blob *reflash_data, + struct block_data *block) +{ + int retval = 0; + struct tcm_application_info *app_info; + struct tcm_boot_info *boot_info; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return DO_NONE; + } + + if (!reflash_data) { + LOGE("Invalid reflash data blob\n"); + return DO_NONE; + } + + if (!block) { + LOGE("Invalid block data\n"); + return DO_NONE; + } + + app_info = &tcm_dev->app_info; + boot_info = &tcm_dev->boot_info; + + switch (block->id) { + case AREA_APP_CODE: + retval = syna_tcm_check_flash_app_code(block); + break; + case AREA_APP_CONFIG: + retval = syna_tcm_check_flash_app_config(block, app_info, + reflash_data->write_block_size); + break; + case AREA_BOOT_CONFIG: + retval = syna_tcm_check_flash_boot_config(block, boot_info, + reflash_data->write_block_size); + break; + case AREA_DISP_CONFIG: + retval = syna_tcm_check_flash_disp_config(block, boot_info, + reflash_data->write_block_size); + break; + case AREA_OPEN_SHORT_TUNING: + retval = syna_tcm_check_flash_openshort(block); + break; + case AREA_PROD_TEST: + retval = syna_tcm_check_flash_app_prod_test(block); + break; + case AREA_PPDT: + retval = syna_tcm_check_flash_ppdt(block); + break; + default: + retval = DO_NONE; + break; + } + + return retval; +} + +/** + * syna_tcm_get_flash_data_location() + * + * Return the address and length of the specified data area + * in the flash memory. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] area: specified area in flash memory + * [out] addr: the flash address of the specified area returned + * [out] len: the size of the specified area returned + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_get_flash_data_location(struct tcm_dev *tcm_dev, + enum flash_area area, unsigned int *addr, unsigned int *len) +{ + int retval = 0; + unsigned char resp_code; + unsigned char payload; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + switch (area) { + case AREA_CUSTOM_LCM: + payload = FLASH_LCM_DATA; + break; + case AREA_CUSTOM_OEM: + payload = FLASH_OEM_DATA; + break; + case AREA_PPDT: + payload = FLASH_PPDT_DATA; + break; + case AREA_FORCE_TUNING: + payload = FLASH_FORCE_CALIB_DATA; + break; + case AREA_OPEN_SHORT_TUNING: + payload = FLASH_OPEN_SHORT_TUNING_DATA; + break; + default: + LOGE("Invalid flash area %d\n", area); + return _EINVAL; + } + + retval = tcm_dev->write_message(tcm_dev, + CMD_GET_DATA_LOCATION, + &payload, + sizeof(payload), + &resp_code, + tcm_dev->msg_data.default_resp_reading); + if (retval < 0) { + LOGE("Fail to send command 0x%02x\n", + CMD_GET_DATA_LOCATION); + goto exit; + } + + if (tcm_dev->resp_buf.data_length != 4) { + LOGE("Invalid data length %d\n", + tcm_dev->resp_buf.data_length); + retval = _EINVAL; + goto exit; + } + + *addr = syna_pal_le2_to_uint(&tcm_dev->resp_buf.buf[0]); + *len = syna_pal_le2_to_uint(&tcm_dev->resp_buf.buf[2]); + + retval = 0; + +exit: + return retval; +} + +/** + * syna_tcm_reflash_send_command() + * + * Helper to wrap up the write_message() function. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] command: given command code + * [ in] payload: payload data, if any + * [ in] payload_len: length of payload data + * [ in] delay_ms_resp: delay time to get the response of command + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_reflash_send_command(struct tcm_dev *tcm_dev, + unsigned char command, unsigned char *payload, + unsigned int payload_len, unsigned int delay_ms_resp) +{ + int retval = 0; + unsigned char resp_code; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if (!IS_BOOTLOADER_MODE(tcm_dev->dev_mode)) { + LOGE("Device is not in BL mode, 0x%x\n", tcm_dev->dev_mode); + retval = _EINVAL; + } + + retval = tcm_dev->write_message(tcm_dev, + command, + payload, + payload_len, + &resp_code, + delay_ms_resp); + if (retval < 0) { + LOGE("Fail to send command 0x%02x\n", command); + goto exit; + } + +exit: + return retval; +} + +/** + * syna_tcm_read_flash() + * + * Implement the bootloader command to read specified data from flash memory. + * + * Reads to the protected bootloader code or application code areas will read + * as 0. If the number of words requested is too large, it may be truncated to + * an defined maximum read size. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] address: the address in flash memory to read + * [out] rd_data: data retrieved + * [ in] rd_len: length of data to be read + * [ in] rd_delay_ms: a short delay after the command executed + * set 'DEFAULT_FLASH_READ_DELAY' to use default, + * which is 10 us; + * set '0' or 'RESP_IN_ATTN' to select ATTN-driven. + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_read_flash(struct tcm_dev *tcm_dev, + unsigned int address, unsigned char *rd_data, + unsigned int rd_len, unsigned int rd_delay_ms) +{ + int retval = 0; + unsigned int length_words; + unsigned int flash_addr_words; + unsigned char out[6] = { 0 }; + unsigned int delay_ms = 0; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if (!rd_data) { + LOGE("Invalid rd_data buffer\n"); + return _EINVAL; + } + + if (address == 0 || rd_len == 0) { + LOGE("Invalid flash address and length\n"); + retval = _EINVAL; + goto exit; + } + + length_words = rd_len / 2; + flash_addr_words = address / 2; + + LOGD("Flash address: 0x%x (words: 0x%x), size: %d (words: %d)\n", + address, flash_addr_words, rd_len, length_words); + + out[0] = (unsigned char)flash_addr_words; + out[1] = (unsigned char)(flash_addr_words >> 8); + out[2] = (unsigned char)(flash_addr_words >> 16); + out[3] = (unsigned char)(flash_addr_words >> 24); + out[4] = (unsigned char)length_words; + out[5] = (unsigned char)(length_words >> 8); + + if (rd_delay_ms == DEFAULT_FLASH_READ_DELAY) + delay_ms = FLASH_READ_DELAY_MS; + else + delay_ms = rd_delay_ms; + + if (delay_ms == RESP_IN_ATTN) { + LOGD("xfer: %d, delay: ATTN-driven\n", length_words); + } else { + delay_ms = (delay_ms * length_words) / 1000; + LOGD("xfer: %d, delay: %d\n", length_words, delay_ms); + } + + retval = syna_tcm_reflash_send_command(tcm_dev, + CMD_READ_FLASH, + out, + sizeof(out), + delay_ms); + if (retval < 0) { + LOGE("Fail to read flash data from addr 0x%x, size %d\n", + address, rd_len); + goto exit; + } + + if (tcm_dev->resp_buf.data_length != rd_len) { + LOGE("Fail to read requested length %d, rd_len %d\n", + tcm_dev->resp_buf.data_length, rd_len); + retval = _EIO; + goto exit; + } + + retval = syna_pal_mem_cpy(rd_data, + rd_len, + tcm_dev->resp_buf.buf, + tcm_dev->resp_buf.buf_size, + rd_len); + if (retval < 0) { + LOGE("Fail to copy read data, size %d\n", rd_len); + goto exit; + } + +exit: + return retval; +} + +/** + * syna_tcm_read_flash_boot_config() + * + * Read the data of boot config area in the flash memory. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] reflash_data: data blob for reflash + * [out] rd_data: buffer used for storing the retrieved data + * [ in] rd_delay_us: delay time to access data in flash memory + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_read_flash_boot_config(struct tcm_dev *tcm_dev, + struct tcm_reflash_data_blob *reflash_data, + struct tcm_buffer *rd_data, unsigned int rd_delay_us) +{ + int retval; + unsigned int temp; + unsigned int addr = 0; + unsigned int length = 0; + struct tcm_boot_info *boot_info; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if (!reflash_data) { + LOGE("Invalid reflash data blob\n"); + return _EINVAL; + } + + if (!rd_data) { + LOGE("Invalid read data buffer\n"); + return _EINVAL; + } + + boot_info = &tcm_dev->boot_info; + + if (IS_APP_FW_MODE(tcm_dev->dev_mode)) { + LOGE("BOOT_CONFIG not available in app fw mode %d\n", + tcm_dev->dev_mode); + return _EINVAL; + } + + temp = VALUE(boot_info->boot_config_start_block); + addr = temp * reflash_data->write_block_size; + length = BOOT_CONFIG_SIZE * BOOT_CONFIG_SLOTS; + + if (addr == 0 || length == 0) { + LOGE("BOOT_CONFIG data area unavailable\n"); + retval = _EINVAL; + goto exit; + } + + LOGD("BOOT_CONFIG address: 0x%x, length: %d\n", addr, length); + + retval = syna_tcm_buf_alloc(rd_data, length); + if (retval < 0) { + LOGE("Fail to allocate memory for rd_data buffer\n"); + goto exit; + } + + retval = syna_tcm_read_flash(tcm_dev, addr, rd_data->buf, + length, rd_delay_us); + if (retval < 0) { + LOGE("Fail to read BOOT_CONFIG area (addr: 0x%x, length: %d)\n", + addr, length); + goto exit; + } + + rd_data->data_length = length; + +exit: + return retval; +} + +/** + * syna_tcm_read_flash_app_config() + * + * Read the data of app config area in the flash memory. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] reflash_data: data blob for reflash + * [out] rd_data: buffer used for storing the retrieved data + * [ in] rd_delay_us: delay time to access data in flash memory + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_read_flash_app_config(struct tcm_dev *tcm_dev, + struct tcm_reflash_data_blob *reflash_data, + struct tcm_buffer *rd_data, unsigned int rd_delay_us) +{ + int retval; + unsigned int temp; + unsigned int addr = 0; + unsigned int length = 0; + struct tcm_application_info *app_info; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if (!reflash_data) { + LOGE("Invalid reflash data blob\n"); + return _EINVAL; + } + + if (!rd_data) { + LOGE("Invalid read data buffer\n"); + return _EINVAL; + } + + app_info = &tcm_dev->app_info; + + if (IS_APP_FW_MODE(tcm_dev->dev_mode)) { + LOGE("APP_CONFIG not available in app fw mode %d\n", + tcm_dev->dev_mode); + return _EINVAL; + } + + temp = VALUE(app_info->app_config_start_write_block); + addr = temp * reflash_data->write_block_size; + length = VALUE(app_info->app_config_size); + + if (addr == 0 || length == 0) { + LOGE("APP_CONFIG data area unavailable\n"); + retval = _EINVAL; + goto exit; + } + + LOGD("APP_CONFIG address: 0x%x, length: %d\n", addr, length); + + retval = syna_tcm_buf_alloc(rd_data, length); + if (retval < 0) { + LOGE("Fail to allocate memory for rd_data buffer\n"); + goto exit; + } + + retval = syna_tcm_read_flash(tcm_dev, addr, rd_data->buf, + length, rd_delay_us); + if (retval < 0) { + LOGE("Fail to read APP_CONFIG area (addr: 0x%x, length: %d)\n", + addr, length); + goto exit; + } + + rd_data->data_length = length; + +exit: + return retval; +} + +/** + * syna_tcm_read_flash_disp_config() + * + * Read the data of display config area in the flash memory. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] reflash_data: data blob for reflash + * [out] rd_data: buffer used for storing the retrieved data + * [ in] rd_delay_us: delay time to access data in flash memory + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_read_flash_disp_config(struct tcm_dev *tcm_dev, + struct tcm_reflash_data_blob *reflash_data, + struct tcm_buffer *rd_data, unsigned int rd_delay_us) +{ + int retval; + unsigned int temp; + unsigned int addr = 0; + unsigned int length = 0; + struct tcm_boot_info *boot_info; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if (!reflash_data) { + LOGE("Invalid reflash data blob\n"); + return _EINVAL; + } + + if (!rd_data) { + LOGE("Invalid read data buffer\n"); + return _EINVAL; + } + + boot_info = &tcm_dev->boot_info; + + if (IS_APP_FW_MODE(tcm_dev->dev_mode)) { + LOGE("DISP_CONFIG not available in app fw mode %d\n", + tcm_dev->dev_mode); + return _EINVAL; + } + + temp = VALUE(boot_info->display_config_start_block); + addr = temp * reflash_data->write_block_size; + temp = VALUE(boot_info->display_config_length_blocks); + length = temp * reflash_data->write_block_size; + + if (addr == 0 || length == 0) { + LOGE("DISP_CONFIG data area unavailable\n"); + retval = _EINVAL; + goto exit; + } + + LOGD("DISP_CONFIG address: 0x%x, length: %d\n", addr, length); + + retval = syna_tcm_buf_alloc(rd_data, length); + if (retval < 0) { + LOGE("Fail to allocate memory for rd_data buffer\n"); + goto exit; + } + + retval = syna_tcm_read_flash(tcm_dev, addr, rd_data->buf, + length, rd_delay_us); + if (retval < 0) { + LOGE("Fail to read DISP_CONFIG area (addr: 0x%x, length: %d)\n", + addr, length); + goto exit; + } + + rd_data->data_length = length; + +exit: + return retval; +} + +/** + * syna_tcm_read_flash_custom_otp() + * + * Read the data of custom OTP area in the flash memory. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] reflash_data: data blob for reflash + * [out] rd_data: buffer used for storing the retrieved data + * [ in] rd_delay_us: delay time to access data in flash memory + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_read_flash_custom_otp(struct tcm_dev *tcm_dev, + struct tcm_reflash_data_blob *reflash_data, + struct tcm_buffer *rd_data, unsigned int rd_delay_us) +{ + int retval; + unsigned int temp; + unsigned int addr = 0; + unsigned int length = 0; + struct tcm_boot_info *boot_info; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if (!reflash_data) { + LOGE("Invalid reflash data blob\n"); + return _EINVAL; + } + + if (!rd_data) { + LOGE("Invalid read data buffer\n"); + return _EINVAL; + } + + boot_info = &tcm_dev->boot_info; + + if (IS_APP_FW_MODE(tcm_dev->dev_mode)) { + LOGE("CUSTOM_OTP not available in app fw mode %d\n", + tcm_dev->dev_mode); + return _EINVAL; + } + + temp = VALUE(boot_info->custom_otp_start_block); + addr = temp * reflash_data->write_block_size; + temp = VALUE(boot_info->custom_otp_length_blocks); + length = temp * reflash_data->write_block_size; + + if (addr == 0 || length == 0) { + LOGE("CUSTOM_OTP data area unavailable\n"); + retval = _EINVAL; + goto exit; + } + + LOGD("CUSTOM_OTP address: 0x%x, length: %d\n", addr, length); + + retval = syna_tcm_buf_alloc(rd_data, length); + if (retval < 0) { + LOGE("Fail to allocate memory for rd_data buffer\n"); + goto exit; + } + + retval = syna_tcm_read_flash(tcm_dev, addr, rd_data->buf, + length, rd_delay_us); + if (retval < 0) { + LOGE("Fail to read CUSTOM_OTP area (addr: 0x%x, length: %d)\n", + addr, length); + goto exit; + } + + rd_data->data_length = length; + +exit: + return retval; +} + +/** + * syna_tcm_read_flash_custom_data() + * + * Read the data of custom data in the flash memory. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] reflash_data: data blob for reflash + * [ in] address: address generated by get_flash_data_location() + * [ in] size: size generated by get_flash_data_location() + * [out] rd_data: buffer used for storing the retrieved data + * [ in] rd_delay_us: delay time to access data in flash memory + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_read_flash_custom_data(struct tcm_dev *tcm_dev, + struct tcm_reflash_data_blob *reflash_data, + unsigned int address, unsigned int size, + struct tcm_buffer *rd_data, unsigned int rd_delay_us) +{ + int retval; + unsigned int addr = 0; + unsigned int length = 0; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if (!reflash_data) { + LOGE("Invalid reflash data blob\n"); + return _EINVAL; + } + + if (!rd_data) { + LOGE("Invalid read data buffer\n"); + return _EINVAL; + } + + if (IS_APP_FW_MODE(tcm_dev->dev_mode)) { + LOGE("Custom data not available in app fw mode %d\n", + tcm_dev->dev_mode); + return _EINVAL; + } + + addr = address * reflash_data->write_block_size; + length = size * reflash_data->write_block_size; + + if (addr == 0 || length == 0) { + LOGE("Custom data area unavailable\n"); + retval = _EINVAL; + goto exit; + } + + LOGD("Custom data address: 0x%x, length: %d\n", addr, length); + + retval = syna_tcm_buf_alloc(rd_data, length); + if (retval < 0) { + LOGE("Fail to allocate memory for rd_data buffer\n"); + goto exit; + } + + retval = syna_tcm_read_flash(tcm_dev, addr, rd_data->buf, + length, rd_delay_us); + if (retval < 0) { + LOGE("Fail to read custom data (addr: 0x%x, length: %d)\n", + addr, length); + goto exit; + } + + rd_data->data_length = length; + +exit: + return retval; +} +/** + * syna_tcm_read_flash_area() + * + * Entry function to read in the data of specific area in the flash memory. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] area: flash area to read + * [out] data: buffer storing the retrieved data + * [ in] rd_delay_us: delay time in micro-sec to read words data from flash. + * set 'DEFAULT_FLASH_READ_DELAY' to use default, + * which is 10 us; + * set '0' or 'RESP_IN_ATTN' to select ATTN-driven. + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_read_flash_area(struct tcm_dev *tcm_dev, + enum flash_area area, struct tcm_buffer *rd_data, + unsigned int rd_delay_us) +{ + int retval; + unsigned int addr = 0; + unsigned int length = 0; + struct tcm_reflash_data_blob reflash_data; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if (!rd_data) { + LOGE("Invalid data buffer\n"); + return _EINVAL; + } + + switch (area) { + case AREA_CUSTOM_LCM: + case AREA_CUSTOM_OEM: + case AREA_PPDT: + case AREA_FORCE_TUNING: + case AREA_OPEN_SHORT_TUNING: + retval = syna_tcm_get_flash_data_location(tcm_dev, + area, &addr, &length); + if (retval < 0) { + LOGE("Fail to get data location of 0x%x\n", area); + return retval; + } + break; + default: + break; + } + + retval = syna_tcm_set_up_flash_access(tcm_dev, + &reflash_data); + if (retval < 0) { + LOGE("Fail to set up flash access\n"); + return retval; + } + + syna_tcm_buf_init(&reflash_data.out); + + switch (area) { + case AREA_BOOT_CONFIG: + retval = syna_tcm_read_flash_boot_config(tcm_dev, + &reflash_data, rd_data, rd_delay_us); + if (retval < 0) { + LOGE("Fail to get boot config data\n"); + goto exit; + } + break; + case AREA_APP_CONFIG: + retval = syna_tcm_read_flash_app_config(tcm_dev, + &reflash_data, rd_data, rd_delay_us); + if (retval < 0) { + LOGE("Fail to get app config data\n"); + goto exit; + } + break; + case AREA_DISP_CONFIG: + retval = syna_tcm_read_flash_disp_config(tcm_dev, + &reflash_data, rd_data, rd_delay_us); + if (retval < 0) { + LOGE("Fail to get disp config data\n"); + goto exit; + } + break; + case AREA_CUSTOM_OTP: + retval = syna_tcm_read_flash_custom_otp(tcm_dev, + &reflash_data, rd_data, rd_delay_us); + if (retval < 0) { + LOGE("Fail to get custom otp data\n"); + goto exit; + } + break; + case AREA_CUSTOM_LCM: + case AREA_CUSTOM_OEM: + case AREA_PPDT: + case AREA_FORCE_TUNING: + case AREA_OPEN_SHORT_TUNING: + retval = syna_tcm_read_flash_custom_data(tcm_dev, + &reflash_data, addr, length, rd_data, + rd_delay_us); + break; + default: + LOGE("Invalid data area\n"); + retval = _EINVAL; + goto exit; + } + + LOGI("%s read\n", AREA_ID_STR(area)); + + retval = 0; + +exit: + retval = syna_tcm_switch_fw_mode(tcm_dev, + MODE_APPLICATION_FIRMWARE, + FW_MODE_SWITCH_DELAY_MS); + if (retval < 0) + LOGE("Fail to go back to application firmware\n"); + + syna_tcm_buf_release(&reflash_data.out); + + return retval; +} + +/** + * syna_tcm_write_flash() + * + * Implement the bootloader command to write specified data to flash memory. + * + * If the length of the data to write is not an integer multiple of words, + * the trailing byte will be discarded. If the length of the data to write + * is not an integer number of write blocks, it will be zero-padded to the + * next write block. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] reflash_data: data blob for reflash + * [ in] address: the address in flash memory to write + * [ in] wr_data: data to write + * [ in] wr_len: length of data to write + * [ in] wr_delay_ms: a short delay after the command executed + * set 'DEFAULT_FLASH_WRITE_DELAY' to use default, + * which is 20 us; + * set '0' or 'RESP_IN_ATTN' to select ATTN-driven. + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_write_flash(struct tcm_dev *tcm_dev, + struct tcm_reflash_data_blob *reflash_data, + unsigned int address, const unsigned char *wr_data, + unsigned int wr_len, unsigned int wr_delay_ms) +{ + int retval; + unsigned int offset; + unsigned int w_length; + unsigned int xfer_length; + unsigned int remaining_length; + unsigned int flash_address; + unsigned int block_address; + unsigned int delay_ms; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + w_length = tcm_dev->max_wr_size - 8; + + w_length = w_length - (w_length % reflash_data->write_block_size); + + w_length = MIN(w_length, reflash_data->max_write_payload_size); + + offset = 0; + + remaining_length = wr_len; + + syna_tcm_buf_lock(&reflash_data->out); + + while (remaining_length) { + if (remaining_length > w_length) + xfer_length = w_length; + else + xfer_length = remaining_length; + + retval = syna_tcm_buf_alloc(&reflash_data->out, + xfer_length + 2); + if (retval < 0) { + LOGE("Fail to allocate memory for buf.out\n"); + syna_tcm_buf_unlock(&reflash_data->out); + return retval; + } + + flash_address = address + offset; + block_address = flash_address / reflash_data->write_block_size; + reflash_data->out.buf[0] = (unsigned char)block_address; + reflash_data->out.buf[1] = (unsigned char)(block_address >> 8); + + retval = syna_pal_mem_cpy(&reflash_data->out.buf[2], + reflash_data->out.buf_size - 2, + &wr_data[offset], + wr_len - offset, + xfer_length); + if (retval < 0) { + LOGE("Fail to copy write data ,size: %d\n", + xfer_length); + syna_tcm_buf_unlock(&reflash_data->out); + return retval; + } + + if (wr_delay_ms == DEFAULT_FLASH_WRITE_DELAY) + delay_ms = FLASH_WRITE_DELAY_MS; + else + delay_ms = wr_delay_ms; + + if (delay_ms == RESP_IN_ATTN) { + LOGD("xfer: %d, delay: ATTN-driven\n", xfer_length); + } else { + delay_ms = (delay_ms * xfer_length) / 1000; + LOGD("xfer: %d, delay: %d\n", xfer_length, delay_ms); + } + + retval = syna_tcm_reflash_send_command(tcm_dev, + CMD_WRITE_FLASH, + reflash_data->out.buf, + xfer_length + 2, + delay_ms); + if (retval < 0) { + LOGE("Fail to write data to flash addr 0x%x, size %d\n", + flash_address, xfer_length + 2); + syna_tcm_buf_unlock(&reflash_data->out); + return retval; + } + + offset += xfer_length; + remaining_length -= xfer_length; + } + + syna_tcm_buf_unlock(&reflash_data->out); + + return 0; +} + +/** + * syna_tcm_write_flash_block() + * + * Write data to the target block data area in the flash memory. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] reflash_data: data blob for reflash + * [ in] area: target block area to write + * [ in] wr_delay_us: delay time in micro-sec to write block data to flash. + * set 'DEFAULT_FLASH_WRITE_DELAY' to use default, + * which is 20 ms; + * set '0' or 'RESP_IN_ATTN' to select ATTN-driven. + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_write_flash_block(struct tcm_dev *tcm_dev, + struct tcm_reflash_data_blob *reflash_data, + struct block_data *block, unsigned int wr_delay_us) +{ + int retval; + unsigned int size; + unsigned int flash_addr; + const unsigned char *data; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if (!reflash_data) { + LOGE("Invalid reflash data blob\n"); + return _EINVAL; + } + + if (!block) { + LOGE("Invalid block data\n"); + return _EINVAL; + } + + data = block->data; + size = block->size; + flash_addr = block->flash_addr; + + LOGD("Write data to %s - address: 0x%x, size: %d\n", + AREA_ID_STR(block->id), flash_addr, size); + + if (size == 0) { + LOGI("No need to update, size = %d\n", size); + goto exit; + } + + retval = syna_tcm_write_flash(tcm_dev, reflash_data, + flash_addr, data, size, wr_delay_us); + if (retval < 0) { + LOGE("Fail to write %s to flash (addr: 0x%x, size: %d)\n", + AREA_ID_STR(block->id), flash_addr, size); + return retval; + } + +exit: + LOGN("%s area written\n", AREA_ID_STR(block->id)); + + return 0; +} + +/** + * syna_tcm_erase_flash() + * + * Implement the bootloader command, which is used to erase the specified + * blocks of flash memory. + * + * Until this command completes, the device may be unresponsive. + * Therefore, this helper is implemented as a blocked function, and the delay + * time is set to 200 ms in default. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] reflash_data: data blob for reflash + * [ in] address: the address in flash memory to read + * [ in] size: size of data to write + * [ in] erase_delay_ms: the delay time to get the resp from mass erase + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_erase_flash(struct tcm_dev *tcm_dev, + struct tcm_reflash_data_blob *reflash_data, + unsigned int address, unsigned int size, + unsigned int erase_delay_ms) +{ + int retval; + unsigned int page_start = 0; + unsigned int page_count = 0; + unsigned char out_buf[4] = {0}; + int size_erase_cmd; + + page_start = address / reflash_data->page_size; + + page_count = syna_pal_ceil_div(size, reflash_data->page_size); + + LOGD("Page start = %d (0x%04x), Page count = %d (0x%04x)\n", + page_start, page_start, page_count, page_count); + + if ((page_start > 0xff) || (page_count > 0xff)) { + size_erase_cmd = 4; + + out_buf[0] = (unsigned char)(page_start & 0xff); + out_buf[1] = (unsigned char)((page_start >> 8) & 0xff); + out_buf[2] = (unsigned char)(page_count & 0xff); + out_buf[3] = (unsigned char)((page_count >> 8) & 0xff); + } else { + size_erase_cmd = 2; + + out_buf[0] = (unsigned char)(page_start & 0xff); + out_buf[1] = (unsigned char)(page_count & 0xff); + } + + retval = syna_tcm_reflash_send_command(tcm_dev, + CMD_ERASE_FLASH, + out_buf, + size_erase_cmd, + erase_delay_ms); + if (retval < 0) { + LOGE("Fail to erase data at flash page 0x%x, count %d\n", + page_start, page_count); + return retval; + } + + return 0; +} + +/** + * syna_tcm_erase_flash_block() + * + * Mass erase the target block data area in the flash memory. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] reflash_data: data blob for reflash + * [ in] block: target block area to erase + * [ in] delay_ms: a short delay after the erase command executed + * set 'DEFAULT_FLASH_ERASE_DELAY' to use default, + * which is 500 ms; + * set '0' or 'RESP_IN_ATTN' to select ATTN-driven. + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_erase_flash_block(struct tcm_dev *tcm_dev, + struct tcm_reflash_data_blob *reflash_data, + struct block_data *block, unsigned int delay_ms) +{ + int retval; + unsigned int size; + unsigned int flash_addr; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if (!reflash_data) { + LOGE("Invalid reflash data blob\n"); + return _EINVAL; + } + + if (!block) { + LOGE("Invalid block data\n"); + return _EINVAL; + } + + flash_addr = block->flash_addr; + + size = block->size; + + if (delay_ms == DEFAULT_FLASH_ERASE_DELAY) + delay_ms = FLASH_ERASE_DELAY_MS; + + LOGD("Erase %s block - address: 0x%x, size: %d\n", + AREA_ID_STR(block->id), flash_addr, size); + + if (size == 0) { + LOGI("No need to erase, size = %d\n", size); + goto exit; + } + + retval = syna_tcm_erase_flash(tcm_dev, reflash_data, + flash_addr, size, delay_ms); + if (retval < 0) { + LOGE("Fail to erase %s data (addr: 0x%x, size: %d)\n", + AREA_ID_STR(block->id), flash_addr, size); + return retval; + } + +exit: + LOGN("%s area erased\n", AREA_ID_STR(block->id)); + + return 0; +} + +/** + * syna_tcm_update_flash_block() + * + * Perform the reflash sequence to the target area + * + * @param + * [ in] tcm_dev: the device handle + * [ in] reflash_data: data blob for reflash + * [ in] block: target block area to update + * [ in] delay_ms: a short delay time in millisecond to wait for + * the completion of flash access + * for polling, set a value formatted with + * [erase | write]; + * for ATTN-driven, set a '0' or 'RESP_IN_ATTN' + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_update_flash_block(struct tcm_dev *tcm_dev, + struct tcm_reflash_data_blob *reflash_data, + struct block_data *block, unsigned int delay_ms) +{ + int retval; + unsigned int erase_delay_ms = (delay_ms >> 16) & 0xFFFF; + unsigned int wr_blk_delay_ms = delay_ms & 0xFFFF; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if (!reflash_data) { + LOGE("Invalid reflash data blob\n"); + return _EINVAL; + } + + if (!block) { + LOGE("Invalid block data\n"); + return _EINVAL; + } + + /* reflash is not needed for the partition */ + retval = syna_tcm_check_flash_block(tcm_dev, + reflash_data, + block); + if (retval < 0) { + LOGE("Invalid %s area\n", AREA_ID_STR(block->id)); + return retval; + } + + if (retval == DO_NONE) + return 0; + + LOGN("Prepare to erase %s area\n", AREA_ID_STR(block->id)); + + retval = syna_tcm_erase_flash_block(tcm_dev, + reflash_data, + block, + erase_delay_ms); + if (retval < 0) { + LOGE("Fail to erase %s area\n", AREA_ID_STR(block->id)); + return retval; + } + + LOGN("Prepare to update %s area\n", AREA_ID_STR(block->id)); + + retval = syna_tcm_write_flash_block(tcm_dev, + reflash_data, + block, + wr_blk_delay_ms); + if (retval < 0) { + LOGE("Fail to write %s area\n", AREA_ID_STR(block->id)); + return retval; + } + + return 0; +} + +/** + * syna_tcm_do_reflash_tddi() + * + * Implement the sequence specific for MODE_TDDI_BOOTLOADER. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] reflash_data: misc. data used for fw update + * [ in] type: the area to update + * [ in] delay_ms: a short delay time in millisecond to wait for + * the completion of flash access + * for polling, set a value formatted with + * [erase | write]; + * for ATTN-driven, set a '0' or 'RESP_IN_ATTN' + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_do_reflash_tddi(struct tcm_dev *tcm_dev, + struct tcm_reflash_data_blob *reflash_data, + enum update_area type, unsigned int delay_ms) +{ + int retval = 0; + int idx; + unsigned int erase_delay_ms = (delay_ms >> 16) & 0xFFFF; + unsigned int wr_blk_delay_ms = delay_ms & 0xFFFF; + struct image_info *image_info; + struct block_data *block; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if (!reflash_data) { + LOGE("Invalid reflash_data blob\n"); + return _EINVAL; + } + + image_info = &reflash_data->image_info; + + if (tcm_dev->dev_mode != MODE_TDDI_BOOTLOADER) { + LOGE("Incorrect bootloader mode, 0x%02x, expected: 0x%02x\n", + tcm_dev->dev_mode, MODE_TDDI_BOOTLOADER); + return _EINVAL; + } + + if (type == UPDATE_NONE) + goto exit; + + /* Always mass erase all blocks, before writing the data */ + for (idx = 0; idx < AREA_MAX; idx++) { + + block = &image_info->data[idx]; + + if (!block->available) + continue; + + retval = syna_tcm_check_flash_block(tcm_dev, + reflash_data, + block); + if (retval == DO_NONE) + continue; + + LOGN("Prepare to erase %s area\n", AREA_ID_STR(block->id)); + + retval = syna_tcm_erase_flash_block(tcm_dev, + reflash_data, + block, + erase_delay_ms); + if (retval < 0) { + LOGE("Fail to erase %s area\n", AREA_ID_STR(block->id)); + goto exit; + } + } + + /* Write all the data to flash */ + for (idx = 0; idx < AREA_MAX; idx++) { + + block = &image_info->data[idx]; + + if (!block->available) + continue; + + retval = syna_tcm_check_flash_block(tcm_dev, + reflash_data, + block); + if (retval == DO_NONE) + continue; + + LOGN("Prepare to update %s area\n", AREA_ID_STR(block->id)); + + retval = syna_tcm_write_flash_block(tcm_dev, + reflash_data, + block, + wr_blk_delay_ms); + if (retval < 0) { + LOGE("Fail to update %s area\n", + AREA_ID_STR(block->id)); + goto exit; + } + } + +exit: + return retval; +} + +/** + * syna_tcm_do_reflash_generic() + * + * Implement the generic sequence of fw update in MODE_BOOTLOADER. + * + * Typically, it is applied on most of discrete touch controllers + * + * @param + * [ in] tcm_dev: the device handle + * [ in] reflash_data: misc. data used for fw update + * [ in] type: the area to update + * [ in] wait_delay_ms: a short delay time in millisecond to wait for + * the completion of flash access + * for polling, set a value formatted with + * [erase | write]; + * for ATTN-driven, set a '0' or 'RESP_IN_ATTN' + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_do_reflash_generic(struct tcm_dev *tcm_dev, + struct tcm_reflash_data_blob *reflash_data, + enum update_area type, unsigned int wait_delay_ms) +{ + int retval = 0; + struct block_data *block; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if (!reflash_data) { + LOGE("Invalid reflash_data blob\n"); + return _EINVAL; + } + + if (tcm_dev->dev_mode != MODE_BOOTLOADER) { + LOGE("Incorrect bootloader mode, 0x%02x, expected: 0x%02x\n", + tcm_dev->dev_mode, MODE_BOOTLOADER); + return _EINVAL; + } + + switch (type) { + case UPDATE_FIRMWARE_CONFIG: + block = &reflash_data->image_info.data[AREA_APP_CODE]; + + retval = syna_tcm_update_flash_block(tcm_dev, + reflash_data, + block, + wait_delay_ms); + if (retval < 0) { + LOGE("Fail to update application firmware\n"); + goto exit; + } + case UPDATE_CONFIG_ONLY: + block = &reflash_data->image_info.data[AREA_APP_CONFIG]; + + retval = syna_tcm_update_flash_block(tcm_dev, + reflash_data, + block, + wait_delay_ms); + if (retval < 0) { + LOGE("Fail to update application config\n"); + goto exit; + } + break; + case UPDATE_NONE: + default: + break; + } +exit: + return retval; +} + +/** + * syna_tcm_do_fw_update() + * + * The entry function to perform fw update upon TouchBoot. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] image: binary data to write + * [ in] image_size: size of data array + * [ in] wait_delay_ms: a short delay time in millisecond to wait for + * the completion of flash access + * for polling, set a value formatted with + * [erase | write]; + * for ATTN-driven, set a '0' or 'RESP_IN_ATTN' + * [ in] force_reflash: '1' to do reflash anyway + * '0' to compare ID info before doing reflash. + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_do_fw_update(struct tcm_dev *tcm_dev, + const unsigned char *image, unsigned int image_size, + unsigned int wait_delay_ms, bool force_reflash) +{ + int retval; + enum update_area type = UPDATE_NONE; + struct tcm_reflash_data_blob reflash_data; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if ((!image) || (image_size == 0)) { + LOGE("Invalid image data\n"); + return _EINVAL; + } + + LOGN("Prepare to do reflash\n"); + + syna_tcm_buf_init(&reflash_data.out); + + reflash_data.image = image; + reflash_data.image_size = image_size; + syna_pal_mem_set(&reflash_data.image_info, 0x00, + sizeof(struct image_info)); + + retval = syna_tcm_parse_fw_image(image, &reflash_data.image_info); + if (retval < 0) { + LOGE("Fail to parse firmware image\n"); + return retval; + } + + LOGN("Start of reflash\n"); + + ATOMIC_SET(tcm_dev->firmware_flashing, 1); + + if (force_reflash) { + type = UPDATE_FIRMWARE_CONFIG; + goto reflash; + } + + type = (enum update_area)syna_tcm_compare_image_id_info(tcm_dev, + &reflash_data); + + if (type == UPDATE_NONE) + goto exit; + +reflash: + syna_tcm_buf_init(&reflash_data.out); + + /* set up flash access, and enter the bootloader mode */ + retval = syna_tcm_set_up_flash_access(tcm_dev, &reflash_data); + if (retval < 0) { + LOGE("Fail to set up flash access\n"); + goto exit; + } + + /* perform the fw update */ + if (tcm_dev->dev_mode == MODE_BOOTLOADER) { + retval = syna_tcm_do_reflash_generic(tcm_dev, + &reflash_data, + type, + wait_delay_ms); + } else if (tcm_dev->dev_mode == MODE_TDDI_BOOTLOADER) { + retval = syna_tcm_do_reflash_tddi(tcm_dev, + &reflash_data, + type, + wait_delay_ms); + } else { + LOGE("Incorrect bootloader mode, 0x%02x\n", + tcm_dev->dev_mode); + goto reset; + } + + if (retval < 0) { + LOGE("Fail to do firmware update\n"); + goto reset; + } + + LOGN("End of reflash\n"); + + retval = 0; +reset: + retval = syna_tcm_reset(tcm_dev); + if (retval < 0) { + LOGE("Fail to do reset\n"); + goto exit; + } + +exit: + ATOMIC_SET(tcm_dev->firmware_flashing, 0); + + syna_tcm_buf_release(&reflash_data.out); + + return retval; +} + diff --git a/tcm/synaptics_touchcom_func_reflash.h b/tcm/synaptics_touchcom_func_reflash.h new file mode 100644 index 0000000..8e90b80 --- /dev/null +++ b/tcm/synaptics_touchcom_func_reflash.h @@ -0,0 +1,145 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Synaptics TouchCom touchscreen driver + * + * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +/** + * @file synaptics_touchcom_func_reflash.h + * + * This file declares relevant functions and structures for TouchBoot. + */ + +#ifndef _SYNAPTICS_TOUCHCOM_REFLASH_FUNCS_H_ +#define _SYNAPTICS_TOUCHCOM_REFLASH_FUNCS_H_ + +#include "synaptics_touchcom_core_dev.h" +#include "synaptics_touchcom_func_base_flash.h" + +/** + * @section: Blocks to be updated + */ +enum update_area { + UPDATE_NONE = 0, + UPDATE_FIRMWARE_CONFIG, + UPDATE_CONFIG_ONLY, + UPDATE_ALL_BLOCKS, +}; + +/** + * @section: Data Type in flash memory + */ +enum flash_data { + FLASH_LCM_DATA = 1, + FLASH_OEM_DATA, + FLASH_PPDT_DATA, + FLASH_FORCE_CALIB_DATA, + FLASH_OPEN_SHORT_TUNING_DATA, +}; + +/** + * @section: Specific data blob for reflash + * + * The structure contains various parameters being used in reflash + */ +struct tcm_reflash_data_blob { + /* binary data of an image file */ + const unsigned char *image; + unsigned int image_size; + /* parsed data based on given image file */ + struct image_info image_info; + /* standard information for flash access */ + unsigned int page_size; + unsigned int write_block_size; + unsigned int max_write_payload_size; + /* temporary buffer during the reflash */ + struct tcm_buffer out; +}; + +/** + * syna_tcm_compare_image_id_info() + * + * Compare the ID information between device and the image file, + * and determine the area to be updated. + * The function should be called after parsing the image file. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] reflash_data: data blob for reflash + * + * @return + * Comparison result + */ +int syna_tcm_compare_image_id_info(struct tcm_dev *tcm_dev, + struct tcm_reflash_data_blob *reflash_data); + +/** + * syna_tcm_read_flash_area() + * + * Entry function to read in the data of specific area in the flash memory. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] area: flash area to read + * [out] data: buffer storing the retrieved data + * [ in] rd_delay_us: delay time in micro-sec to read words data from flash. + * set '0' to use default time, which is 10 us; + * set 'FORCE_ATTN_DRIVEN' to adopt ATTN-driven. + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_read_flash_area(struct tcm_dev *tcm_dev, + enum flash_area area, struct tcm_buffer *rd_data, + unsigned int rd_delay_us); + +/** + * syna_tcm_do_fw_update() + * + * The entry function to perform fw update upon TouchBoot. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] image: image file given + * [ in] image_size: size of data array + * [ in] wait_delay_ms: a short delay time in millisecond to wait for + * the completion of flash access + * for polling, set a value formatted with + * [erase | write]; + * for ATTN-driven, set a '0' or 'RESP_IN_ATTN' + * [ in] force_reflash: '1' to do reflash anyway + * '0' to compare ID info before doing reflash. + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_do_fw_update(struct tcm_dev *tcm_dev, + const unsigned char *image, unsigned int image_size, + unsigned int wait_delay_ms, bool force_reflash); + +#endif /* end of _SYNAPTICS_TOUCHCOM_REFLASH_FUNCS_H_ */ diff --git a/tcm/synaptics_touchcom_func_romboot.c b/tcm/synaptics_touchcom_func_romboot.c new file mode 100644 index 0000000..e144fa3 --- /dev/null +++ b/tcm/synaptics_touchcom_func_romboot.c @@ -0,0 +1,1586 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +/** + * @file synaptics_touchcom_func_romboot.c + * + * This file implements the ROM boot-loader related functions. + * The declarations are available in synaptics_touchcom_func_romboot.h. + */ + +#include "synaptics_touchcom_func_base.h" +#include "synaptics_touchcom_func_romboot.h" + +#define JEDEC_STATUS_CHECK_US_MIN 5000 +#define JEDEC_STATUS_CHECK_US_MAX 10000 + +#define BINARY_FILE_MAGIC_VALUE 0xaa55 + +#define ROMBOOT_FLASH_PAGE_SIZE 256 + +/** + * @section: JEDEC flash command set + */ +enum flash_command { + JEDEC_PAGE_PROGRAM = 0x02, + JEDEC_READ_STATUS = 0x05, + JEDEC_WRITE_ENABLE = 0x06, + JEDEC_CHIP_ERASE = 0xc7, +}; + + +/** + * syna_tcm_romboot_send_command() + * + * Helper to send a packet to ROM bootloader. + * + * Please be noted that the given packet must be formatted into the + * specific structure in order to communicate with flash. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] out: data sent out, if any + * [ in] out_size: size of data sent out + * [ in] in: buffer to store the data read in + * [ in] in_size: size of data read in + * [ in] delay_ms_resp: delay time to get the response + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_romboot_send_command(struct tcm_dev *tcm_dev, + unsigned char *out, unsigned int out_size, unsigned char *in, + unsigned int in_size, unsigned int delay_ms_resp) +{ + int retval; + unsigned char resp_code; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if (out_size < sizeof(struct flash_param)) { + LOGE("Invalid size of out data, %d, min. size:%d\n", + out_size, (int)sizeof(struct flash_param)); + return _EINVAL; + } + + retval = tcm_dev->write_message(tcm_dev, + CMD_SPI_MASTER_WRITE_THEN_READ_EXTENDED, + out, + out_size, + &resp_code, + delay_ms_resp); + if (retval < 0) { + LOGE("Fail to send romboot flash command 0x%02x\n", + CMD_SPI_MASTER_WRITE_THEN_READ_EXTENDED); + goto exit; + } + + LOGD("resp_code: 0x%x, resp length: %d\n", + resp_code, tcm_dev->resp_buf.data_length); + + if ((in == NULL) || (in_size < tcm_dev->resp_buf.data_length)) + goto exit; + + /* copy resp data to caller */ + retval = syna_pal_mem_cpy((unsigned char *)in, + in_size, + tcm_dev->resp_buf.buf, + tcm_dev->resp_buf.buf_size, + tcm_dev->resp_buf.data_length); + if (retval < 0) { + LOGE("Fail to copy resp data to caller\n"); + goto exit; + } + +exit: + return retval; +} + + +/** + * syna_tcm_romboot_multichip_send_command() + * + * Send a command code to the ROM bootloader inside the multi-chip device + * + * @param + * [ in] tcm_dev: the device handle + * [ in] command: command to send + * [ in] out: additional data sent out, if any + * [ in] out_size: size of data sent out + * [ in] in: buffer to store the data read in + * [ in] in_size: size of data read in + * [ in] delay_ms: delay time to get the response + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_romboot_multichip_send_command(struct tcm_dev *tcm_dev, + unsigned char command, unsigned char *out, + unsigned int out_size, unsigned char *in, + unsigned int in_size, unsigned int delay_ms) +{ + int retval; + unsigned char *payld_buf = NULL; + unsigned int payld_size; + struct flash_param flash_param; + unsigned int offset = (int)sizeof(struct flash_param); + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + syna_pal_mem_set((void *)&flash_param, 0x00, sizeof(flash_param)); + + flash_param.read_size[0] = (unsigned char)(in_size & 0xff); + flash_param.read_size[1] = (unsigned char)(in_size >> 8) & 0xff; + + flash_param.command = command; + + payld_size = offset + out_size; + if (flash_param.command != 0x00) + payld_size += 2; + + LOGD("Command: 0x%02x, packet size: %d, wr:%d, rd:%d\n", + command, payld_size, out_size, in_size); + + payld_buf = syna_pal_mem_alloc(payld_size, sizeof(unsigned char)); + if (!payld_buf) { + LOGE("Fail to allocate buffer to store flash command\n"); + return _ENOMEM; + } + + if (flash_param.command != 0x00) { + payld_buf[offset] = (unsigned char)out_size; + payld_buf[offset + 1] = (unsigned char)(out_size >> 8); + + if (out_size > 0) { + retval = syna_pal_mem_cpy(&payld_buf[offset + 2], + payld_size - offset - 2, + out, + out_size, + out_size); + if (retval < 0) { + LOGE("Fail to copy payload to payld_buf\n"); + goto exit; + } + } + + LOGD("Packet: %02x %02x %02x %02x %02x %02x %02x %02x\n", + flash_param.byte0, flash_param.byte1, flash_param.byte2, + flash_param.read_size[0], flash_param.read_size[1], + flash_param.command, payld_buf[offset], + payld_buf[offset + 1]); + } + + retval = syna_pal_mem_cpy(payld_buf, payld_size, + &flash_param, sizeof(flash_param), sizeof(flash_param)); + if (retval < 0) { + LOGE("Fail to copy flash_param header to payld_buf\n"); + goto exit; + } + + retval = syna_tcm_romboot_send_command(tcm_dev, + payld_buf, + payld_size, + in, + in_size, + delay_ms); + if (retval < 0) { + LOGE("Fail to write command 0x%x\n", flash_param.command); + goto exit; + } + +exit: + syna_pal_mem_free((void *)payld_buf); + + return retval; +} +/** + * syna_tcm_romboot_multichip_get_resp() + * + * To get the response data from ROM bootloader inside the multi-chip device + * + * @param + * [ in] tcm_dev: the device handle + * [ in] length: size to read + * [ in] resp: buffer to store the resp data + * [ in] resp_size: size of resp data + * [ in] delay_ms: delay time to get the response + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_romboot_multichip_get_resp(struct tcm_dev *tcm_dev, + unsigned int length, unsigned char *resp, + unsigned int resp_size, unsigned int delay_ms) +{ + int retval; + unsigned char *tmp_buf = NULL; + unsigned int xfer_len; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if (resp && (resp_size < length)) { + LOGE("Invalid buffer size, len:%d, size:%d\n", + length, resp_size); + return _EINVAL; + } + + xfer_len = length + 2; + + tmp_buf = syna_pal_mem_alloc(xfer_len, sizeof(unsigned char)); + if (!tmp_buf) { + LOGE("Fail to allocate tmp_buf\n"); + return _ENOMEM; + } + + retval = syna_tcm_romboot_multichip_send_command(tcm_dev, + CMD_NONE, NULL, 0, + tmp_buf, xfer_len, delay_ms); + if (retval < 0) { + LOGE("Fail to get resp, size: %d\n", xfer_len); + goto exit; + } + + if (resp) { + retval = syna_pal_mem_cpy(resp, resp_size, + &tmp_buf[1], xfer_len - 1, length); + if (retval < 0) { + LOGE("Fail to copy resp data\n"); + goto exit; + } + } + +exit: + syna_pal_mem_free((void *)tmp_buf); + + return retval; +} +/** + * syna_tcm_romboot_multichip_get_status() + * + * To poll the status until the completion + * + * @param + * [ in] tcm_dev: the device handle + * [ in] resp_status: response status returned + * [ in] resp_length: response length returned + * [ in] delay_ms: delay time to get the response + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_romboot_multichip_get_status(struct tcm_dev *tcm_dev, + unsigned char *resp_status, unsigned int *resp_length, + unsigned int delay_ms) +{ + int retval; + unsigned char resp[4] = { 0 }; + int timeout = 0; + int MAX_TIMEOUT = 1000; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + syna_pal_sleep_ms(delay_ms); + + do { + retval = syna_tcm_romboot_multichip_send_command(tcm_dev, + CMD_NONE, NULL, 0, + resp, 3, delay_ms); + if (retval < 0) { + LOGE("Fail to poll the resp\n"); + goto exit; + } + + LOGD("status: %02x %02x %02x\n", resp[0], resp[1], resp[2]); + + if (resp[0] == 0xff) { + syna_pal_sleep_ms(100); + timeout += 100; + continue; + } else if (resp[0] == 0x01) { + *resp_status = resp[0]; + *resp_length = syna_pal_le2_to_uint(&resp[1]); + goto exit; + } else { + LOGE("Invalid resp, %02x %02x %02x\n", + resp[0], resp[1], resp[2]); + retval = _EIO; + goto exit; + } + + } while (timeout < MAX_TIMEOUT); + + if (timeout >= 500) { + LOGE("Timeout to get the status\n"); + retval = _EIO; + } +exit: + return retval; +} +/** + * syna_tcm_romboot_multichip_write_flash() + * + * Write the given binary data to the flash through the ROM bootloader + * inside the multi-chip device + * + * @param + * [ in] tcm_dev: the device handle + * [ in] romboot_data: data blob for romboot + * [ in] address: the address in flash memory to write + * [ in] wr_data: binary data to write + * [ in] wr_len: length of data to write + * [ in] wr_delay_ms: a short delay after the command executed + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_romboot_multichip_write_flash(struct tcm_dev *tcm_dev, + struct tcm_romboot_data_blob *romboot_data, + unsigned int address, const unsigned char *wr_data, + unsigned int wr_len, unsigned int wr_delay_ms) +{ + int retval; + unsigned int offset; + unsigned int w_length; + unsigned int xfer_length; + unsigned int remaining_length; + unsigned int flash_address; + unsigned int block_address; + unsigned char resp_code = 0; + unsigned int resp_length = 0; + unsigned int delay_ms; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + w_length = tcm_dev->max_wr_size - 16; + + w_length = w_length - (w_length % romboot_data->write_block_size); + + w_length = MIN(w_length, romboot_data->max_write_payload_size); + + offset = 0; + + remaining_length = wr_len; + + syna_tcm_buf_lock(&romboot_data->out); + + while (remaining_length) { + if (remaining_length > w_length) + xfer_length = w_length; + else + xfer_length = remaining_length; + + retval = syna_tcm_buf_alloc(&romboot_data->out, + xfer_length + 2); + if (retval < 0) { + LOGE("Fail to allocate memory for buf.out\n"); + goto exit; + } + + flash_address = address + offset; + block_address = flash_address / romboot_data->write_block_size; + + romboot_data->out.buf[0] = (unsigned char)block_address & 0xff; + romboot_data->out.buf[1] = (unsigned char)(block_address >> 8); + + retval = syna_pal_mem_cpy(&romboot_data->out.buf[2], + romboot_data->out.buf_size - 2, + &wr_data[offset], + wr_len - offset, + xfer_length); + if (retval < 0) { + LOGE("Fail to copy write data ,size: %d\n", + xfer_length); + goto exit; + } + + if (wr_delay_ms == 0) + delay_ms = ROMBOOT_DELAY_MS; + else + delay_ms = wr_delay_ms; + + LOGD("write xfer: %d (remaining: %d)\n", + xfer_length, remaining_length); + + retval = syna_tcm_romboot_multichip_send_command(tcm_dev, + CMD_WRITE_FLASH, + romboot_data->out.buf, + xfer_length + 2, + NULL, + 0, + delay_ms); + if (retval < 0) { + LOGE("Fail to write data to flash addr 0x%x, size %d\n", + flash_address, xfer_length + 2); + goto exit; + } + + retval = syna_tcm_romboot_multichip_get_status(tcm_dev, + &resp_code, &resp_length, ROMBOOT_DELAY_MS); + if (retval < 0) { + LOGE("Fail to get the response of command 0x%x\n", + CMD_WRITE_FLASH); + goto exit; + } + + LOGD("status:%02x, data_length:%d\n", resp_code, resp_length); + + if (resp_code != STATUS_OK) { + LOGE("Invalid response of command %x\n", + CMD_WRITE_FLASH); + retval = _EIO; + goto exit; + } + + retval = syna_tcm_romboot_multichip_get_resp(tcm_dev, + resp_length, NULL, 0, ROMBOOT_DELAY_MS); + if (retval < 0) { + LOGE("Fail to get the boot info packet\n"); + goto exit; + } + + offset += xfer_length; + remaining_length -= xfer_length; + } + + retval = 0; + +exit: + syna_tcm_buf_unlock(&romboot_data->out); + + return retval; +} +/** + * syna_tcm_romboot_multichip_erase_flash() + * + * Ask the ROM bootloader to erase the flash inside the multi-chip device + * + * @param + * [ in] tcm_dev: the device handle + * [ in] romboot_data: data blob for remboot + * [ in] address: the address in flash memory to read + * [ in] size: size of data to write + * [ in] erase_delay_ms: the delay time to get the resp from mass erase + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_romboot_multichip_erase_flash(struct tcm_dev *tcm_dev, + struct tcm_romboot_data_blob *romboot_data, + unsigned int address, unsigned int size, + unsigned int erase_delay_ms) +{ + int retval; + unsigned int page_start = 0; + unsigned int page_count = 0; + unsigned char out_buf[4] = { 0 }; + unsigned char resp_code = 0; + unsigned int resp_length = 0; + int size_erase_cmd; + + page_start = address / romboot_data->page_size; + + page_count = syna_pal_ceil_div(size, romboot_data->page_size); + + LOGD("Page start = %d (0x%04x), Page count = %d (0x%04x)\n", + page_start, page_start, page_count, page_count); + + if ((page_start > 0xff) || (page_count > 0xff)) { + size_erase_cmd = 4; + + out_buf[0] = (unsigned char)(page_start & 0xff); + out_buf[1] = (unsigned char)((page_start >> 8) & 0xff); + out_buf[2] = (unsigned char)(page_count & 0xff); + out_buf[3] = (unsigned char)((page_count >> 8) & 0xff); + } else { + size_erase_cmd = 2; + + out_buf[0] = (unsigned char)(page_start & 0xff); + out_buf[1] = (unsigned char)(page_count & 0xff); + } + + retval = syna_tcm_romboot_multichip_send_command(tcm_dev, + CMD_ERASE_FLASH, + out_buf, + size_erase_cmd, + NULL, + 0, + erase_delay_ms); + if (retval < 0) { + LOGE("Fail to erase data at 0x%x (page:0x%x, count:%d)\n", + address, page_start, page_count); + return retval; + } + + retval = syna_tcm_romboot_multichip_get_status(tcm_dev, + &resp_code, &resp_length, ROMBOOT_DELAY_MS); + if (retval < 0) { + LOGE("Fail to get the response of command 0x%x\n", + CMD_ERASE_FLASH); + return retval; + } + + LOGD("status:%02x, data_length:%d\n", resp_code, resp_length); + + if (resp_code != STATUS_OK) { + LOGE("Invalid response of command %x\n", CMD_WRITE_FLASH); + retval = _EIO; + return retval; + } + + return retval; +} + +/** + * syna_tcm_romboot_multichip_get_boot_info() + * + * To request a boot information packet from ROM bootloader inside + * the multi-chip device + * + * @param + * [ in] tcm_dev: the device handle + * [out] boot_info: the boot info packet returned + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_romboot_multichip_get_boot_info(struct tcm_dev *tcm_dev, + struct tcm_boot_info *boot_info) +{ + int retval = 0; + unsigned char resp_code; + unsigned int resp_data_len = 0; + unsigned int copy_size; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + retval = syna_tcm_romboot_multichip_send_command(tcm_dev, + CMD_GET_BOOT_INFO, NULL, 0, NULL, 0, ROMBOOT_DELAY_MS); + if (retval < 0) { + LOGE("Fail to run command 0x%x\n", CMD_GET_BOOT_INFO); + goto exit; + } + + retval = syna_tcm_romboot_multichip_get_status(tcm_dev, + &resp_code, &resp_data_len, ROMBOOT_DELAY_MS); + if (retval < 0) { + LOGE("Fail to get the response of command 0x%x\n", + CMD_GET_BOOT_INFO); + return retval; + } + + LOGD("status:%02x, data_length:%d\n", resp_code, resp_data_len); + + if (resp_code != STATUS_OK) { + LOGE("Invalid response of command %x\n", CMD_GET_BOOT_INFO); + retval = _EIO; + return retval; + } + + if (boot_info == NULL) + goto exit; + + copy_size = MIN(sizeof(struct tcm_boot_info), resp_data_len); + + retval = syna_tcm_romboot_multichip_get_resp(tcm_dev, + copy_size, (unsigned char *)boot_info, + sizeof(struct tcm_boot_info), ROMBOOT_DELAY_MS); + if (retval < 0) { + LOGE("Fail to get the boot info packet\n"); + return retval; + } + +exit: + return retval; +} +/** + * syna_tcm_romboot_preparation() + * + * Perform the preparation before doing firmware update of multi-chip device + * + * @param + * [ in] tcm_dev: the device handle + * [out] romboot_data: data blob for romboot access + * [ in] is_multichip: flag to indicate a multichip DUT + * + * @return + * Result of image file comparison + */ +static int syna_tcm_romboot_preparation(struct tcm_dev *tcm_dev, + struct tcm_romboot_data_blob *romboot_data, bool is_multichip) +{ + int retval; + unsigned int temp; + struct tcm_boot_info *boot_info; + unsigned int wr_chunk; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if (!romboot_data) { + LOGE("Invalid romboot data blob\n"); + return _EINVAL; + } + + LOGI("Set up preparation, multi-chip: %s\n", + (is_multichip)?"yes":"no"); + + retval = syna_tcm_identify(tcm_dev, NULL); + if (retval < 0) { + LOGE("Fail to do identification\n"); + return retval; + } + + /* switch to bootloader mode */ + if (IS_APP_FW_MODE(tcm_dev->dev_mode)) { + LOGI("Prepare to enter bootloader mode\n"); + if (is_multichip) + retval = syna_tcm_switch_fw_mode(tcm_dev, + MODE_MULTICHIP_TDDI_BOOTLOADER, + FW_MODE_SWITCH_DELAY_MS); + else + retval = syna_tcm_switch_fw_mode(tcm_dev, + MODE_TDDI_BOOTLOADER, + FW_MODE_SWITCH_DELAY_MS); + + if (retval < 0) { + LOGE("Fail to enter bootloader mode\n"); + return retval; + } + } + /* switch to rom boot mode */ + if (!IS_ROM_BOOTLOADER_MODE(tcm_dev->dev_mode)) { + LOGI("Prepare to enter rom boot mode\n"); + + retval = syna_tcm_switch_fw_mode(tcm_dev, + MODE_ROMBOOTLOADER, + FW_MODE_SWITCH_DELAY_MS); + if (retval < 0) { + LOGE("Fail to enter rom boot mode\n"); + return retval; + } + } + + if (!IS_ROM_BOOTLOADER_MODE(tcm_dev->dev_mode)) { + LOGE("Device not in romboot mode\n"); + return _EINVAL; + } + + if (!is_multichip) + return 0; + + boot_info = &tcm_dev->boot_info; + + retval = syna_tcm_romboot_multichip_get_boot_info(tcm_dev, + boot_info); + if (retval < 0) { + LOGE("Fail to get boot info\n"); + return retval; + } + + wr_chunk = tcm_dev->max_wr_size; + + temp = boot_info->write_block_size_words; + romboot_data->write_block_size = temp * 2; + + LOGI("Write block size: %d (words size: %d)\n", + romboot_data->write_block_size, temp); + + temp = syna_pal_le2_to_uint(boot_info->erase_page_size_words); + romboot_data->page_size = temp * 2; + + LOGI("Erase page size: %d (words size: %d)\n", + romboot_data->page_size, temp); + + temp = syna_pal_le2_to_uint(boot_info->max_write_payload_size); + romboot_data->max_write_payload_size = temp; + + LOGI("Max write flash data size: %d\n", + romboot_data->max_write_payload_size); + + if (romboot_data->write_block_size > (wr_chunk - 9)) { + LOGE("Write block size, %d, greater than chunk space, %d\n", + romboot_data->write_block_size, (wr_chunk - 9)); + return _EINVAL; + } + + if (romboot_data->write_block_size == 0) { + LOGE("Invalid write block size %d\n", + romboot_data->write_block_size); + return _EINVAL; + } + + if (romboot_data->page_size == 0) { + LOGE("Invalid erase page size %d\n", + romboot_data->page_size); + return _EINVAL; + } + + return 0; +} + +/** + * syna_tcm_romboot_jedec_send_command() + * + * Send a jedec flash commend to the ROM bootloader + * + * @param + * [ in] tcm_dev: the device handle + * [ in] flash_command: flash command to send + * [ in] out: additional data sent out, if any + * [ in] out_size: size of data sent out + * [ in] in: buffer to store the data read in + * [ in] in_size: size of data read in + * [ in] delay_ms: delay time to get the response + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_romboot_jedec_send_command(struct tcm_dev *tcm_dev, + unsigned char flash_command, unsigned char *out, + unsigned int out_size, unsigned char *in, unsigned int in_size, + unsigned int delay_ms) +{ + int retval; + unsigned char *payld_buf = NULL; + unsigned int payld_size; + struct flash_param flash_param; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + syna_pal_mem_set((void *)&flash_param, 0x00, sizeof(flash_param)); + + flash_param.spi_param = 1; + flash_param.clk_div = 0x19; + + flash_param.read_size[0] = (unsigned char)(in_size & 0xff); + flash_param.read_size[1] = (unsigned char)(in_size >> 8) & 0xff; + + flash_param.command = flash_command; + + payld_size = sizeof(struct flash_param) + out_size; + + LOGD("Flash command: 0x%02x, total size: %d, wr: %d, rd: %d\n", + flash_command, payld_size, out_size, in_size); + LOGD("Packet: %02x %02x %02x %02x %02x %02x\n", + flash_param.byte0, flash_param.byte1, flash_param.byte2, + flash_param.read_size[0], flash_param.read_size[1], + flash_param.command); + + payld_buf = syna_pal_mem_alloc(payld_size, sizeof(unsigned char)); + if (!payld_buf) { + LOGE("Fail to allocate buffer to store flash command\n"); + return _ENOMEM; + } + + retval = syna_pal_mem_cpy(payld_buf, payld_size, + &flash_param, sizeof(flash_param), sizeof(flash_param)); + if (retval < 0) { + LOGE("Fail to copy flash_param header to payld_buf\n"); + goto exit; + } + + if (out && (out_size > 0)) { + retval = syna_pal_mem_cpy(payld_buf + sizeof(flash_param), + payld_size - sizeof(flash_param), + out, out_size, out_size); + if (retval < 0) { + LOGE("Fail to copy data to payld_buf\n"); + goto exit; + } + } + + retval = syna_tcm_romboot_send_command(tcm_dev, + payld_buf, + payld_size, + in, + in_size, + delay_ms); + if (retval < 0) { + LOGE("Fail to write flash command 0x%x\n", flash_command); + goto exit; + } + +exit: + syna_pal_mem_free((void *)payld_buf); + + return retval; +} + +/** + * syna_tcm_romboot_jedec_get_status() + * + * Use jedec command to poll the flash status until the completion + * + * @param + * [ in] tcm_dev: the device handle + * [ in] delay_ms: delay time to get the response + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_romboot_jedec_get_status(struct tcm_dev *tcm_dev, + unsigned int delay_ms) +{ + int retval; + int idx; + unsigned char status; + int STATUS_CHECK_RETRY = 50; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + for (idx = 0; idx < STATUS_CHECK_RETRY; idx++) { + retval = syna_tcm_romboot_jedec_send_command(tcm_dev, + JEDEC_READ_STATUS, + NULL, + 0, + &status, + sizeof(status), + delay_ms); + if (retval < 0) { + LOGE("Failed to write JEDEC_READ_STATUS\n"); + return retval; + } + + syna_pal_sleep_us(JEDEC_STATUS_CHECK_US_MIN, + JEDEC_STATUS_CHECK_US_MAX); + /* once completed, status = 0 */ + if (!status) + break; + } + + if (status) + retval = _EIO; + else + retval = status; + + return retval; +} + +/** + * syna_tcm_romboot_jedec_erase_flash() + * + * Ask the ROM bootloader to erase the flash by using the jedec command + * + * @param + * [ in] tcm_dev: the device handle + * [ in] delay_ms: delay time to get the response + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_romboot_jedec_erase_flash(struct tcm_dev *tcm_dev, + unsigned int delay_ms) +{ + int retval; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + retval = syna_tcm_romboot_jedec_send_command(tcm_dev, + JEDEC_WRITE_ENABLE, + NULL, + 0, + NULL, + 0, + delay_ms); + if (retval < 0) { + LOGE("Failed to write JEDEC_WRITE_ENABLE\n"); + return retval; + } + + retval = syna_tcm_romboot_jedec_send_command(tcm_dev, + JEDEC_CHIP_ERASE, + NULL, + 0, + NULL, + 0, + delay_ms); + if (retval < 0) { + LOGE("Failed to write JEDEC_WRITE_ENABLE\n"); + return retval; + } + + retval = syna_tcm_romboot_jedec_get_status(tcm_dev, delay_ms); + if (retval < 0) + LOGE("Fail to get correct status, retval = %d\n", retval); + + return retval; +} + + /** + * syna_tcm_romboot_jedec_write_flash() + * + * Write the given binary data to the flash through the ROM bootloader + * by using the jedec command + * + * @param + * [ in] tcm_dev: the device handle + * [ in] address: the address in flash memory to write + * [ in] data: binary data to write + * [ in] data_size: size of binary data + * [ in] delay_ms: a short delay time in millisecond to wait for + * the completion of flash access + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_romboot_jedec_write_flash(struct tcm_dev *tcm_dev, + unsigned int address, const unsigned char *data, + unsigned int data_size, unsigned int delay_ms) +{ + int retval = 0; + unsigned int offset; + unsigned char buf[ROMBOOT_FLASH_PAGE_SIZE + 3]; + unsigned int remaining_length; + unsigned int xfer_length; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if ((!data) || (data_size == 0)) { + LOGE("Invalid image data, no data available\n"); + return _EINVAL; + } + + remaining_length = data_size; + + offset = 0; + + while (remaining_length) { + if (remaining_length > ROMBOOT_FLASH_PAGE_SIZE) + xfer_length = ROMBOOT_FLASH_PAGE_SIZE; + else + xfer_length = remaining_length; + + syna_pal_mem_set(buf, 0x00, sizeof(buf)); + + retval = syna_tcm_romboot_jedec_send_command(tcm_dev, + JEDEC_WRITE_ENABLE, + NULL, + 0, + NULL, + 0, + delay_ms); + if (retval < 0) { + LOGE("Failed to write JEDEC_WRITE_ENABLE\n"); + goto exit; + } + + buf[0] = (unsigned char)((address + offset) >> 16); + buf[1] = (unsigned char)((address + offset) >> 8); + buf[2] = (unsigned char)(address + offset); + + retval = syna_pal_mem_cpy(&buf[3], + sizeof(buf) - 3, + &data[offset], + data_size - offset, + xfer_length); + if (retval < 0) { + LOGE("Fail to copy data to write, size: %d\n", + xfer_length); + goto exit; + } + + retval = syna_tcm_romboot_jedec_send_command(tcm_dev, + JEDEC_PAGE_PROGRAM, + buf, + sizeof(buf), + NULL, + 0, + delay_ms); + if (retval < 0) { + LOGE("Failed to write data to addr 0x%x (offset: %x)\n", + address + offset, offset); + LOGE("Remaining data %d\n", + remaining_length); + goto exit; + } + + retval = syna_tcm_romboot_jedec_get_status(tcm_dev, delay_ms); + if (retval < 0) { + LOGE("Fail to get correct status, retval = %d\n", + retval); + goto exit; + } + offset += xfer_length; + remaining_length -= xfer_length; + } + +exit: + return retval; +} + + +/** + * syna_tcm_romboot_erase_flash() + * + * The entry function to perform mass erase + * + * @param + * @param + * [ in] tcm_dev: the device handle + * [ in] romboot_data: data blob for remboot + * [ in] blk: the block in flash memory to erase + * [ in] delay_ms: delay time to get the response + * [ in] is_multichip: use multi-chip command packet instead + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_romboot_erase_flash(struct tcm_dev *tcm_dev, + struct tcm_romboot_data_blob *romboot_data, + struct block_data *blk, unsigned int delay_ms, + bool is_multichip) +{ + int retval; + + if (!tcm_dev || !romboot_data || !blk) + return _EINVAL; + + if (is_multichip) + retval = syna_tcm_romboot_multichip_erase_flash(tcm_dev, + romboot_data, blk->flash_addr, blk->size, + delay_ms); + else + retval = syna_tcm_romboot_jedec_erase_flash(tcm_dev, + delay_ms); + + return retval; +} + +/** + * syna_tcm_romboot_write_flash() + * + * The entry function to write hex data to flash + * + * @param + * [ in] tcm_dev: the device handle + * [ in] romboot_data: data blob for remboot + * [ in] blk: the block in flash memory to update + * [ in] delay_ms: a short delay time in millisecond to wait for + * the completion of flash access + * [ in] is_multichip: use multi-chip command packet instead + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_romboot_write_flash(struct tcm_dev *tcm_dev, + struct tcm_romboot_data_blob *romboot_data, + struct block_data *blk, unsigned int delay_ms, + bool is_multichip) +{ + int retval; + + if (!tcm_dev || !romboot_data || !blk) + return _EINVAL; + + if (is_multichip) + retval = syna_tcm_romboot_multichip_write_flash(tcm_dev, + romboot_data, blk->flash_addr, blk->data, + blk->size, delay_ms); + else + retval = syna_tcm_romboot_jedec_write_flash(tcm_dev, + blk->flash_addr, blk->data, blk->size, + delay_ms); + + return retval; +} + + +/** + * syna_tcm_romboot_do_ihex_update() + * + * The entry function to perform ihex update upon ROM Boot. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] ihex: ihex data to write + * [ in] ihex_size: size of ihex data + * [ in] flash_size: size for temporary buffer allocation, which used + * to re-order the flash data. + * in general, (ihex_size + 4K) is preferred size + * [ in] len_per_line: length per line in the ihex file + * [ in] delay_ms: a short delay time in millisecond to wait for + * the completion of flash access + * [ in] is_multichip: flag to indicate a multi-chip product used + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_romboot_do_ihex_update(struct tcm_dev *tcm_dev, + const unsigned char *ihex, unsigned int ihex_size, + unsigned int flash_size, unsigned int len_per_line, + unsigned int delay_ms, bool is_multichip) +{ + int retval; + struct tcm_romboot_data_blob romboot_data; + struct ihex_info *ihex_info = NULL; + struct block_data *block; + unsigned int erase_delay_ms = (delay_ms >> 16) & 0xFFFF; + unsigned int wr_delay_ms = delay_ms & 0xFFFF; + unsigned short *header; + int idx; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if ((!ihex) || (ihex_size == 0)) { + LOGE("Invalid ihex data\n"); + return _EINVAL; + } + + if (flash_size == 0) + flash_size = ihex_size + 4096; + + romboot_data.bdata = ihex; + romboot_data.bdata_size = ihex_size; + syna_pal_mem_set(&romboot_data.ihex_info, 0x00, + sizeof(struct ihex_info)); + + ihex_info = &romboot_data.ihex_info; + + ihex_info->bin = syna_pal_mem_alloc(flash_size, + sizeof(unsigned char)); + if (!ihex_info->bin) { + LOGE("Fail to allocate buffer for ihex data\n"); + + return _ENOMEM; + } + + ihex_info->bin_size = flash_size; + + syna_tcm_buf_init(&romboot_data.out); + + LOGN("Parse ihex file\n"); + + /* parse ihex file */ + retval = syna_tcm_parse_fw_ihex((const char *)ihex, + ihex_size, ihex_info, len_per_line); + if (retval < 0) { + LOGE("Fail to parse firmware ihex file\n"); + goto exit; + } + + if (!is_multichip) { + header = (unsigned short *)ihex_info->block[0].data; + if (*header != BINARY_FILE_MAGIC_VALUE) { + LOGE("Incorrect image header 0x%04X\n", *header); + goto exit; + } + } + + /* set up flash access, and enter the bootloader mode */ + retval = syna_tcm_romboot_preparation(tcm_dev, + &romboot_data, + is_multichip); + if (retval < 0) { + LOGE("Fail to do preparation\n"); + goto reset; + } + + LOGN("Start of ihex update\n"); + + ATOMIC_SET(tcm_dev->firmware_flashing, 1); + + for (idx = 0; idx < IHEX_MAX_BLOCKS; idx++) { + + block = &ihex_info->block[idx]; + + if (!block->available) + continue; + + if (block->size == 0) + continue; + + /* for single-chip, mass erase will affect the + * entire flash memory, so it just takes once + */ + if ((idx != 0) && (!is_multichip)) + break; + + retval = syna_tcm_romboot_erase_flash(tcm_dev, + &romboot_data, + block, + erase_delay_ms, + is_multichip); + if (retval < 0) { + LOGE("Fail to erase flash\n"); + goto reset; + } + } + + LOGN("Flash erased\n"); + LOGN("Start to write all data to flash\n"); + + for (idx = 0; idx < IHEX_MAX_BLOCKS; idx++) { + + block = &ihex_info->block[idx]; + + if (!block->available) + continue; + + LOGD("block:%d, addr:0x%x, size:%d\n", + idx, block->flash_addr, block->size); + + if (block->size == 0) + continue; + + retval = syna_tcm_romboot_write_flash(tcm_dev, + &romboot_data, + block, + wr_delay_ms, + is_multichip); + if (retval < 0) { + LOGE("Fail to write data to addr 0x%x, size:%d\n", + block->flash_addr, block->size); + goto reset; + } + + LOGI("Data written, size:%d\n", block->size); + } + + LOGN("End of ihex update\n"); + + retval = 0; + +reset: + retval = syna_tcm_reset(tcm_dev); + if (retval < 0) { + LOGE("Fail to do reset\n"); + goto exit; + } + +exit: + syna_pal_mem_free((void *)ihex_info->bin); + + ATOMIC_SET(tcm_dev->firmware_flashing, 0); + + syna_tcm_buf_release(&romboot_data.out); + + return retval; +} + +/** + * syna_tcm_romboot_do_multichip_reflash() + * + * The entry function to perform fw update in multi-chip product. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] image: image file given + * [ in] image_size: size of data array + * [ in] wait_delay_ms: a short delay time in millisecond to wait for + * the completion of flash access + * for polling, set a value formatted with + * [erase | write]; + * for ATTN-driven, set a '0' or 'RESP_IN_ATTN' + * [ in] force_reflash: '1' to do reflash anyway + * '0' to compare ID info before doing reflash. + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_romboot_do_multichip_reflash(struct tcm_dev *tcm_dev, + const unsigned char *image, unsigned int image_size, + unsigned int wait_delay_ms, bool force_reflash) +{ + int retval; + int idx; + struct tcm_romboot_data_blob romboot_data; + struct block_data *block; + struct app_config_header *header; + unsigned int image_fw_id; + unsigned int erase_delay_ms = (wait_delay_ms >> 16) & 0xFFFF; + unsigned int wr_delay_ms = wait_delay_ms & 0xFFFF; + bool has_tool_boot_cfg = false; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if ((!image) || (image_size == 0)) { + LOGE("Invalid image data\n"); + return _EINVAL; + } + + LOGN("Prepare to do reflash\n"); + + syna_pal_mem_set(&romboot_data, 0x00, + sizeof(struct tcm_romboot_data_blob)); + + syna_tcm_buf_init(&romboot_data.out); + + romboot_data.bdata = image; + romboot_data.bdata_size = image_size; + + syna_tcm_buf_init(&romboot_data.out); + + retval = syna_tcm_parse_fw_image(image, &romboot_data.image_info); + if (retval < 0) { + LOGE("Fail to parse firmware image\n"); + retval = _EINVAL; + goto exit; + } + + block = &romboot_data.image_info.data[AREA_APP_CONFIG]; + if (block->size < sizeof(struct app_config_header)) { + LOGE("Invalid application config in image file\n"); + retval = _EINVAL; + goto exit; + } + header = (struct app_config_header *)block->data; + + image_fw_id = syna_pal_le4_to_uint(header->build_id); + + LOGN("Device firmware ID: %d, image build id: %d\n", + tcm_dev->packrat_number, image_fw_id); + + if ((image_fw_id <= tcm_dev->packrat_number) && !force_reflash) { + LOGN("No need to do reflash\n"); + retval = 0; + goto exit; + } + + block = &romboot_data.image_info.data[AREA_TOOL_BOOT_CONFIG]; + has_tool_boot_cfg = block->available; + + /* set up flash access, and enter the bootloader mode */ + retval = syna_tcm_romboot_preparation(tcm_dev, &romboot_data, true); + if (retval < 0) { + LOGE("Fail to do preparation\n"); + goto reset; + } + + if (!IS_ROM_BOOTLOADER_MODE(tcm_dev->dev_mode)) { + LOGE("Incorrect device mode 0x%02x, expected:0x%02x\n", + tcm_dev->dev_mode, MODE_ROMBOOTLOADER); + retval = _EINVAL; + goto reset; + } + + LOGN("Start of reflash\n"); + + ATOMIC_SET(tcm_dev->firmware_flashing, 1); + + /* Traverse through all blocks in the image file, + * then erase the corresponding block area + */ + for (idx = 0; idx < AREA_MAX; idx++) { + + block = &romboot_data.image_info.data[idx]; + + if (!block->available) + continue; + + if (idx == AREA_ROMBOOT_APP_CODE) + continue; + + if ((idx == AREA_BOOT_CONFIG) && has_tool_boot_cfg) + continue; + + LOGD("Erase %s block - address: 0x%x (%d), size: %d\n", + AREA_ID_STR(block->id), block->flash_addr, + block->flash_addr, block->size); + + if (block->size == 0) + continue; + + if (erase_delay_ms == DEFAULT_FLASH_ERASE_DELAY) + erase_delay_ms = ROMBOOT_DELAY_MS; + + retval = syna_tcm_romboot_erase_flash(tcm_dev, + &romboot_data, + block, + erase_delay_ms, + true); + if (retval < 0) { + LOGE("Fail to erase %s area\n", AREA_ID_STR(block->id)); + goto reset; + } + + LOGN("%s partition erased\n", AREA_ID_STR(block->id)); + } + + /* Traverse through all blocks in the image file, + * and then update the corresponding block area + */ + for (idx = 0; idx < AREA_MAX; idx++) { + + LOGD("Prepare to update %s partition\n", AREA_ID_STR(idx)); + + block = &romboot_data.image_info.data[idx]; + + if (!block->available) + continue; + + if (idx == AREA_ROMBOOT_APP_CODE) + continue; + + if ((idx == AREA_BOOT_CONFIG) && has_tool_boot_cfg) + continue; + + LOGD("Write data to %s - address: 0x%x (%d), size: %d\n", + AREA_ID_STR(block->id), block->flash_addr, + block->flash_addr, block->size); + + if (block->size == 0) + continue; + + if (wr_delay_ms == DEFAULT_FLASH_WRITE_DELAY) + wr_delay_ms = ROMBOOT_DELAY_MS; + + retval = syna_tcm_romboot_write_flash(tcm_dev, + &romboot_data, + block, + wr_delay_ms, + true); + + if (retval < 0) { + LOGE("Fail to update %s partition, size: %d\n", + AREA_ID_STR(block->id), block->size); + goto reset; + } + + LOGN("%s written\n", AREA_ID_STR(block->id)); + } + + LOGN("End of reflash\n"); + + retval = 0; +reset: + retval = syna_tcm_reset(tcm_dev); + if (retval < 0) { + LOGE("Fail to do reset\n"); + goto exit; + } + +exit: + ATOMIC_SET(tcm_dev->firmware_flashing, 0); + + syna_tcm_buf_release(&romboot_data.out); + + return retval; +} + + +/** + * syna_tcm_get_romboot_info() + * + * Implement the bootloader command code, which is used to request a + * RomBoot information packet. + * + * @param + * [ in] tcm_dev: the device handle + * [out] rom_boot_info: the romboot info packet returned + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_get_romboot_info(struct tcm_dev *tcm_dev, + struct tcm_romboot_info *rom_boot_info) +{ + int retval = 0; + unsigned char resp_code; + unsigned int copy_size = 0; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + retval = tcm_dev->write_message(tcm_dev, + CMD_GET_ROMBOOT_INFO, + NULL, + 0, + &resp_code, + tcm_dev->msg_data.default_resp_reading); + if (retval < 0) { + LOGE("Fail to send command 0x%02x\n", + CMD_GET_ROMBOOT_INFO); + goto exit; + } + + if (rom_boot_info == NULL) + goto exit; + + copy_size = MIN(sizeof(struct tcm_romboot_info), + tcm_dev->resp_buf.data_length); + + /* copy romboot_info to caller */ + retval = syna_pal_mem_cpy((unsigned char *)rom_boot_info, + sizeof(struct tcm_romboot_info), + tcm_dev->resp_buf.buf, + tcm_dev->resp_buf.buf_size, + copy_size); + if (retval < 0) { + LOGE("Fail to copy romboot info to caller\n"); + goto exit; + } + +exit: + return retval; +} + + + diff --git a/tcm/synaptics_touchcom_func_romboot.h b/tcm/synaptics_touchcom_func_romboot.h new file mode 100644 index 0000000..812caf9 --- /dev/null +++ b/tcm/synaptics_touchcom_func_romboot.h @@ -0,0 +1,155 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Synaptics TouchCom touchscreen driver + * + * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +/** + * @file synaptics_touchcom_func_romboot.h + * + * This file declares relevant functions and structures for ROM boot-loader. + */ + +#ifndef _SYNAPTICS_TOUCHCOM_ROMBOOT_FUNCS_H_ +#define _SYNAPTICS_TOUCHCOM_ROMBOOT_FUNCS_H_ + +#include "synaptics_touchcom_core_dev.h" +#include "synaptics_touchcom_func_base_flash.h" + + +#define ROMBOOT_DELAY_MS (20) + +/** + * @section: Structure to assemble flash command + */ +struct flash_param { + union { + struct { + unsigned char byte0; + unsigned char byte1; + unsigned char byte2; + }; + struct { + unsigned char spi_param; + unsigned char clk_div; + unsigned char mode; + }; + }; + unsigned char read_size[2]; + unsigned char command; +}; + +/** + * @section: Specific data blob for romboot + * + * The structure contains various parameters being used in + * ROM boot control + */ +struct tcm_romboot_data_blob { + /* binary data to write */ + const unsigned char *bdata; + unsigned int bdata_size; + /* parsed data based on given binary data */ + struct ihex_info ihex_info; + struct image_info image_info; + /* standard information for flash access */ + unsigned int page_size; + unsigned int write_block_size; + unsigned int max_write_payload_size; + /* temporary buffer during the reflash */ + struct tcm_buffer out; +}; + + +/** + * syna_tcm_romboot_do_ihex_update() + * + * The entry function to perform ihex update upon ROM Boot. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] ihex: ihex data to write + * [ in] ihex_size: size of ihex data + * [ in] flash_size: size for temporary buffer allocation, which used + * to re-order the flash data. + * in general, (ihex_size + 4K) is preferred size + * [ in] len_per_line: length per line in the ihex file + * [ in] delay_ms: a short delay time in millisecond to wait for + * the completion of flash access + * [ in] is_multichip: flag to indicate a multi-chip product used + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_romboot_do_ihex_update(struct tcm_dev *tcm_dev, + const unsigned char *ihex, unsigned int ihex_size, + unsigned int flash_size, unsigned int len_per_line, + unsigned int delay_ms, bool is_multichip); + +/** + * syna_tcm_romboot_do_multichip_reflash() + * + * The entry function to perform fw update with multi-chip product. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] image: image file given + * [ in] image_size: size of data array + * [ in] wait_delay_ms: a short delay time in millisecond to wait for + * the completion of flash access + * set [erase_delay_ms | write_delay_ms] for setup; + * set '0' to use default time; + * set 'FORCE_ATTN_DRIVEN' to adopt ATTN-driven. + * [ in] force_reflash: '1' to do reflash anyway + * '0' to compare ID info before doing reflash. + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_romboot_do_multichip_reflash(struct tcm_dev *tcm_dev, + const unsigned char *image, unsigned int image_size, + unsigned int wait_delay_ms, bool force_reflash); + +/** + * syna_tcm_get_romboot_info() + * + * Implement the bootloader command code, which is used to request a + * RomBoot information packet. + * + * @param + * [ in] tcm_dev: the device handle + * [out] rom_boot_info: the romboot info packet returned + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_get_romboot_info(struct tcm_dev *tcm_dev, + struct tcm_romboot_info *rom_boot_info); + + +#endif /* end of _SYNAPTICS_TOUCHCOM_ROMBOOT_FUNCS_H_ */ diff --git a/tcm/synaptics_touchcom_func_touch.c b/tcm/synaptics_touchcom_func_touch.c new file mode 100644 index 0000000..f390d0c --- /dev/null +++ b/tcm/synaptics_touchcom_func_touch.c @@ -0,0 +1,877 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Synaptics TCM touchscreen driver + * + * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +/** + * @file synaptics_tcm2_func_touch.c + * + * This file implements the touch report handling functions. + * The declarations are available in synaptics_touchcom_func_touch.h. + */ + +#include "synaptics_touchcom_func_touch.h" + +/** + * syna_tcm_get_touch_data() + * + * Get data entity from the received report according to bit offset and bit + * length defined in the touch report configuration. + * + * @param + * [ in] report: touch report generated by TouchComm device + * [ in] report_size: size of given report + * [ in] offset: bit offset in the report + * [ in] bits: number of bits representing the data + * [out] data: data parsed + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_get_touch_data(const unsigned char *report, + unsigned int report_size, unsigned int offset, + unsigned int bits, unsigned int *data) +{ + unsigned char mask; + unsigned char byte_data; + unsigned int output_data; + unsigned int bit_offset; + unsigned int byte_offset; + unsigned int data_bits; + unsigned int available_bits; + unsigned int remaining_bits; + + if (bits == 0 || bits > 32) { + LOGE("Invalid number of bits %d\n", bits); + return _EINVAL; + } + + if (!report) { + LOGE("Invalid report data\n"); + return _EINVAL; + } + + if (offset + bits > report_size * 8) { + *data = 0; + return 0; + } + + output_data = 0; + remaining_bits = bits; + + bit_offset = offset % 8; + byte_offset = offset / 8; + + while (remaining_bits) { + byte_data = report[byte_offset]; + byte_data >>= bit_offset; + + available_bits = 8 - bit_offset; + data_bits = MIN(available_bits, remaining_bits); + mask = 0xff >> (8 - data_bits); + + byte_data &= mask; + + output_data |= byte_data << (bits - remaining_bits); + + bit_offset = 0; + byte_offset += 1; + remaining_bits -= data_bits; + } + + *data = output_data; + + return 0; +} + +/** + * syna_tcm_get_gesture_data() + * + * The contents of the gesture data entity depend on which gesture + * is detected. The default size of data is defined in 16-64 bits natively. + * + * @param + * [ in] report: touch report generated by TouchComm device + * [ in] report_size: size of given report + * [ in] offset: bit offset in the report + * [ in] bits: total bits representing the gesture data + * [out] gesture_data: gesture data parsed + * [ in] gesture_id: gesture id retrieved + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int syna_tcm_get_gesture_data(const unsigned char *report, + unsigned int report_size, unsigned int offset, + unsigned int bits, struct tcm_gesture_data_blob *gesture_data, + unsigned int gesture_id) +{ + int retval; + unsigned int idx; + unsigned int data; + unsigned int size; + unsigned int data_end; + + if (!report) { + LOGE("Invalid report data\n"); + return _EINVAL; + } + + if (offset + bits > report_size * 8) + return 0; + + data_end = offset + bits; + + size = (sizeof(gesture_data->data) / sizeof(unsigned char)); + + idx = 0; + while ((offset < data_end) && (idx < size)) { + retval = syna_tcm_get_touch_data(report, report_size, + offset, 16, &data); + if (retval < 0) { + LOGE("Fail to get object index\n"); + return retval; + } + gesture_data->data[idx++] = (unsigned char)(data & 0xff); + gesture_data->data[idx++] = (unsigned char)((data >> 8) & 0xff); + offset += 16; + } + + switch (gesture_id) { + case GESTURE_ID_DOUBLE_TAP: + case GESTURE_ID_ACTIVE_TAP_AND_HOLD: + LOGD("Tap info: (%d, %d)\n", + syna_pal_le2_to_uint(gesture_data->tap_x), + syna_pal_le2_to_uint(gesture_data->tap_y)); + break; + case GESTURE_ID_SWIPE: + LOGD("Swipe info: direction:%x (%d, %d)\n", + syna_pal_le2_to_uint(gesture_data->swipe_direction), + syna_pal_le2_to_uint(gesture_data->tap_x), + syna_pal_le2_to_uint(gesture_data->tap_y)); + break; + default: + LOGW("Unknown gesture_id:%d\n", gesture_id); + break; + } + + return 0; +} + +/** + * syna_tcm_parse_touch_report() + * + * Traverse through touch report configuration and parse the contents of + * report packet to get the exactly touched data entity from touch reports. + * + * At the end of function, the touched data will be parsed and stored at the + * associated position in structure touch_data_blob. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] report: touch report generated by TouchComm device + * [ in] report_size: size of given report + * [out] touch_data: touch data generated + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_parse_touch_report(struct tcm_dev *tcm_dev, + unsigned char *report, unsigned int report_size, + struct tcm_touch_data_blob *touch_data) +{ + int retval; + bool active_only; + bool num_of_active_objects; + unsigned char code; + unsigned int size; + unsigned int idx; + unsigned int obj; + unsigned int next; + unsigned int data; + unsigned int bits; + unsigned int offset; + unsigned int objects; + unsigned int active_objects; + unsigned int config_size; + unsigned char *config_data; + struct tcm_objects_data_blob *object_data; + static unsigned int end_of_foreach; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if (!report) { + LOGE("Invalid report data\n"); + return _EINVAL; + } + + if (report_size <= 0) { + LOGE("Invalid report data length\n"); + return _EINVAL; + } + + if (!touch_data) { + LOGE("Invalid touch data structure\n"); + return _EINVAL; + } + + if (tcm_dev->max_objects == 0) { + LOGE("Invalid max_objects supported\n"); + return _EINVAL; + } + + object_data = touch_data->object_data; + + if (!object_data) { + LOGE("Invalid object_data\n"); + return _EINVAL; + } + + config_data = tcm_dev->touch_config.buf; + config_size = tcm_dev->touch_config.data_length; + + if ((!config_data) || (config_size == 0)) { + LOGE("Invalid config_data\n"); + return _EINVAL; + } + + size = sizeof(touch_data->object_data); + syna_pal_mem_set(touch_data->object_data, 0x00, size); + + num_of_active_objects = false; + + idx = 0; + offset = 0; + objects = 0; + active_objects = 0; + active_only = false; + obj = 0; + next = 0; + + while (idx < config_size) { + code = config_data[idx++]; + switch (code) { + case TOUCH_REPORT_END: + goto exit; + case TOUCH_REPORT_FOREACH_ACTIVE_OBJECT: + obj = 0; + next = idx; + active_only = true; + break; + case TOUCH_REPORT_FOREACH_OBJECT: + obj = 0; + next = idx; + active_only = false; + break; + case TOUCH_REPORT_FOREACH_END: + end_of_foreach = idx; + if (active_only) { + if (num_of_active_objects) { + objects++; + obj++; + if (objects < active_objects) + idx = next; + } else if (offset < report_size * 8) { + obj++; + idx = next; + } + } else { + obj++; + if (obj < tcm_dev->max_objects) + idx = next; + } + break; + case TOUCH_REPORT_PAD_TO_NEXT_BYTE: + offset = syna_pal_ceil_div(offset, 8) * 8; + break; + case TOUCH_REPORT_TIMESTAMP: + bits = config_data[idx++]; + retval = syna_tcm_get_touch_data(report, report_size, + offset, bits, &data); + if (retval < 0) { + LOGE("Fail to get time-stamp\n"); + return retval; + } + touch_data->timestamp = data; + offset += bits; + break; + case TOUCH_REPORT_OBJECT_N_INDEX: + bits = config_data[idx++]; + retval = syna_tcm_get_touch_data(report, report_size, + offset, bits, &data); + if (retval < 0) { + LOGE("Fail to get object index\n"); + return retval; + } + obj = data; + offset += bits; + break; + case TOUCH_REPORT_OBJECT_N_CLASSIFICATION: + bits = config_data[idx++]; + retval = syna_tcm_get_touch_data(report, report_size, + offset, bits, &data); + if (retval < 0) { + LOGE("Fail to get object classification\n"); + return retval; + } + object_data[obj].status = (unsigned char)data; + offset += bits; + break; + case TOUCH_REPORT_OBJECT_N_X_POSITION: + bits = config_data[idx++]; + retval = syna_tcm_get_touch_data(report, report_size, + offset, bits, &data); + if (retval < 0) { + LOGE("Fail to get object x position\n"); + return retval; + } + object_data[obj].x_pos = data; + offset += bits; + break; + case TOUCH_REPORT_OBJECT_N_Y_POSITION: + bits = config_data[idx++]; + retval = syna_tcm_get_touch_data(report, report_size, + offset, bits, &data); + if (retval < 0) { + LOGE("Fail to get object y position\n"); + return retval; + } + object_data[obj].y_pos = data; + offset += bits; + break; + case TOUCH_REPORT_OBJECT_N_Z: + bits = config_data[idx++]; + retval = syna_tcm_get_touch_data(report, report_size, + offset, bits, &data); + if (retval < 0) { + LOGE("Fail to get object z\n"); + return retval; + } + object_data[obj].z = data; + offset += bits; + break; + case TOUCH_REPORT_OBJECT_N_X_WIDTH: + bits = config_data[idx++]; + retval = syna_tcm_get_touch_data(report, report_size, + offset, bits, &data); + if (retval < 0) { + LOGE("Fail to get object x width\n"); + return retval; + } + object_data[obj].x_width = data; + offset += bits; + break; + case TOUCH_REPORT_OBJECT_N_Y_WIDTH: + bits = config_data[idx++]; + retval = syna_tcm_get_touch_data(report, report_size, + offset, bits, &data); + if (retval < 0) { + LOGE("Fail to get object y width\n"); + return retval; + } + object_data[obj].y_width = data; + offset += bits; + break; + case TOUCH_REPORT_OBJECT_N_TX_POSITION_TIXELS: + bits = config_data[idx++]; + retval = syna_tcm_get_touch_data(report, report_size, + offset, bits, &data); + if (retval < 0) { + LOGE("Fail to get object tx position\n"); + return retval; + } + object_data[obj].tx_pos = data; + offset += bits; + break; + case TOUCH_REPORT_OBJECT_N_RX_POSITION_TIXELS: + bits = config_data[idx++]; + retval = syna_tcm_get_touch_data(report, report_size, + offset, bits, &data); + if (retval < 0) { + LOGE("Fail to get object rx position\n"); + return retval; + } + object_data[obj].rx_pos = data; + offset += bits; + break; + case TOUCH_REPORT_NUM_OF_ACTIVE_OBJECTS: + bits = config_data[idx++]; + retval = syna_tcm_get_touch_data(report, report_size, + offset, bits, &data); + if (retval < 0) { + LOGE("Fail to get number of active objects\n"); + return retval; + } + active_objects = data; + num_of_active_objects = true; + touch_data->num_of_active_objects = data; + offset += bits; + if (touch_data->num_of_active_objects == 0) { + if (end_of_foreach == 0) { + LOGE("Invalid end_foreach\n"); + return 0; + } + idx = end_of_foreach; + } + break; + case TOUCH_REPORT_0D_BUTTONS_STATE: + bits = config_data[idx++]; + retval = syna_tcm_get_touch_data(report, report_size, + offset, bits, &data); + if (retval < 0) { + LOGE("Fail to get 0D buttons state\n"); + return retval; + } + touch_data->buttons_state = data; + offset += bits; + break; + case TOUCH_REPORT_GESTURE_ID: + if (tcm_dev->custom_gesture_parse_func) { + retval = tcm_dev->custom_gesture_parse_func( + TOUCH_REPORT_GESTURE_ID, config_data, + &idx, report, &offset, report_size, + tcm_dev->cbdata_gesture_parse); + } else { + bits = config_data[idx++]; + retval = syna_tcm_get_touch_data(report, + report_size, offset, bits, &data); + touch_data->gesture_id = data; + offset += bits; + } + if (retval < 0) { + LOGE("Fail to get gesture id\n"); + return retval; + } + break; + case TOUCH_REPORT_GESTURE_DATA: + if (tcm_dev->custom_gesture_parse_func) { + retval = tcm_dev->custom_gesture_parse_func( + TOUCH_REPORT_GESTURE_DATA, config_data, + &idx, report, &offset, report_size, + tcm_dev->cbdata_gesture_parse); + } else { + bits = config_data[idx++]; + retval = syna_tcm_get_gesture_data(report, + report_size, + offset, bits, + &touch_data->gesture_data, + touch_data->gesture_id); + offset += bits; + } + if (retval < 0) { + LOGE("Fail to get gesture data\n"); + return retval; + } + break; + case TOUCH_REPORT_FRAME_RATE: + bits = config_data[idx++]; + retval = syna_tcm_get_touch_data(report, report_size, + offset, bits, &data); + if (retval < 0) { + LOGE("Fail to get frame rate\n"); + return retval; + } + touch_data->frame_rate = data; + offset += bits; + break; + case TOUCH_REPORT_FORCE_MEASUREMENT: + bits = config_data[idx++]; + retval = syna_tcm_get_touch_data(report, report_size, + offset, bits, &data); + if (retval < 0) { + LOGE("Fail to get force measurement data\n"); + return retval; + } + touch_data->force_data = data; + offset += bits; + break; + case TOUCH_REPORT_FINGERPRINT_AREA_MEET: + bits = config_data[idx++]; + retval = syna_tcm_get_touch_data(report, report_size, + offset, bits, &data); + if (retval < 0) { + LOGE("Fail to get data for fingerprint area\n"); + return retval; + } + touch_data->fingerprint_area_meet = data; + offset += bits; + break; + case TOUCH_REPORT_POWER_IM: + bits = config_data[idx++]; + retval = syna_tcm_get_touch_data(report, report_size, + offset, bits, &data); + if (retval < 0) { + LOGE("Fail to get power IM\n"); + return retval; + } + touch_data->power_im = data; + offset += bits; + break; + case TOUCH_REPORT_CID_IM: + bits = config_data[idx++]; + retval = syna_tcm_get_touch_data(report, report_size, + offset, bits, &data); + if (retval < 0) { + LOGE("Fail to get CID IM\n"); + return retval; + } + touch_data->cid_im = data; + offset += bits; + break; + case TOUCH_REPORT_RAIL_IM: + bits = config_data[idx++]; + retval = syna_tcm_get_touch_data(report, report_size, + offset, bits, &data); + if (retval < 0) { + LOGE("Fail to get rail IM\n"); + return retval; + } + touch_data->rail_im = data; + offset += bits; + break; + case TOUCH_REPORT_CID_VARIANCE_IM: + bits = config_data[idx++]; + retval = syna_tcm_get_touch_data(report, report_size, + offset, bits, &data); + if (retval < 0) { + LOGE("Fail to get CID variance IM\n"); + return retval; + } + touch_data->cid_variance_im = data; + offset += bits; + break; + case TOUCH_REPORT_NSM_FREQUENCY_INDEX: + bits = config_data[idx++]; + retval = syna_tcm_get_touch_data(report, report_size, + offset, bits, &data); + if (retval < 0) { + LOGE("Fail to get NSM frequency\n"); + return retval; + } + touch_data->nsm_frequency = data; + offset += bits; + break; + case TOUCH_REPORT_NSM_STATE: + bits = config_data[idx++]; + retval = syna_tcm_get_touch_data(report, report_size, + offset, bits, &data); + if (retval < 0) { + LOGE("Fail to get NSM state\n"); + return retval; + } + touch_data->nsm_state = data; + offset += bits; + break; + case TOUCH_REPORT_CPU_CYCLES_USED_SINCE_LAST_FRAME: + bits = config_data[idx++]; + retval = syna_tcm_get_touch_data(report, report_size, + offset, bits, &data); + if (retval < 0) { + LOGE("Fail to get cpu cycles info\n"); + return retval; + } + touch_data->num_of_cpu_cycles = data; + offset += bits; + break; + case TOUCH_REPORT_FACE_DETECT: + bits = config_data[idx++]; + retval = syna_tcm_get_touch_data(report, report_size, + offset, bits, &data); + if (retval < 0) { + LOGE("Fail to detect face\n"); + return retval; + } + touch_data->fd_data = data; + offset += bits; + break; + case TOUCH_REPORT_SENSING_MODE: + bits = config_data[idx++]; + retval = syna_tcm_get_touch_data(report, report_size, + offset, bits, &data); + if (retval < 0) { + LOGE("Fail to get sensing mode\n"); + return retval; + } + touch_data->sensing_mode = data; + offset += bits; + break; + default: + /* use custom parsing method, if registered */ + if (tcm_dev->custom_touch_data_parse_func) { + retval = tcm_dev->custom_touch_data_parse_func( + code, config_data, &idx, report, + &offset, report_size, + tcm_dev->cbdata_touch_data_parse); + if (retval >= 0) + continue; + } + + LOGW("Unknown touch config code:0x%02x (length:%d)\n", + code, config_data[idx]); + bits = config_data[idx++]; + offset += bits; + break; + } + } + +exit: + return 0; +} + +/** + * syna_tcm_set_touch_report_config() + * + * Setup the format and content of touch report if needed. + * + * TouchComm allows to set how touch reports are formatted and what items get + * reported each time a touch report is generated. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] config: the customized report configuration + * [ in] config_size: size of given config + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_set_touch_report_config(struct tcm_dev *tcm_dev, + unsigned char *config, unsigned int config_size) +{ + int retval = 0; + unsigned char resp_code; + unsigned int size; + struct tcm_application_info *app_info; + unsigned char *data; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if ((!config) || (config_size == 0)) { + LOGE("Invalid given config data\n"); + return _EINVAL; + } + + if (IS_NOT_APP_FW_MODE(tcm_dev->dev_mode)) { + LOGE("Not in application fw mode, mode: %d\n", + tcm_dev->dev_mode); + return _EINVAL; + } + + app_info = &tcm_dev->app_info; + size = syna_pal_le2_to_uint(app_info->max_touch_report_config_size); + + if (config_size > size) { + LOGE("Invalid config size: %d (max: %d)\n", config_size, size); + return _EINVAL; + } + + data = syna_pal_mem_alloc(size, sizeof(unsigned char)); + if (!data) { + LOGE("Fail to allocate memory for touch config setting\n"); + return _ENOMEM; + } + + retval = syna_pal_mem_cpy(data, + size, + config, + config_size, + config_size); + if (retval < 0) { + LOGE("Fail to copy custom touch config\n"); + goto exit; + } + + retval = tcm_dev->write_message(tcm_dev, + CMD_SET_TOUCH_REPORT_CONFIG, + data, + size, + &resp_code, + tcm_dev->msg_data.default_resp_reading); + if (retval < 0) { + LOGE("Fail to write command CMD_SET_TOUCH_REPORT_CONFIG\n"); + goto exit; + } + + LOGI("Set touch config done\n"); + +exit: + if (data) + syna_pal_mem_free((void *)data); + + return retval; +} + +/** + * syna_tcm_preserve_touch_report_config() + * + * Retrieve and preserve the current touch report configuration. + * + * The retrieved configuration is stored in touch_config buffer defined + * in structure syna_tcm_dev for later using of touch position parsing. + * + * The touch_config buffer will be allocated internally and its size will + * be updated accordingly. + * + * @param + * [ in] tcm_dev: the device handle + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_preserve_touch_report_config(struct tcm_dev *tcm_dev) +{ + int retval = 0; + unsigned char resp_code; + unsigned int size = 0; + + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + if (IS_NOT_APP_FW_MODE(tcm_dev->dev_mode)) { + LOGE("Not in application fw mode, mode: %d\n", + tcm_dev->dev_mode); + return _EINVAL; + } + + retval = tcm_dev->write_message(tcm_dev, + CMD_GET_TOUCH_REPORT_CONFIG, + NULL, + 0, + &resp_code, + tcm_dev->msg_data.default_resp_reading); + if (retval < 0) { + LOGE("Fail to write command CMD_GET_TOUCH_REPORT_CONFIG\n"); + goto exit; + } + + syna_tcm_buf_lock(&tcm_dev->resp_buf); + + size = tcm_dev->resp_buf.data_length; + retval = syna_tcm_buf_alloc(&tcm_dev->touch_config, + size); + if (retval < 0) { + LOGE("Fail to allocate memory for internal touch_config\n"); + syna_tcm_buf_unlock(&tcm_dev->resp_buf); + goto exit; + } + + syna_tcm_buf_lock(&tcm_dev->touch_config); + + retval = syna_pal_mem_cpy(tcm_dev->touch_config.buf, + tcm_dev->touch_config.buf_size, + tcm_dev->resp_buf.buf, + tcm_dev->resp_buf.buf_size, + size); + if (retval < 0) { + LOGE("Fail to clone touch config\n"); + syna_tcm_buf_unlock(&tcm_dev->touch_config); + syna_tcm_buf_unlock(&tcm_dev->resp_buf); + goto exit; + } + + tcm_dev->touch_config.data_length = size; + + syna_tcm_buf_unlock(&tcm_dev->touch_config); + syna_tcm_buf_unlock(&tcm_dev->resp_buf); + +exit: + return retval; +} + +/** + * syna_tcm_set_custom_touch_data_parsing_callback() + * + * Set up callback function to handle custom touch data. + * + * Once finding the "new" custom entity in touch report, the core library will + * invoke the custom parsing method to handle this "new" code entity. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] p_cb: the callback function pointer + * [ in] p_cbdata: pointer to caller data passed to callback function + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_set_custom_touch_data_parsing_callback(struct tcm_dev *tcm_dev, + tcm_touch_data_parse_callback_t p_cb, void *p_cbdata) +{ + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + tcm_dev->custom_touch_data_parse_func = p_cb; + tcm_dev->cbdata_touch_data_parse = p_cbdata; + + LOGI("enabled\n"); + + return 0; +} + +/** + * syna_tcm_set_custom_gesture_parsing_callback() + * + * Set up callback function to handle the gesture data defined as the following + * code entities + * - TOUCH_REPORT_GESTURE_ID + * - TOUCH_REPORT_GESTURE_DATA + * + * @param + * [ in] tcm_dev: the device handle + * [ in] p_cb: the callback function pointer + * [ in] p_cbdata: pointer to caller data passed to callback function + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_set_custom_gesture_parsing_callback(struct tcm_dev *tcm_dev, + tcm_gesture_parse_callback_t p_cb, void *p_cbdata) +{ + if (!tcm_dev) { + LOGE("Invalid tcm device handle\n"); + return _EINVAL; + } + + tcm_dev->custom_gesture_parse_func = p_cb; + tcm_dev->cbdata_gesture_parse = p_cbdata; + + LOGI("enabled\n"); + + return 0; +} + diff --git a/tcm/synaptics_touchcom_func_touch.h b/tcm/synaptics_touchcom_func_touch.h new file mode 100644 index 0000000..eb63c05 --- /dev/null +++ b/tcm/synaptics_touchcom_func_touch.h @@ -0,0 +1,235 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Synaptics TouchCom touchscreen driver + * + * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +/** + * @file synaptics_tcm2_func_touch.h + * + * This file declares related APIs and definitions for touch report handling. + */ + +#ifndef _SYNAPTICS_TOUCHCOM_TOUCH_FUNCS_H_ +#define _SYNAPTICS_TOUCHCOM_TOUCH_FUNCS_H_ + +#include "synaptics_touchcom_core_dev.h" + +/** + * @section: Types of Object Reported + * + * List the object classifications + */ +enum object_classification { + LIFT = 0, + FINGER = 1, + GLOVED_OBJECT = 2, + STYLUS = 3, + ERASER = 4, + SMALL_OBJECT = 5, + PALM = 6, + EDGE_TOUCHED = 8, + HOVER_OBJECT = 9, + NOP = -1, +}; + +/** + * @section: Types of Gesture ID + * + * List the gesture ID assigned + */ +enum gesture_classification { + GESTURE_ID_NONE = 0, + GESTURE_ID_DOUBLE_TAP = 1, + GESTURE_ID_SWIPE = 2, + GESTURE_ID_ACTIVE_TAP_AND_HOLD = 7, +}; + +/** + * @section: Codes for Touch Report Configuration + * + * Define the 8-bit codes for the touch report configuration + */ +enum touch_report_code { + /* control flow codes */ + TOUCH_REPORT_END = 0x00, + TOUCH_REPORT_FOREACH_ACTIVE_OBJECT = 0x01, + TOUCH_REPORT_FOREACH_OBJECT = 0x02, + TOUCH_REPORT_FOREACH_END = 0x03, + TOUCH_REPORT_PAD_TO_NEXT_BYTE = 0x04, + /* entity codes */ + TOUCH_REPORT_TIMESTAMP = 0x05, + TOUCH_REPORT_OBJECT_N_INDEX = 0x06, + TOUCH_REPORT_OBJECT_N_CLASSIFICATION = 0x07, + TOUCH_REPORT_OBJECT_N_X_POSITION = 0x08, + TOUCH_REPORT_OBJECT_N_Y_POSITION = 0x09, + TOUCH_REPORT_OBJECT_N_Z = 0x0a, + TOUCH_REPORT_OBJECT_N_X_WIDTH = 0x0b, + TOUCH_REPORT_OBJECT_N_Y_WIDTH = 0x0c, + TOUCH_REPORT_OBJECT_N_TX_POSITION_TIXELS = 0x0d, + TOUCH_REPORT_OBJECT_N_RX_POSITION_TIXELS = 0x0e, + TOUCH_REPORT_0D_BUTTONS_STATE = 0x0f, + TOUCH_REPORT_GESTURE_ID = 0x10, + TOUCH_REPORT_FRAME_RATE = 0x11, + TOUCH_REPORT_POWER_IM = 0x12, + TOUCH_REPORT_CID_IM = 0x13, + TOUCH_REPORT_RAIL_IM = 0x14, + TOUCH_REPORT_CID_VARIANCE_IM = 0x15, + TOUCH_REPORT_NSM_FREQUENCY_INDEX = 0x16, + TOUCH_REPORT_NSM_STATE = 0x17, + TOUCH_REPORT_NUM_OF_ACTIVE_OBJECTS = 0x18, + TOUCH_REPORT_CPU_CYCLES_USED_SINCE_LAST_FRAME = 0x19, + TOUCH_REPORT_FACE_DETECT = 0x1a, + TOUCH_REPORT_GESTURE_DATA = 0x1b, + TOUCH_REPORT_FORCE_MEASUREMENT = 0x1c, + TOUCH_REPORT_FINGERPRINT_AREA_MEET = 0x1d, + TOUCH_REPORT_SENSING_MODE = 0x1e, + TOUCH_REPORT_KNOB_DATA = 0x24, +}; + +/** + * syna_tcm_parse_touch_report() + * + * Traverse through touch report configuration and parse the contents of + * report packet to get the exactly touched data entity from touch reports. + * + * At the end of function, the touched data will be parsed and stored at the + * associated position in struct touch_data_blob. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] report: touch report generated by TouchComm device + * [ in] report_size: size of given report + * [out] touch_data: touch data generated + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_parse_touch_report(struct tcm_dev *tcm_dev, + unsigned char *report, unsigned int report_size, + struct tcm_touch_data_blob *touch_data); + +/** + * syna_tcm_set_touch_report_config() + * + * Setup the format and content of touch report if needed + * + * TouchComm allows to set how touch reports are formatted and what items get + * reported each time a touch report is generated. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] config: the customized report configuration + * [ in] config_size: size of given config + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_set_touch_report_config(struct tcm_dev *tcm_dev, + unsigned char *config, unsigned int config_size); + +/** + * syna_tcm_preserve_touch_report_config() + * + * Retrieve and preserve the current touch report configuration. + * + * The retrieved configuration is stored in touch_config buffer defined + * in struct syna_tcm_dev for later using of touch position parsing. + * + * The touch_config buffer will be allocated internally and its size will + * be updated accordingly. + * + * @param + * [ in] tcm_dev: the device handle + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_preserve_touch_report_config(struct tcm_dev *tcm_dev); + + +/** + * syna_tcm_get_touch_data() + * + * Get data entity from the received report according to bit offset and bit + * length defined in the touch report configuration. + * + * @param + * [ in] report: touch report generated by TouchComm device + * [ in] report_size: size of given report + * [ in] offset: bit offset in the report + * [ in] bits: number of bits representing the data + * [out] data: data parsed + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_get_touch_data(const unsigned char *report, + unsigned int report_size, unsigned int offset, + unsigned int bits, unsigned int *data); + +/** + * syna_tcm_set_custom_touch_data_parsing_callback() + * + * Set up callback function to handle custom touch data. + * + * Once finding the "new" custom entity in touch report, the core library will + * invoke the custom parsing method to handle this "new" code entity. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] p_cb: the callback function pointer + * [ in] p_cbdata: pointer to caller data passed to callback function + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_set_custom_touch_data_parsing_callback(struct tcm_dev *tcm_dev, + tcm_touch_data_parse_callback_t p_cb, void *p_cbdata); + +/** + * syna_tcm_set_custom_gesture_parsing_callback() + * + * Set up callback function to handle the gesture data defined as the following + * code entities + * - TOUCH_REPORT_GESTURE_ID + * - TOUCH_REPORT_GESTURE_DATA + * + * @param + * [ in] tcm_dev: the device handle + * [ in] p_cb: the callback function pointer + * [ in] p_cbdata: pointer to caller data passed to callback function + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int syna_tcm_set_custom_gesture_parsing_callback(struct tcm_dev *tcm_dev, + tcm_gesture_parse_callback_t p_cb, void *p_cbdata); + + +#endif /* end of _SYNAPTICS_TOUCHCOM_TOUCH_FUNCS_H_ */ -- cgit v1.2.3