From c222fce7d89a0af316fc32d43f07017bffff407b Mon Sep 17 00:00:00 2001 From: Alistair Francis Date: Sun, 7 Feb 2021 09:37:17 -0800 Subject: [PATCH] rM2: input: touchscreen: Copy rM2 cyttsp5 driver Copy the rM2 zero-sugar branch (https://github.com/reMarkable/linux/tree/zero-sugar) to the new kernel. Signed-off-by: Alistair Francis --- drivers/input/touchscreen/Kconfig | 219 + drivers/input/touchscreen/Makefile | 47 + drivers/input/touchscreen/cyttsp5_btn.c | 367 + drivers/input/touchscreen/cyttsp5_core.c | 5991 +++++++++++++++++ drivers/input/touchscreen/cyttsp5_debug.c | 393 ++ .../input/touchscreen/cyttsp5_device_access.c | 5134 ++++++++++++++ drivers/input/touchscreen/cyttsp5_devtree.c | 750 +++ drivers/input/touchscreen/cyttsp5_i2c.c | 221 + drivers/input/touchscreen/cyttsp5_loader.c | 1570 +++++ drivers/input/touchscreen/cyttsp5_mt_common.c | 758 +++ drivers/input/touchscreen/cyttsp5_mta.c | 85 + drivers/input/touchscreen/cyttsp5_mtb.c | 93 + drivers/input/touchscreen/cyttsp5_platform.c | 288 + drivers/input/touchscreen/cyttsp5_proximity.c | 553 ++ drivers/input/touchscreen/cyttsp5_regs.h | 1144 ++++ drivers/input/touchscreen/cyttsp5_spi.c | 254 + .../cyttsp5_test_device_access_api.c | 442 ++ include/linux/input/cyttsp5.h | 60 + include/linux/platform_data/cyttsp5.h | 180 + include/uapi/linux/cyttsp5.h | 44 + 20 files changed, 18593 insertions(+) create mode 100644 drivers/input/touchscreen/cyttsp5_btn.c create mode 100644 drivers/input/touchscreen/cyttsp5_core.c create mode 100644 drivers/input/touchscreen/cyttsp5_debug.c create mode 100644 drivers/input/touchscreen/cyttsp5_device_access.c create mode 100644 drivers/input/touchscreen/cyttsp5_devtree.c create mode 100644 drivers/input/touchscreen/cyttsp5_i2c.c create mode 100644 drivers/input/touchscreen/cyttsp5_loader.c create mode 100644 drivers/input/touchscreen/cyttsp5_mt_common.c create mode 100644 drivers/input/touchscreen/cyttsp5_mta.c create mode 100644 drivers/input/touchscreen/cyttsp5_mtb.c create mode 100644 drivers/input/touchscreen/cyttsp5_platform.c create mode 100644 drivers/input/touchscreen/cyttsp5_proximity.c create mode 100644 drivers/input/touchscreen/cyttsp5_regs.h create mode 100644 drivers/input/touchscreen/cyttsp5_spi.c create mode 100644 drivers/input/touchscreen/cyttsp5_test_device_access_api.c create mode 100644 include/linux/input/cyttsp5.h create mode 100644 include/linux/platform_data/cyttsp5.h create mode 100644 include/uapi/linux/cyttsp5.h diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 7823dfaa013a..bad21e2e64d8 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -289,6 +289,225 @@ config TOUCHSCREEN_CYTTSP4_SPI To compile this driver as a module, choose M here: the module will be called cyttsp4_spi. +config TOUCHSCREEN_CYPRESS_CYTTSP5 + tristate "Parade TrueTouch Gen5 Touchscreen Driver" + help + Core driver for Parade TrueTouch(tm) Standard Product + Geneartion5 touchscreen controllers. + + Say Y here if you have a Parade Gen5 touchscreen. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called cyttsp5. + +config TOUCHSCREEN_CYPRESS_CYTTSP5_DEVICETREE_SUPPORT + bool "Enable Device Tree support" + depends on TOUCHSCREEN_CYPRESS_CYTTSP5 && OF + help + Say Y here to enable support for device tree. + + If unsure, say N. + +config TOUCHSCREEN_CYPRESS_CYTTSP5_DEBUG + bool "Enable debug output" + depends on TOUCHSCREEN_CYPRESS_CYTTSP5 + help + Say Y here to enable debug output for Parade TrueTouch(tm) + Standard Product Generation5 drivers set. + + If unsure, say N. + +config TOUCHSCREEN_CYPRESS_CYTTSP5_VDEBUG + bool "Enable verbose debug output" + depends on TOUCHSCREEN_CYPRESS_CYTTSP5_DEBUG + help + Say Y here to enable verbose debug output for Parade TrueTouch(tm) + Standard Product Generation5 drivers set. + + If unsure, say N. + +config TOUCHSCREEN_CYPRESS_CYTTSP5_I2C + tristate "Parade TrueTouch Gen5 I2C" + depends on TOUCHSCREEN_CYPRESS_CYTTSP5 + select I2C + help + Say Y here to enable I2C bus interface to Parade TrueTouch(tm) + Standard Product Generation5 touchscreen controller. + + If unsure, say Y. + + To compile this driver as a module, choose M here: the + module will be called cyttsp5_i2c. + +config TOUCHSCREEN_CYPRESS_CYTTSP5_SPI + tristate "Parade TrueTouch Gen5 SPI" + depends on TOUCHSCREEN_CYPRESS_CYTTSP5 + select SPI + help + Say Y here to enable SPI bus interface to Parade TrueTouch(tm) + Standard Product Generation5 touchscreen controller. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called cyttsp5_spi. + +choice + bool "Parade TrueTouch Gen5 MultiTouch Protocol" + depends on TOUCHSCREEN_CYPRESS_CYTTSP5 + default TOUCHSCREEN_CYPRESS_CYTTSP5_MT_B + help + This option controls which MultiTouch protocol will be used to + report the touch events. + +config TOUCHSCREEN_CYPRESS_CYTTSP5_MT_A + bool "Protocol A" + help + Select to enable MultiTouch touch reporting using protocol A + on Parade TrueTouch(tm) Standard Product Generation4 touchscreen + controller. + +config TOUCHSCREEN_CYPRESS_CYTTSP5_MT_B + bool "Protocol B" + help + Select to enable MultiTouch touch reporting using protocol B + on Parade TrueTouch(tm) Standard Product Generation4 touchscreen + controller. + +endchoice + +config TOUCHSCREEN_CYPRESS_CYTTSP5_BUTTON + bool "Parade TrueTouch Gen5 MultiTouch CapSense Button" + depends on TOUCHSCREEN_CYPRESS_CYTTSP5 + help + Say Y here to enable CapSense reporting on Parade TrueTouch(tm) + Standard Product Generation5 touchscreen controller. + + If unsure, say N. + +config TOUCHSCREEN_CYPRESS_CYTTSP5_PROXIMITY + bool "Parade TrueTouch Gen5 Proximity" + depends on TOUCHSCREEN_CYPRESS_CYTTSP5 + help + Say Y here to enable proximity reporting on Parade TrueTouch(tm) + Standard Product Generation5 touchscreen controller. + + If unsure, say N. + +config TOUCHSCREEN_CYPRESS_CYTTSP5_DEVICE_ACCESS + tristate "Parade TrueTouch Gen5 MultiTouch Device Access" + depends on TOUCHSCREEN_CYPRESS_CYTTSP5 + help + Say Y here to enable Parade TrueTouch(tm) Standard Product + Generation5 touchscreen controller device access module. + + This modules adds an interface to access touchscreen + controller using driver sysfs nodes. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called cyttsp5_device_access. + +config TOUCHSCREEN_CYPRESS_CYTTSP5_DEVICE_ACCESS_API + bool "Enable Device Access kernel API" + depends on TOUCHSCREEN_CYPRESS_CYTTSP5_DEVICE_ACCESS + help + Say Y here to enable Device access kernel API which provides + access to Parade TrueTouch(tm) Standard Product Generation5 + touchscreen controller for other modules. + + If unsure, say N. + +config TOUCHSCREEN_CYPRESS_CYTTSP5_TEST_DEVICE_ACCESS_API + tristate "Simple Test module for Device Access kernel API" + depends on TOUCHSCREEN_CYPRESS_CYTTSP5_DEVICE_ACCESS + depends on TOUCHSCREEN_CYPRESS_CYTTSP5_DEVICE_ACCESS_API + help + Say Y here to enable test module for Device access kernel API. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called cyttsp5_test_device_access_api. + +config TOUCHSCREEN_CYPRESS_CYTTSP5_LOADER + tristate "Parade TrueTouch Gen5 MultiTouch Loader" + depends on TOUCHSCREEN_CYPRESS_CYTTSP5 + help + Say Y here to enable Parade TrueTouch(tm) Standard Product + Generation5 touchscreen controller FW Loader module. + + This module enables support for Firmware upgrade. + + If unsure, say Y. + + To compile this driver as a module, choose M here: the + module will be called cyttsp5_loader. + +config TOUCHSCREEN_CYPRESS_CYTTSP5_PLATFORM_FW_UPGRADE + bool "FW upgrade from header file" + depends on TOUCHSCREEN_CYPRESS_CYTTSP5_LOADER + help + Say Y here to include Parade TrueTouch(tm) Standard Product + Generation5 device Firmware into driver. + + Need proper header file for this. + + If unsure, say N. + +config TOUCHSCREEN_CYPRESS_CYTTSP5_BINARY_FW_UPGRADE + bool "FW upgrade from binary file" + depends on TOUCHSCREEN_CYPRESS_CYTTSP5_LOADER + help + Say Y here to include Parade TrueTouch(tm) Standard Product + Generation5 device Firmware into kernel as binary blob. + + This should be enabled for manual FW upgrade support. + + If unsure, say Y. + +config TOUCHSCREEN_CYPRESS_CYTTSP5_PLATFORM_TTCONFIG_UPGRADE + bool "TT Configuration upgrade from header file" + depends on TOUCHSCREEN_CYPRESS_CYTTSP5_LOADER + help + Say Y here to include Parade TrueTouch(tm) Standard Product + Generation5 device TrueTouch Configuration into kernel itself. + + Need proper header file for this. + + If unsure, say N. + +config TOUCHSCREEN_CYPRESS_CYTTSP5_MANUAL_TTCONFIG_UPGRADE + bool "TT Configuration upgrade via SysFs" + depends on TOUCHSCREEN_CYPRESS_CYTTSP5_LOADER + help + Say Y here to provide a SysFs interface to upgrade TrueTouch + Configuration with a binary configuration file. + + Need proper binary version of config file for this + feature. + + If unsure, say Y. + +config TOUCHSCREEN_CYPRESS_CYTTSP5_DEBUG_MDL + tristate "Parade TrueTouch Gen5 MultiTouch Debug Module" + depends on TOUCHSCREEN_CYPRESS_CYTTSP5 + help + Say Y here to enable Parade TrueTouch(tm) Standard Product + Generation5 Debug module. + + This module adds support for verbose printing touch + information. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called cyttsp5_debug. + + config TOUCHSCREEN_DA9034 tristate "Touchscreen support for Dialog Semiconductor DA9034" depends on PMIC_DA903X diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 28985ca20b7e..2000264617f1 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -114,3 +114,50 @@ obj-$(CONFIG_TOUCHSCREEN_COLIBRI_VF50) += colibri-vf50-ts.o obj-$(CONFIG_TOUCHSCREEN_ROHM_BU21023) += rohm_bu21023.o obj-$(CONFIG_TOUCHSCREEN_RASPBERRYPI_FW) += raspberrypi-ts.o obj-$(CONFIG_TOUCHSCREEN_IQS5XX) += iqs5xx.o +obj-$(CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5) += cyttsp5.o +cyttsp5-y := cyttsp5_core.o cyttsp5_mt_common.o +cyttsp5-$(CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_MT_A) += cyttsp5_mta.o +cyttsp5-$(CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_MT_B) += cyttsp5_mtb.o +cyttsp5-$(CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_BUTTON) += cyttsp5_btn.o +cyttsp5-$(CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_PROXIMITY) += cyttsp5_proximity.o +obj-$(CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_DEVICETREE_SUPPORT) += cyttsp5_devtree.o +ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5 +obj-y += cyttsp5_platform.o +endif +obj-$(CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_I2C) += cyttsp5_i2c.o +obj-$(CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_SPI) += cyttsp5_spi.o +obj-$(CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_DEBUG_MDL) += cyttsp5_debug.o +obj-$(CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_LOADER) += cyttsp5_loader.o +obj-$(CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_DEVICE_ACCESS) += cyttsp5_device_access.o +obj-$(CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_TEST_DEVICE_ACCESS_API) += cyttsp5_test_device_access_api.o + +ifeq ($(CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_DEBUG),y) +CFLAGS_cyttsp5_core.o += -DDEBUG +CFLAGS_cyttsp5_i2c.o += -DDEBUG +CFLAGS_cyttsp5_spi.o += -DDEBUG +CFLAGS_cyttsp5_mta.o += -DDEBUG +CFLAGS_cyttsp5_mtb.o += -DDEBUG +CFLAGS_cyttsp5_mt_common.o += -DDEBUG +CFLAGS_cyttsp5_btn.o += -DDEBUG +CFLAGS_cyttsp5_proximity.o += -DDEBUG +CFLAGS_cyttsp5_device_access.o += -DDEBUG +CFLAGS_cyttsp5_loader.o += -DDEBUG +CFLAGS_cyttsp5_debug.o += -DDEBUG +CFLAGS_cyttsp5_devtree.o += -DDEBUG +CFLAGS_cyttsp5_platform.o += -DDEBUG +endif +ifeq ($(CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_VDEBUG),y) +CFLAGS_cyttsp5_core.o += -DVERBOSE_DEBUG +CFLAGS_cyttsp5_i2c.o += -DVERBOSE_DEBUG +CFLAGS_cyttsp5_spi.o += -DVERBOSE_DEBUG +CFLAGS_cyttsp5_mta.o += -DVERBOSE_DEBUG +CFLAGS_cyttsp5_mtb.o += -DVERBOSE_DEBUG +CFLAGS_cyttsp5_mt_common.o += -DVERBOSE_DEBUG +CFLAGS_cyttsp5_btn.o += -DVERBOSE_DEBUG +CFLAGS_cyttsp5_proximity.o += -DVERBOSE_DEBUG +CFLAGS_cyttsp5_device_access.o += -DVERBOSE_DEBUG +CFLAGS_cyttsp5_loader.o += -DVERBOSE_DEBUG +CFLAGS_cyttsp5_debug.o += -DVERBOSE_DEBUG +CFLAGS_cyttsp5_devtree.o += -DVERBOSE_DEBUG +CFLAGS_cyttsp5_platform.o += -DVERBOSE_DEBUG +endif diff --git a/drivers/input/touchscreen/cyttsp5_btn.c b/drivers/input/touchscreen/cyttsp5_btn.c new file mode 100644 index 000000000000..fed1ee9491db --- /dev/null +++ b/drivers/input/touchscreen/cyttsp5_btn.c @@ -0,0 +1,367 @@ +/* + * cyttsp5_btn.c + * Parade TrueTouch(TM) Standard Product V5 CapSense Reports Module. + * For use with Parade touchscreen controllers. + * Supported parts include: + * CYTMA5XX + * CYTMA448 + * CYTMA445A + * CYTT21XXX + * CYTT31XXX + * + * Copyright (C) 2015 Parade Technologies + * Copyright (C) 2012-2015 Cypress Semiconductor + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Contact Parade Technologies at www.paradetech.com + * + */ + +#include "cyttsp5_regs.h" + +#define CYTTSP5_BTN_NAME "cyttsp5_btn" + +static inline void cyttsp5_btn_key_action(struct cyttsp5_btn_data *bd, + int btn_no, int btn_state) +{ + struct device *dev = bd->dev; + struct cyttsp5_sysinfo *si = bd->si; + + if (!si->btn[btn_no].enabled || + si->btn[btn_no].state == btn_state) + return; + + si->btn[btn_no].state = btn_state; + input_report_key(bd->input, si->btn[btn_no].key_code, btn_state); + input_sync(bd->input); + + dev_dbg(dev, "%s: btn=%d key_code=%d %s\n", __func__, + btn_no, si->btn[btn_no].key_code, + btn_state == CY_BTN_PRESSED ? + "PRESSED" : "RELEASED"); +} + +static void cyttsp5_get_btn_touches(struct cyttsp5_btn_data *bd) +{ + struct cyttsp5_sysinfo *si = bd->si; + int num_btns = si->num_btns; + int cur_btn; + int cur_btn_state; + + for (cur_btn = 0; cur_btn < num_btns; cur_btn++) { + /* Get current button state */ + cur_btn_state = (si->xy_data[0] >> (cur_btn * CY_BITS_PER_BTN)) + & CY_NUM_BTN_EVENT_ID; + + cyttsp5_btn_key_action(bd, cur_btn, cur_btn_state); + } +} + +static void cyttsp5_btn_lift_all(struct cyttsp5_btn_data *bd) +{ + struct cyttsp5_sysinfo *si = bd->si; + int i; + + if (!si || si->num_btns == 0) + return; + + for (i = 0; i < si->num_btns; i++) + cyttsp5_btn_key_action(bd, i, CY_BTN_RELEASED); +} + +#ifdef VERBOSE_DEBUG +static void cyttsp5_log_btn_data(struct cyttsp5_btn_data *bd) +{ + struct device *dev = bd->dev; + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + u8 *pr_buf = cd->pr_buf; + struct cyttsp5_sysinfo *si = bd->si; + int cur; + int value; + + for (cur = 0; cur < si->num_btns; cur++) { + pr_buf[0] = 0; + if (si->xy_data[0] & (1 << cur)) + value = 1; + else + value = 0; + snprintf(pr_buf, CY_MAX_PRBUF_SIZE, "btn_rec[%d]=0x", cur); + snprintf(pr_buf, CY_MAX_PRBUF_SIZE, "%s%X (%02X)", + pr_buf, value, + le16_to_cpu(si->xy_data[1 + cur * 2])); + + dev_vdbg(dev, "%s: %s\n", __func__, pr_buf); + } +} +#endif + +/* read xy_data for all current CapSense button touches */ +static int cyttsp5_xy_worker(struct cyttsp5_btn_data *bd) +{ + struct cyttsp5_sysinfo *si = bd->si; + + /* extract button press/release touch information */ + if (si->num_btns > 0) { + cyttsp5_get_btn_touches(bd); +#ifdef VERBOSE_DEBUG + /* log button press/release touch information */ + cyttsp5_log_btn_data(bd); +#endif + } + + return 0; +} + +static int cyttsp5_btn_attention(struct device *dev) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + struct cyttsp5_btn_data *bd = &cd->bd; + int rc; + + if (bd->si->xy_mode[2] != bd->si->desc.btn_report_id) + return 0; + + /* core handles handshake */ + mutex_lock(&bd->btn_lock); + rc = cyttsp5_xy_worker(bd); + mutex_unlock(&bd->btn_lock); + if (rc < 0) + dev_err(dev, "%s: xy_worker error r=%d\n", __func__, rc); + + return rc; +} + +static int cyttsp5_startup_attention(struct device *dev) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + struct cyttsp5_btn_data *bd = &cd->bd; + + mutex_lock(&bd->btn_lock); + cyttsp5_btn_lift_all(bd); + mutex_unlock(&bd->btn_lock); + + return 0; +} + +static int cyttsp5_btn_suspend_attention(struct device *dev) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + struct cyttsp5_btn_data *bd = &cd->bd; + + mutex_lock(&bd->btn_lock); + cyttsp5_btn_lift_all(bd); + bd->is_suspended = true; + mutex_unlock(&bd->btn_lock); + + pm_runtime_put(dev); + + return 0; +} + +static int cyttsp5_btn_resume_attention(struct device *dev) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + struct cyttsp5_btn_data *bd = &cd->bd; + + pm_runtime_get(dev); + + mutex_lock(&bd->btn_lock); + bd->is_suspended = false; + mutex_unlock(&bd->btn_lock); + + return 0; +} + +static int cyttsp5_btn_open(struct input_dev *input) +{ + struct device *dev = input->dev.parent; + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + struct cyttsp5_btn_data *bd = &cd->bd; + + pm_runtime_get_sync(dev); + + mutex_lock(&bd->btn_lock); + bd->is_suspended = false; + mutex_unlock(&bd->btn_lock); + + dev_vdbg(dev, "%s: setup subscriptions\n", __func__); + + /* set up touch call back */ + _cyttsp5_subscribe_attention(dev, CY_ATTEN_IRQ, CYTTSP5_BTN_NAME, + cyttsp5_btn_attention, CY_MODE_OPERATIONAL); + + /* set up startup call back */ + _cyttsp5_subscribe_attention(dev, CY_ATTEN_STARTUP, CYTTSP5_BTN_NAME, + cyttsp5_startup_attention, 0); + + /* set up suspend call back */ + _cyttsp5_subscribe_attention(dev, CY_ATTEN_SUSPEND, CYTTSP5_BTN_NAME, + cyttsp5_btn_suspend_attention, 0); + + /* set up resume call back */ + _cyttsp5_subscribe_attention(dev, CY_ATTEN_RESUME, CYTTSP5_BTN_NAME, + cyttsp5_btn_resume_attention, 0); + + return 0; +} + +static void cyttsp5_btn_close(struct input_dev *input) +{ + struct device *dev = input->dev.parent; + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + struct cyttsp5_btn_data *bd = &cd->bd; + + _cyttsp5_unsubscribe_attention(dev, CY_ATTEN_IRQ, CYTTSP5_BTN_NAME, + cyttsp5_btn_attention, CY_MODE_OPERATIONAL); + + _cyttsp5_unsubscribe_attention(dev, CY_ATTEN_STARTUP, CYTTSP5_BTN_NAME, + cyttsp5_startup_attention, 0); + + _cyttsp5_unsubscribe_attention(dev, CY_ATTEN_SUSPEND, CYTTSP5_BTN_NAME, + cyttsp5_btn_suspend_attention, 0); + + _cyttsp5_unsubscribe_attention(dev, CY_ATTEN_RESUME, CYTTSP5_BTN_NAME, + cyttsp5_btn_resume_attention, 0); + + mutex_lock(&bd->btn_lock); + if (!bd->is_suspended) { + pm_runtime_put(dev); + bd->is_suspended = true; + } + mutex_unlock(&bd->btn_lock); +} + +static int cyttsp5_setup_input_device(struct device *dev) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + struct cyttsp5_btn_data *bd = &cd->bd; + int i; + int rc; + + dev_vdbg(dev, "%s: Initialize event signals\n", __func__); + __set_bit(EV_KEY, bd->input->evbit); + dev_vdbg(dev, "%s: Number of buttons %d\n", __func__, bd->si->num_btns); + for (i = 0; i < bd->si->num_btns; i++) { + dev_vdbg(dev, "%s: btn:%d keycode:%d\n", + __func__, i, bd->si->btn[i].key_code); + __set_bit(bd->si->btn[i].key_code, bd->input->keybit); + } + + rc = input_register_device(bd->input); + if (rc < 0) + dev_err(dev, "%s: Error, failed register input device r=%d\n", + __func__, rc); + else + bd->input_device_registered = true; + + return rc; +} + +static int cyttsp5_setup_input_attention(struct device *dev) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + struct cyttsp5_btn_data *bd = &cd->bd; + int rc; + + bd->si = _cyttsp5_request_sysinfo(dev); + if (!bd->si) + return -1; + + rc = cyttsp5_setup_input_device(dev); + + _cyttsp5_unsubscribe_attention(dev, CY_ATTEN_STARTUP, CYTTSP5_BTN_NAME, + cyttsp5_setup_input_attention, 0); + + return rc; +} + +int cyttsp5_btn_probe(struct device *dev) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + struct cyttsp5_btn_data *bd = &cd->bd; + struct cyttsp5_platform_data *pdata = dev_get_platdata(dev); + struct cyttsp5_btn_platform_data *btn_pdata; + int rc = 0; + + if (!pdata || !pdata->btn_pdata) { + dev_err(dev, "%s: Missing platform data\n", __func__); + rc = -ENODEV; + goto error_no_pdata; + } + btn_pdata = pdata->btn_pdata; + + mutex_init(&bd->btn_lock); + bd->dev = dev; + bd->pdata = btn_pdata; + + /* Create the input device and register it. */ + dev_vdbg(dev, "%s: Create the input device and register it\n", + __func__); + bd->input = input_allocate_device(); + if (!bd->input) { + dev_err(dev, "%s: Error, failed to allocate input device\n", + __func__); + rc = -ENODEV; + goto error_alloc_failed; + } + + if (bd->pdata->inp_dev_name) + bd->input->name = bd->pdata->inp_dev_name; + else + bd->input->name = CYTTSP5_BTN_NAME; + scnprintf(bd->phys, sizeof(bd->phys), "%s/input%d", dev_name(dev), + cd->phys_num++); + bd->input->phys = bd->phys; + bd->input->dev.parent = bd->dev; + bd->input->open = cyttsp5_btn_open; + bd->input->close = cyttsp5_btn_close; + input_set_drvdata(bd->input, bd); + + /* get sysinfo */ + bd->si = _cyttsp5_request_sysinfo(dev); + + if (bd->si) { + rc = cyttsp5_setup_input_device(dev); + if (rc) + goto error_init_input; + } else { + dev_err(dev, "%s: Fail get sysinfo pointer from core p=%p\n", + __func__, bd->si); + _cyttsp5_subscribe_attention(dev, CY_ATTEN_STARTUP, + CYTTSP5_BTN_NAME, cyttsp5_setup_input_attention, 0); + } + + return 0; + +error_init_input: + input_free_device(bd->input); +error_alloc_failed: +error_no_pdata: + dev_err(dev, "%s failed.\n", __func__); + return rc; +} + +int cyttsp5_btn_release(struct device *dev) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + struct cyttsp5_btn_data *bd = &cd->bd; + + if (bd->input_device_registered) { + input_unregister_device(bd->input); + } else { + input_free_device(bd->input); + _cyttsp5_unsubscribe_attention(dev, CY_ATTEN_STARTUP, + CYTTSP5_BTN_NAME, cyttsp5_setup_input_attention, 0); + } + + return 0; +} diff --git a/drivers/input/touchscreen/cyttsp5_core.c b/drivers/input/touchscreen/cyttsp5_core.c new file mode 100644 index 000000000000..8dcf06dd2175 --- /dev/null +++ b/drivers/input/touchscreen/cyttsp5_core.c @@ -0,0 +1,5991 @@ +/* + * cyttsp5_core.c + * Parade TrueTouch(TM) Standard Product V5 Core Module. + * For use with Parade touchscreen controllers. + * Supported parts include: + * CYTMA5XX + * CYTMA448 + * CYTMA445A + * CYTT21XXX + * CYTT31XXX + * + * Copyright (C) 2015 Parade Technologies + * Copyright (C) 2012-2015 Cypress Semiconductor + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Contact Parade Technologies at www.paradetech.com + * + */ + +#include "cyttsp5_regs.h" +#include +#include + +#define CY_CORE_STARTUP_RETRY_COUNT 3 + +MODULE_FIRMWARE(CY_FW_FILE_NAME); + +static const char *cy_driver_core_name = CYTTSP5_CORE_NAME; +static const char *cy_driver_core_version = CY_DRIVER_VERSION; +static const char *cy_driver_core_date = CY_DRIVER_DATE; + +struct cyttsp5_hid_field { + int report_count; + int report_size; + int size; /* report_count * report_size */ + int offset; + int data_type; + int logical_min; + int logical_max; + /* Usage Page (Hi 16 bit) + Usage (Lo 16 bit) */ + u32 usage_page; + u32 collection_usage_pages[CY_HID_MAX_COLLECTIONS]; + struct cyttsp5_hid_report *report; + bool record_field; +}; + +struct cyttsp5_hid_report { + u8 id; + u8 type; + int size; + struct cyttsp5_hid_field *fields[CY_HID_MAX_FIELDS]; + int num_fields; + int record_field_index; + int header_size; + int record_size; + u32 usage_page; +}; + +struct atten_node { + struct list_head node; + char *id; + struct device *dev; + + int (*func)(struct device *); + int mode; +}; + +struct param_node { + struct list_head node; + u8 id; + u32 value; + u8 size; +}; + +struct module_node { + struct list_head node; + struct cyttsp5_module *module; + void *data; +}; + +struct cyttsp5_hid_cmd { + u8 opcode; + u8 report_type; + union { + u8 report_id; + u8 power_state; + }; + u8 has_data_register; + size_t write_length; + u8 *write_buf; + u8 *read_buf; + u8 wait_interrupt; + u8 reset_cmd; + u16 timeout_ms; +}; + +struct cyttsp5_hid_output { + u8 cmd_type; + u16 length; + u8 command_code; + size_t write_length; + u8 *write_buf; + u8 novalidate; + u8 reset_expected; + u16 timeout_ms; +}; + +#define SET_CMD_OPCODE(byte, opcode) SET_CMD_LOW(byte, opcode) +#define SET_CMD_REPORT_TYPE(byte, type) SET_CMD_HIGH(byte, ((type) << 4)) +#define SET_CMD_REPORT_ID(byte, id) SET_CMD_LOW(byte, id) + +#define HID_OUTPUT_APP_COMMAND(command) \ + .cmd_type = HID_OUTPUT_CMD_APP, \ + .command_code = command + +#define HID_OUTPUT_BL_COMMAND(command) \ + .cmd_type = HID_OUTPUT_CMD_BL, \ + .command_code = command + +#ifdef VERBOSE_DEBUG +void cyttsp5_pr_buf(struct device *dev, u8 *dptr, int size, + const char *data_name) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + u8 *pr_buf = cd->pr_buf; + int i, k; + const char fmt[] = "%02X "; + int max; + + if (!size) + return; + + max = (CY_MAX_PRBUF_SIZE - 1) - sizeof(CY_PR_TRUNCATED); + + pr_buf[0] = 0; + for (i = k = 0; i < size && k < max; i++, k += 3) + scnprintf(pr_buf + k, CY_MAX_PRBUF_SIZE, fmt, dptr[i]); + + if (size) + dev_vdbg(dev, "%s: %s[0..%d]=%s%s\n", __func__, data_name, + size - 1, pr_buf, size <= max ? "" : CY_PR_TRUNCATED); + else + dev_vdbg(dev, "%s: %s[]\n", __func__, data_name); +} +EXPORT_SYMBOL_GPL(cyttsp5_pr_buf); +#endif + +#ifdef TTHE_TUNER_SUPPORT +static int tthe_print(struct cyttsp5_core_data *cd, u8 *buf, int buf_len, + const u8 *data_name) +{ + int len = strlen(data_name); + int i, n; + u8 *p; + int remain; + + mutex_lock(&cd->tthe_lock); + if (!cd->tthe_buf) + goto exit; + + if (cd->tthe_buf_len + (len + buf_len) > CY_MAX_PRBUF_SIZE) + goto exit; + + if (len + buf_len == 0) + goto exit; + + remain = CY_MAX_PRBUF_SIZE - cd->tthe_buf_len; + if (remain < len) + len = remain; + + p = cd->tthe_buf + cd->tthe_buf_len; + memcpy(p, data_name, len); + cd->tthe_buf_len += len; + p += len; + remain -= len; + + *p = 0; + for (i = 0; i < buf_len; i++) { + n = scnprintf(p, remain, "%02X ", buf[i]); + if (!n) + break; + p += n; + remain -= n; + cd->tthe_buf_len += n; + } + + n = scnprintf(p, remain, "\n"); + if (!n) + cd->tthe_buf[cd->tthe_buf_len] = 0; + cd->tthe_buf_len += n; + wake_up(&cd->wait_q); +exit: + mutex_unlock(&cd->tthe_lock); + return 0; +} + +static int _cyttsp5_request_tthe_print(struct device *dev, u8 *buf, + int buf_len, const u8 *data_name) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + return tthe_print(cd, buf, buf_len, data_name); +} +#endif + +/* + * cyttsp5_platform_detect_read() + * + * This function is passed to platform detect + * function to perform a read operation + */ +static int cyttsp5_platform_detect_read(struct device *dev, void *buf, int size) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + return cyttsp5_adap_read_default(cd, buf, size); +} + +/* Must be called with cd->hid_report_lock acquired */ +static struct cyttsp5_hid_report *cyttsp5_get_hid_report_( + struct cyttsp5_core_data *cd, u8 report_type, u8 report_id, + bool create) +{ + struct cyttsp5_hid_report *report = NULL; + int i; + + /* Look for created reports */ + for (i = 0; i < cd->num_hid_reports; i++) { + if (cd->hid_reports[i]->type == report_type + && cd->hid_reports[i]->id == report_id) { + return cd->hid_reports[i]; + } + } + + /* Create a new report */ + if (create && cd->num_hid_reports < CY_HID_MAX_REPORTS) { + report = kzalloc(sizeof(struct cyttsp5_hid_report), + GFP_KERNEL); + if (!report) + return NULL; + + report->type = report_type; + report->id = report_id; + cd->hid_reports[cd->num_hid_reports++] = report; + } + + return report; +} + +/* Must be called with cd->hid_report_lock acquired */ +static void cyttsp5_free_hid_reports_(struct cyttsp5_core_data *cd) +{ + struct cyttsp5_hid_report *report; + int i, j; + + for (i = 0; i < cd->num_hid_reports; i++) { + report = cd->hid_reports[i]; + for (j = 0; j < report->num_fields; j++) + kfree(report->fields[j]); + kfree(report); + cd->hid_reports[i] = NULL; + } + + cd->num_hid_reports = 0; +} + +static void cyttsp5_free_hid_reports(struct cyttsp5_core_data *cd) +{ + mutex_lock(&cd->hid_report_lock); + cyttsp5_free_hid_reports_(cd); + mutex_unlock(&cd->hid_report_lock); +} + +/* Must be called with cd->hid_report_lock acquired */ +static struct cyttsp5_hid_field *cyttsp5_create_hid_field_( + struct cyttsp5_hid_report *report) +{ + struct cyttsp5_hid_field *field; + + if (!report) + return NULL; + + if (report->num_fields == CY_HID_MAX_FIELDS) + return NULL; + + field = kzalloc(sizeof(struct cyttsp5_hid_field), GFP_KERNEL); + if (!field) + return NULL; + + field->report = report; + + report->fields[report->num_fields++] = field; + + return field; +} + +static int cyttsp5_add_parameter(struct cyttsp5_core_data *cd, + u8 param_id, u32 param_value, u8 param_size) +{ + struct param_node *param, *param_new; + + /* Check if parameter exists */ + spin_lock(&cd->spinlock); + list_for_each_entry(param, &cd->param_list, node) { + if (param->id == param_id) { + /* Update parameter */ + param->value = param_value; + dev_vdbg(cd->dev, "%s: Update parameter id:%d value:%d size:%d\n", + __func__, param_id, param_value, param_size); + goto exit_unlock; + } + } + spin_unlock(&cd->spinlock); + + param_new = kzalloc(sizeof(*param_new), GFP_KERNEL); + if (!param_new) + return -ENOMEM; + + param_new->id = param_id; + param_new->value = param_value; + param_new->size = param_size; + + dev_vdbg(cd->dev, "%s: Add parameter id:%d value:%d size:%d\n", + __func__, param_id, param_value, param_size); + + spin_lock(&cd->spinlock); + list_add(¶m_new->node, &cd->param_list); +exit_unlock: + spin_unlock(&cd->spinlock); + + return 0; +} + +int request_exclusive(struct cyttsp5_core_data *cd, void *ownptr, + int timeout_ms) +{ + int t = msecs_to_jiffies(timeout_ms); + bool with_timeout = (timeout_ms != 0); + + mutex_lock(&cd->system_lock); + if (!cd->exclusive_dev && cd->exclusive_waits == 0) { + cd->exclusive_dev = ownptr; + goto exit; + } + + cd->exclusive_waits++; +wait: + mutex_unlock(&cd->system_lock); + if (with_timeout) { + t = wait_event_timeout(cd->wait_q, !cd->exclusive_dev, t); + if (IS_TMO(t)) { + dev_err(cd->dev, "%s: tmo waiting exclusive access\n", + __func__); + return -ETIME; + } + } else { + wait_event(cd->wait_q, !cd->exclusive_dev); + } + mutex_lock(&cd->system_lock); + if (cd->exclusive_dev) + goto wait; + cd->exclusive_dev = ownptr; + cd->exclusive_waits--; +exit: + mutex_unlock(&cd->system_lock); + dev_vdbg(cd->dev, "%s: request_exclusive ok=%p\n", + __func__, ownptr); + + return 0; +} + +static int release_exclusive_(struct cyttsp5_core_data *cd, void *ownptr) +{ + if (cd->exclusive_dev != ownptr) + return -EINVAL; + + dev_vdbg(cd->dev, "%s: exclusive_dev %p freed\n", + __func__, cd->exclusive_dev); + cd->exclusive_dev = NULL; + wake_up(&cd->wait_q); + return 0; +} + +/* + * returns error if was not owned + */ +int release_exclusive(struct cyttsp5_core_data *cd, void *ownptr) +{ + int rc; + + mutex_lock(&cd->system_lock); + rc = release_exclusive_(cd, ownptr); + mutex_unlock(&cd->system_lock); + + return rc; +} + +static int cyttsp5_hid_exec_cmd_(struct cyttsp5_core_data *cd, + struct cyttsp5_hid_cmd *hid_cmd) +{ + int rc; + u8 *cmd; + u8 cmd_length; + u8 cmd_offset = 0; + + cmd_length = 2 /* command register */ + + 2 /* command */ + + (hid_cmd->report_id >= 0XF ? 1 : 0) /* Report ID */ + + (hid_cmd->has_data_register ? 2 : 0) /* Data register */ + + hid_cmd->write_length; /* Data length */ + + cmd = kzalloc(cmd_length, GFP_KERNEL); + if (!cmd) + return -ENOMEM; + + /* Set Command register */ + memcpy(&cmd[cmd_offset], &cd->hid_desc.command_register, + sizeof(cd->hid_desc.command_register)); + cmd_offset += sizeof(cd->hid_desc.command_register); + + /* Set Command */ + SET_CMD_REPORT_TYPE(cmd[cmd_offset], hid_cmd->report_type); + + if (hid_cmd->report_id >= 0XF) + SET_CMD_REPORT_ID(cmd[cmd_offset], 0xF); + else + SET_CMD_REPORT_ID(cmd[cmd_offset], hid_cmd->report_id); + cmd_offset++; + + SET_CMD_OPCODE(cmd[cmd_offset], hid_cmd->opcode); + cmd_offset++; + + if (hid_cmd->report_id >= 0XF) { + cmd[cmd_offset] = hid_cmd->report_id; + cmd_offset++; + } + + /* Set Data register */ + if (hid_cmd->has_data_register) { + memcpy(&cmd[cmd_offset], &cd->hid_desc.data_register, + sizeof(cd->hid_desc.data_register)); + cmd_offset += sizeof(cd->hid_desc.data_register); + } + + /* Set Data */ + if (hid_cmd->write_length && hid_cmd->write_buf) { + memcpy(&cmd[cmd_offset], hid_cmd->write_buf, + hid_cmd->write_length); + cmd_offset += hid_cmd->write_length; + } + + rc = cyttsp5_adap_write_read_specific(cd, cmd_length, cmd, + hid_cmd->read_buf); + if (rc) + dev_err(cd->dev, "%s: Fail cyttsp5_adap_transfer\n", __func__); + + kfree(cmd); + return rc; +} + +static int cyttsp5_hid_exec_cmd_and_wait_(struct cyttsp5_core_data *cd, + struct cyttsp5_hid_cmd *hid_cmd) +{ + int rc; + int t; + u16 timeout_ms; + int *cmd_state; + + if (hid_cmd->reset_cmd) + cmd_state = &cd->hid_reset_cmd_state; + else + cmd_state = &cd->hid_cmd_state; + + if (hid_cmd->wait_interrupt) { + mutex_lock(&cd->system_lock); + *cmd_state = 1; + mutex_unlock(&cd->system_lock); + } + + rc = cyttsp5_hid_exec_cmd_(cd, hid_cmd); + if (rc) { + if (hid_cmd->wait_interrupt) + goto error; + + goto exit; + } + + if (!hid_cmd->wait_interrupt) + goto exit; + + if (hid_cmd->timeout_ms) + timeout_ms = hid_cmd->timeout_ms; + else + timeout_ms = CY_HID_RESET_TIMEOUT; + + t = wait_event_timeout(cd->wait_q, (*cmd_state == 0), + msecs_to_jiffies(timeout_ms)); + if (IS_TMO(t)) { + dev_err(cd->dev, "%s: HID output cmd execution timed out\n", + __func__); + rc = -ETIME; + goto error; + } + + goto exit; + +error: + mutex_lock(&cd->system_lock); + *cmd_state = 0; + mutex_unlock(&cd->system_lock); + +exit: + return rc; +} + +static int cyttsp5_hid_cmd_reset_(struct cyttsp5_core_data *cd) +{ + struct cyttsp5_hid_cmd hid_cmd = { + .opcode = HID_CMD_RESET, + .wait_interrupt = 1, + .reset_cmd = 1, + .timeout_ms = CY_HID_RESET_TIMEOUT, + }; + + return cyttsp5_hid_exec_cmd_and_wait_(cd, &hid_cmd); +} + +static int cyttsp5_hid_cmd_reset(struct cyttsp5_core_data *cd) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = cyttsp5_hid_cmd_reset_(cd); + + if (release_exclusive(cd, cd->dev) < 0) + dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); + + return rc; +} + +static int cyttsp5_hid_cmd_set_power_(struct cyttsp5_core_data *cd, + u8 power_state) +{ + int rc; + struct cyttsp5_hid_cmd hid_cmd = { + .opcode = HID_CMD_SET_POWER, + .wait_interrupt = 1, + .timeout_ms = CY_HID_SET_POWER_TIMEOUT, + }; + hid_cmd.power_state = power_state; + + rc = cyttsp5_hid_exec_cmd_and_wait_(cd, &hid_cmd); + if (rc) { + dev_err(cd->dev, "%s: Failed to set power to state:%d\n", + __func__, power_state); + return rc; + } + + /* validate */ + if ((cd->response_buf[2] != HID_RESPONSE_REPORT_ID) + || ((cd->response_buf[3] & 0x3) != power_state) + || ((cd->response_buf[4] & 0xF) != HID_CMD_SET_POWER)) + rc = -EINVAL; + + return rc; +} + +static int cyttsp5_hid_cmd_set_power(struct cyttsp5_core_data *cd, + u8 power_state) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = cyttsp5_hid_cmd_set_power_(cd, power_state); + + if (release_exclusive(cd, cd->dev) < 0) + dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); + + return rc; +} + +static const u16 crc_table[16] = { + 0x0000, 0x1021, 0x2042, 0x3063, + 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, + 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, +}; + +static u16 _cyttsp5_compute_crc(u8 *buf, u32 size) +{ + u16 remainder = 0xFFFF; + u16 xor_mask = 0x0000; + u32 index; + u32 byte_value; + u32 table_index; + u32 crc_bit_width = sizeof(u16) * 8; + + /* Divide the message by polynomial, via the table. */ + for (index = 0; index < size; index++) { + byte_value = buf[index]; + table_index = ((byte_value >> 4) & 0x0F) + ^ (remainder >> (crc_bit_width - 4)); + remainder = crc_table[table_index] ^ (remainder << 4); + table_index = (byte_value & 0x0F) + ^ (remainder >> (crc_bit_width - 4)); + remainder = crc_table[table_index] ^ (remainder << 4); + } + + /* Perform the final remainder CRC. */ + return remainder ^ xor_mask; +} + +static int cyttsp5_hid_output_validate_bl_response( + struct cyttsp5_core_data *cd, + struct cyttsp5_hid_output *hid_output) +{ + u16 size; + u16 crc; + u8 status; + + size = get_unaligned_le16(&cd->response_buf[0]); + + if (hid_output->reset_expected && !size) + return 0; + + if (cd->response_buf[HID_OUTPUT_RESPONSE_REPORT_OFFSET] + != HID_BL_RESPONSE_REPORT_ID) { + dev_err(cd->dev, "%s: HID output response, wrong report_id\n", + __func__); + return -EPROTO; + } + + if (cd->response_buf[4] != HID_OUTPUT_BL_SOP) { + dev_err(cd->dev, "%s: HID output response, wrong SOP\n", + __func__); + return -EPROTO; + } + + if (cd->response_buf[size - 1] != HID_OUTPUT_BL_EOP) { + dev_err(cd->dev, "%s: HID output response, wrong EOP\n", + __func__); + return -EPROTO; + } + + crc = _cyttsp5_compute_crc(&cd->response_buf[4], size - 7); + if (cd->response_buf[size - 3] != LOW_BYTE(crc) + || cd->response_buf[size - 2] != HI_BYTE(crc)) { + dev_err(cd->dev, "%s: HID output response, wrong CRC 0x%X\n", + __func__, crc); + return -EPROTO; + } + + status = cd->response_buf[5]; + if (status) { + dev_err(cd->dev, "%s: HID output response, ERROR:%d\n", + __func__, status); + return -EPROTO; + } + + return 0; +} + +static int cyttsp5_hid_output_validate_app_response( + struct cyttsp5_core_data *cd, + struct cyttsp5_hid_output *hid_output) +{ + int command_code; + u16 size; + + size = get_unaligned_le16(&cd->response_buf[0]); + + if (hid_output->reset_expected && !size) + return 0; + + if (cd->response_buf[HID_OUTPUT_RESPONSE_REPORT_OFFSET] + != HID_APP_RESPONSE_REPORT_ID) { + dev_err(cd->dev, "%s: HID output response, wrong report_id\n", + __func__); + return -EPROTO; + } + + command_code = cd->response_buf[HID_OUTPUT_RESPONSE_CMD_OFFSET] + & HID_OUTPUT_RESPONSE_CMD_MASK; + if (command_code != hid_output->command_code) { + dev_err(cd->dev, + "%s: HID output response, wrong command_code:%X\n", + __func__, command_code); + return -EPROTO; + } + + return 0; +} + +static void cyttsp5_check_set_parameter(struct cyttsp5_core_data *cd, + struct cyttsp5_hid_output *hid_output, bool raw) +{ + u8 *param_buf; + u32 param_value = 0; + u8 param_size; + u8 param_id; + int i = 0; + + if (!(cd->cpdata->flags & CY_CORE_FLAG_RESTORE_PARAMETERS)) + return; + + /* Check command input for Set Parameter command */ + if (raw && hid_output->length >= 10 && hid_output->length <= 13 + && !memcmp(&hid_output->write_buf[0], + &cd->hid_desc.output_register, + sizeof(cd->hid_desc.output_register)) + && hid_output->write_buf[4] == + HID_APP_OUTPUT_REPORT_ID + && hid_output->write_buf[6] == + HID_OUTPUT_SET_PARAM) + param_buf = &hid_output->write_buf[7]; + else if (!raw && hid_output->cmd_type == HID_OUTPUT_CMD_APP + && hid_output->command_code == HID_OUTPUT_SET_PARAM + && hid_output->write_length >= 3 + && hid_output->write_length <= 6) + param_buf = &hid_output->write_buf[0]; + else + return; + + /* Get parameter ID, size and value */ + param_id = param_buf[0]; + param_size = param_buf[1]; + if (param_size > 4) { + dev_err(cd->dev, "%s: Invalid parameter size\n", __func__); + return; + } + + param_buf = ¶m_buf[2]; + while (i < param_size) + param_value += *(param_buf++) << (8 * i++); + + /* Check command response for Set Parameter command */ + if (cd->response_buf[2] != HID_APP_RESPONSE_REPORT_ID + || (cd->response_buf[4] & HID_OUTPUT_CMD_MASK) != + HID_OUTPUT_SET_PARAM + || cd->response_buf[5] != param_id + || cd->response_buf[6] != param_size) { + dev_err(cd->dev, "%s: Set Parameter command not successful\n", + __func__); + return; + } + + cyttsp5_add_parameter(cd, param_id, param_value, param_size); +} + +static void cyttsp5_check_command(struct cyttsp5_core_data *cd, + struct cyttsp5_hid_output *hid_output, bool raw) +{ + cyttsp5_check_set_parameter(cd, hid_output, raw); +} + +static int cyttsp5_hid_output_validate_response(struct cyttsp5_core_data *cd, + struct cyttsp5_hid_output *hid_output) +{ + if (hid_output->cmd_type == HID_OUTPUT_CMD_BL) + return cyttsp5_hid_output_validate_bl_response(cd, hid_output); + + return cyttsp5_hid_output_validate_app_response(cd, hid_output); + +} + +static int cyttsp5_hid_send_output_user_(struct cyttsp5_core_data *cd, + struct cyttsp5_hid_output *hid_output) +{ + int rc; + + if (!hid_output->length || !hid_output->write_buf) + return -EINVAL; + + rc = cyttsp5_adap_write_read_specific(cd, hid_output->length, + hid_output->write_buf, NULL); + if (rc) + dev_err(cd->dev, "%s: Fail cyttsp5_adap_transfer\n", __func__); + + return rc; +} + +static int cyttsp5_hid_send_output_user_and_wait_(struct cyttsp5_core_data *cd, + struct cyttsp5_hid_output *hid_output) +{ + int rc; + int t; + + mutex_lock(&cd->system_lock); + cd->hid_cmd_state = HID_OUTPUT_USER_CMD + 1; + mutex_unlock(&cd->system_lock); + + rc = cyttsp5_hid_send_output_user_(cd, hid_output); + if (rc) + goto error; + + t = wait_event_timeout(cd->wait_q, (cd->hid_cmd_state == 0), + msecs_to_jiffies(CY_HID_OUTPUT_USER_TIMEOUT)); + if (IS_TMO(t)) { + dev_err(cd->dev, "%s: HID output cmd execution timed out\n", + __func__); + rc = -ETIME; + goto error; + } + + cyttsp5_check_command(cd, hid_output, true); + + goto exit; + +error: + mutex_lock(&cd->system_lock); + cd->hid_cmd_state = 0; + mutex_unlock(&cd->system_lock); + +exit: + return rc; +} + +static int cyttsp5_hid_send_output_(struct cyttsp5_core_data *cd, + struct cyttsp5_hid_output *hid_output) +{ + int rc; + u8 *cmd; + u16 length; + u8 report_id; + u8 cmd_offset = 0; + u16 crc; + u8 cmd_allocated = 0; + + switch (hid_output->cmd_type) { + case HID_OUTPUT_CMD_APP: + report_id = HID_APP_OUTPUT_REPORT_ID; + length = 5; + break; + case HID_OUTPUT_CMD_BL: + report_id = HID_BL_OUTPUT_REPORT_ID; + length = 11 /* 5 + SOP + LEN(2) + CRC(2) + EOP */; + break; + default: + return -EINVAL; + } + + length += hid_output->write_length; + + if (length + 2 > CYTTSP5_PREALLOCATED_CMD_BUFFER) { + cmd = kzalloc(length + 2, GFP_KERNEL); + if (!cmd) + return -ENOMEM; + cmd_allocated = 1; + } else { + cmd = cd->cmd_buf; + } + + /* Set Output register */ + memcpy(&cmd[cmd_offset], &cd->hid_desc.output_register, + sizeof(cd->hid_desc.output_register)); + cmd_offset += sizeof(cd->hid_desc.output_register); + + cmd[cmd_offset++] = LOW_BYTE(length); + cmd[cmd_offset++] = HI_BYTE(length); + cmd[cmd_offset++] = report_id; + cmd[cmd_offset++] = 0x0; /* reserved */ + if (hid_output->cmd_type == HID_OUTPUT_CMD_BL) + cmd[cmd_offset++] = HID_OUTPUT_BL_SOP; + cmd[cmd_offset++] = hid_output->command_code; + + /* Set Data Length for bootloader */ + if (hid_output->cmd_type == HID_OUTPUT_CMD_BL) { + cmd[cmd_offset++] = LOW_BYTE(hid_output->write_length); + cmd[cmd_offset++] = HI_BYTE(hid_output->write_length); + } + /* Set Data */ + if (hid_output->write_length && hid_output->write_buf) { + memcpy(&cmd[cmd_offset], hid_output->write_buf, + hid_output->write_length); + cmd_offset += hid_output->write_length; + } + if (hid_output->cmd_type == HID_OUTPUT_CMD_BL) { + crc = _cyttsp5_compute_crc(&cmd[6], + hid_output->write_length + 4); + cmd[cmd_offset++] = LOW_BYTE(crc); + cmd[cmd_offset++] = HI_BYTE(crc); + cmd[cmd_offset++] = HID_OUTPUT_BL_EOP; + } + + cyttsp5_pr_buf(cd->dev, cmd, length + 2, "command"); + rc = cyttsp5_adap_write_read_specific(cd, length + 2, cmd, NULL); + if (rc) + dev_err(cd->dev, "%s: Fail cyttsp5_adap_transfer\n", __func__); + + if (cmd_allocated) + kfree(cmd); + return rc; +} + +static int cyttsp5_hid_send_output_and_wait_(struct cyttsp5_core_data *cd, + struct cyttsp5_hid_output *hid_output) +{ + int rc; + int t; +#ifdef VERBOSE_DEBUG + u16 size; +#endif + u16 timeout_ms; + + mutex_lock(&cd->system_lock); + cd->hid_cmd_state = hid_output->command_code + 1; + mutex_unlock(&cd->system_lock); + + if (hid_output->timeout_ms) + timeout_ms = hid_output->timeout_ms; + else + timeout_ms = CY_HID_OUTPUT_TIMEOUT; + + rc = cyttsp5_hid_send_output_(cd, hid_output); + if (rc) + goto error; + + + t = wait_event_timeout(cd->wait_q, (cd->hid_cmd_state == 0), + msecs_to_jiffies(timeout_ms)); + if (IS_TMO(t)) { + dev_err(cd->dev, "%s: HID output cmd execution timed out\n", + __func__); + rc = -ETIME; + goto error; + } + + if (!hid_output->novalidate) + rc = cyttsp5_hid_output_validate_response(cd, hid_output); + + cyttsp5_check_command(cd, hid_output, false); + +#ifdef VERBOSE_DEBUG + size = get_unaligned_le16(&cd->response_buf[0]); + cyttsp5_pr_buf(cd->dev, cd->response_buf, size, "return_buf"); +#endif + + goto exit; + +error: + mutex_lock(&cd->system_lock); + cd->hid_cmd_state = 0; + mutex_unlock(&cd->system_lock); +exit: + return rc; +} + +static int cyttsp5_hid_output_null_(struct cyttsp5_core_data *cd) +{ + struct cyttsp5_hid_output hid_output = { + HID_OUTPUT_APP_COMMAND(HID_OUTPUT_NULL), + }; + + return cyttsp5_hid_send_output_and_wait_(cd, &hid_output); +} + +static int cyttsp5_hid_output_null(struct cyttsp5_core_data *cd) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = cyttsp5_hid_output_null_(cd); + + if (release_exclusive(cd, cd->dev) < 0) + dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); + + return rc; +} + +static int cyttsp5_hid_output_start_bootloader_(struct cyttsp5_core_data *cd) +{ + struct cyttsp5_hid_output hid_output = { + HID_OUTPUT_APP_COMMAND(HID_OUTPUT_START_BOOTLOADER), + .timeout_ms = CY_HID_OUTPUT_START_BOOTLOADER_TIMEOUT, + .reset_expected = 1, + }; + + return cyttsp5_hid_send_output_and_wait_(cd, &hid_output); +} + +static int cyttsp5_hid_output_start_bootloader(struct cyttsp5_core_data *cd) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = cyttsp5_hid_output_start_bootloader_(cd); + + if (release_exclusive(cd, cd->dev) < 0) + dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); + + return rc; +} + +static int _cyttsp5_request_hid_output_start_bl(struct device *dev, int protect) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return cyttsp5_hid_output_start_bootloader(cd); + + return cyttsp5_hid_output_start_bootloader_(cd); +} + +static void cyttsp5_si_get_cydata(struct cyttsp5_core_data *cd) +{ + struct cyttsp5_cydata *cydata = &cd->sysinfo.cydata; + struct cyttsp5_cydata_dev *cydata_dev = + (struct cyttsp5_cydata_dev *) + &cd->response_buf[HID_SYSINFO_CYDATA_OFFSET]; + + cydata->pip_ver_major = cydata_dev->pip_ver_major; + cydata->pip_ver_minor = cydata_dev->pip_ver_minor; + cydata->bl_ver_major = cydata_dev->bl_ver_major; + cydata->bl_ver_minor = cydata_dev->bl_ver_minor; + cydata->fw_ver_major = cydata_dev->fw_ver_major; + cydata->fw_ver_minor = cydata_dev->fw_ver_minor; + + cydata->fw_pid = get_unaligned_le16(&cydata_dev->fw_pid); + cydata->fw_ver_conf = get_unaligned_le16(&cydata_dev->fw_ver_conf); + cydata->post_code = get_unaligned_le16(&cydata_dev->post_code); + cydata->revctrl = get_unaligned_le32(&cydata_dev->revctrl); + cydata->jtag_id_l = get_unaligned_le16(&cydata_dev->jtag_si_id_l); + cydata->jtag_id_h = get_unaligned_le16(&cydata_dev->jtag_si_id_h); + + memcpy(cydata->mfg_id, cydata_dev->mfg_id, CY_NUM_MFGID); + + cyttsp5_pr_buf(cd->dev, (u8 *)cydata_dev, + sizeof(struct cyttsp5_cydata_dev), "sysinfo_cydata"); +} + +static void cyttsp5_si_get_sensing_conf_data(struct cyttsp5_core_data *cd) +{ + struct cyttsp5_sensing_conf_data *scd = &cd->sysinfo.sensing_conf_data; + struct cyttsp5_sensing_conf_data_dev *scd_dev = + (struct cyttsp5_sensing_conf_data_dev *) + &cd->response_buf[HID_SYSINFO_SENSING_OFFSET]; + + scd->electrodes_x = scd_dev->electrodes_x; + scd->electrodes_y = scd_dev->electrodes_y; + scd->origin_x = scd_dev->origin_x; + scd->origin_y = scd_dev->origin_y; + + /* PIP 1.4 (001-82649 *Q) add X_IS_TX bit in X_ORG */ + if (scd->origin_x & 0x02) { + scd->tx_num = scd->electrodes_x; + scd->rx_num = scd->electrodes_y; + } else { + scd->tx_num = scd->electrodes_y; + scd->rx_num = scd->electrodes_x; + } + + scd->panel_id = scd_dev->panel_id; + scd->btn = scd_dev->btn; + scd->scan_mode = scd_dev->scan_mode; + scd->max_tch = scd_dev->max_num_of_tch_per_refresh_cycle; + + scd->res_x = get_unaligned_le16(&scd_dev->res_x); + scd->res_y = get_unaligned_le16(&scd_dev->res_y); + scd->max_z = get_unaligned_le16(&scd_dev->max_z); + scd->len_x = get_unaligned_le16(&scd_dev->len_x); + scd->len_y = get_unaligned_le16(&scd_dev->len_y); + + cyttsp5_pr_buf(cd->dev, (u8 *)scd_dev, + sizeof(struct cyttsp5_sensing_conf_data_dev), + "sensing_conf_data"); +} + +static int cyttsp5_si_setup(struct cyttsp5_core_data *cd) +{ + struct cyttsp5_sysinfo *si = &cd->sysinfo; + int max_tch = si->sensing_conf_data.max_tch; + + if (!si->xy_data) + si->xy_data = kzalloc(max_tch * si->desc.tch_record_size, + GFP_KERNEL); + if (!si->xy_data) + return -ENOMEM; + + if (!si->xy_mode) + si->xy_mode = kzalloc(si->desc.tch_header_size, GFP_KERNEL); + if (!si->xy_mode) { + kfree(si->xy_data); + return -ENOMEM; + } + + return 0; +} + +static int cyttsp5_si_get_btn_data(struct cyttsp5_core_data *cd) +{ + struct cyttsp5_sysinfo *si = &cd->sysinfo; + int num_btns = 0; + int num_defined_keys; + u16 *key_table; + int btn; + int i; + int rc = 0; + unsigned int btns = cd->response_buf[HID_SYSINFO_BTN_OFFSET] + & HID_SYSINFO_BTN_MASK; + size_t btn_keys_size; + + dev_vdbg(cd->dev, "%s: get btn data\n", __func__); + + for (i = 0; i < HID_SYSINFO_MAX_BTN; i++) { + if (btns & (1 << i)) + num_btns++; + } + si->num_btns = num_btns; + + if (num_btns) { + btn_keys_size = num_btns * sizeof(struct cyttsp5_btn); + if (!si->btn) + si->btn = kzalloc(btn_keys_size, GFP_KERNEL); + if (!si->btn) + return -ENOMEM; + + if (cd->cpdata->sett[CY_IC_GRPNUM_BTN_KEYS] == NULL) + num_defined_keys = 0; + else if (cd->cpdata->sett[CY_IC_GRPNUM_BTN_KEYS]->data == NULL) + num_defined_keys = 0; + else + num_defined_keys = cd->cpdata->sett + [CY_IC_GRPNUM_BTN_KEYS]->size; + + for (btn = 0; btn < num_btns && btn < num_defined_keys; btn++) { + key_table = (u16 *)cd->cpdata->sett + [CY_IC_GRPNUM_BTN_KEYS]->data; + si->btn[btn].key_code = key_table[btn]; + si->btn[btn].enabled = true; + } + for (; btn < num_btns; btn++) { + si->btn[btn].key_code = KEY_RESERVED; + si->btn[btn].enabled = true; + } + + return rc; + } + + kfree(si->btn); + si->btn = NULL; + return rc; +} + +static void cyttsp5_si_put_log_data(struct cyttsp5_core_data *cd) +{ + struct cyttsp5_sysinfo *si = &cd->sysinfo; + struct cyttsp5_cydata *cydata = &si->cydata; + struct cyttsp5_sensing_conf_data *scd = &si->sensing_conf_data; + int i; + + dev_dbg(cd->dev, "%s: pip_ver_major =0x%02X (%d)\n", __func__, + cydata->pip_ver_major, cydata->pip_ver_major); + dev_dbg(cd->dev, "%s: pip_ver_minor =0x%02X (%d)\n", __func__, + cydata->pip_ver_minor, cydata->pip_ver_minor); + dev_dbg(cd->dev, "%s: fw_pid =0x%04X (%d)\n", __func__, + cydata->fw_pid, cydata->fw_pid); + dev_dbg(cd->dev, "%s: fw_ver_major =0x%02X (%d)\n", __func__, + cydata->fw_ver_major, cydata->fw_ver_major); + dev_dbg(cd->dev, "%s: fw_ver_minor =0x%02X (%d)\n", __func__, + cydata->fw_ver_minor, cydata->fw_ver_minor); + dev_dbg(cd->dev, "%s: revctrl =0x%08X (%d)\n", __func__, + cydata->revctrl, cydata->revctrl); + dev_dbg(cd->dev, "%s: fw_ver_conf =0x%04X (%d)\n", __func__, + cydata->fw_ver_conf, cydata->fw_ver_conf); + dev_dbg(cd->dev, "%s: bl_ver_major =0x%02X (%d)\n", __func__, + cydata->bl_ver_major, cydata->bl_ver_major); + dev_dbg(cd->dev, "%s: bl_ver_minor =0x%02X (%d)\n", __func__, + cydata->bl_ver_minor, cydata->bl_ver_minor); + dev_dbg(cd->dev, "%s: jtag_id_h =0x%04X (%d)\n", __func__, + cydata->jtag_id_h, cydata->jtag_id_h); + dev_dbg(cd->dev, "%s: jtag_id_l =0x%04X (%d)\n", __func__, + cydata->jtag_id_l, cydata->jtag_id_l); + for (i = 0; i < CY_NUM_MFGID; i++) + dev_dbg(cd->dev, "%s: mfg_id[%d] =0x%02X (%d)\n", __func__, i, + cydata->mfg_id[i], cydata->mfg_id[i]); + dev_dbg(cd->dev, "%s: post_code =0x%04X (%d)\n", __func__, + cydata->post_code, cydata->post_code); + + dev_dbg(cd->dev, "%s: electrodes_x =0x%02X (%d)\n", __func__, + scd->electrodes_x, scd->electrodes_x); + dev_dbg(cd->dev, "%s: electrodes_y =0x%02X (%d)\n", __func__, + scd->electrodes_y, scd->electrodes_y); + dev_dbg(cd->dev, "%s: len_x =0x%04X (%d)\n", __func__, + scd->len_x, scd->len_x); + dev_dbg(cd->dev, "%s: len_y =0x%04X (%d)\n", __func__, + scd->len_y, scd->len_y); + dev_dbg(cd->dev, "%s: res_x =0x%04X (%d)\n", __func__, + scd->res_x, scd->res_x); + dev_dbg(cd->dev, "%s: res_y =0x%04X (%d)\n", __func__, + scd->res_y, scd->res_y); + dev_dbg(cd->dev, "%s: max_z =0x%04X (%d)\n", __func__, + scd->max_z, scd->max_z); + dev_dbg(cd->dev, "%s: origin_x =0x%02X (%d)\n", __func__, + scd->origin_x, scd->origin_x); + dev_dbg(cd->dev, "%s: origin_y =0x%02X (%d)\n", __func__, + scd->origin_y, scd->origin_y); + dev_dbg(cd->dev, "%s: panel_id =0x%02X (%d)\n", __func__, + scd->panel_id, scd->panel_id); + dev_dbg(cd->dev, "%s: btn =0x%02X (%d)\n", __func__, + scd->btn, scd->btn); + dev_dbg(cd->dev, "%s: scan_mode =0x%02X (%d)\n", __func__, + scd->scan_mode, scd->scan_mode); + dev_dbg(cd->dev, "%s: max_num_of_tch_per_refresh_cycle =0x%02X (%d)\n", + __func__, scd->max_tch, scd->max_tch); + + dev_dbg(cd->dev, "%s: xy_mode =%p\n", __func__, + si->xy_mode); + dev_dbg(cd->dev, "%s: xy_data =%p\n", __func__, + si->xy_data); +} + +static int cyttsp5_get_sysinfo_regs(struct cyttsp5_core_data *cd) +{ + struct cyttsp5_sysinfo *si = &cd->sysinfo; + int rc; + + rc = cyttsp5_si_get_btn_data(cd); + if (rc < 0) + return rc; + + cyttsp5_si_get_cydata(cd); + + cyttsp5_si_get_sensing_conf_data(cd); + + cyttsp5_si_setup(cd); + + cyttsp5_si_put_log_data(cd); + + si->ready = true; + return rc; +} + +static void cyttsp5_free_si_ptrs(struct cyttsp5_core_data *cd) +{ + struct cyttsp5_sysinfo *si = &cd->sysinfo; + + kfree(si->btn); + kfree(si->xy_mode); + kfree(si->xy_data); +} + +static int cyttsp5_hid_output_get_sysinfo_(struct cyttsp5_core_data *cd) +{ + int rc; + struct cyttsp5_hid_output hid_output = { + HID_OUTPUT_APP_COMMAND(HID_OUTPUT_GET_SYSINFO), + .timeout_ms = CY_HID_OUTPUT_GET_SYSINFO_TIMEOUT, + }; + + rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); + if (rc) + return rc; + + rc = cyttsp5_get_sysinfo_regs(cd); + if (rc) + cyttsp5_free_si_ptrs(cd); + + return rc; +} + +static int cyttsp5_hid_output_get_sysinfo(struct cyttsp5_core_data *cd) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = cyttsp5_hid_output_get_sysinfo_(cd); + + if (release_exclusive(cd, cd->dev) < 0) + dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); + + return rc; +} + +static int cyttsp5_hid_output_suspend_scanning_(struct cyttsp5_core_data *cd) +{ + struct cyttsp5_hid_output hid_output = { + HID_OUTPUT_APP_COMMAND(HID_OUTPUT_SUSPEND_SCANNING), + }; + + return cyttsp5_hid_send_output_and_wait_(cd, &hid_output); +} + +static int cyttsp5_hid_output_suspend_scanning(struct cyttsp5_core_data *cd) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = cyttsp5_hid_output_suspend_scanning_(cd); + + if (release_exclusive(cd, cd->dev) < 0) + dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); + + return rc; +} + +static int _cyttsp5_request_hid_output_suspend_scanning(struct device *dev, + int protect) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return cyttsp5_hid_output_suspend_scanning(cd); + + return cyttsp5_hid_output_suspend_scanning_(cd); +} + +static int cyttsp5_hid_output_resume_scanning_(struct cyttsp5_core_data *cd) +{ + struct cyttsp5_hid_output hid_output = { + HID_OUTPUT_APP_COMMAND(HID_OUTPUT_RESUME_SCANNING), + }; + + return cyttsp5_hid_send_output_and_wait_(cd, &hid_output); +} + +static int cyttsp5_hid_output_resume_scanning(struct cyttsp5_core_data *cd) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = cyttsp5_hid_output_resume_scanning_(cd); + + if (release_exclusive(cd, cd->dev) < 0) + dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); + + return rc; +} + +static int _cyttsp5_request_hid_output_resume_scanning(struct device *dev, + int protect) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return cyttsp5_hid_output_resume_scanning(cd); + + return cyttsp5_hid_output_resume_scanning_(cd); +} + +static int cyttsp5_hid_output_get_param_(struct cyttsp5_core_data *cd, + u8 param_id, u32 *value) +{ + int write_length = 1; + u8 param[1] = { param_id }; + u8 read_param_id; + int param_size; + u8 *ptr; + int rc; + int i; + struct cyttsp5_hid_output hid_output = { + HID_OUTPUT_APP_COMMAND(HID_OUTPUT_GET_PARAM), + .write_length = write_length, + .write_buf = param, + }; + + rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); + if (rc) + return rc; + + read_param_id = cd->response_buf[5]; + if (read_param_id != param_id) + return -EPROTO; + + param_size = cd->response_buf[6]; + ptr = &cd->response_buf[7]; + *value = 0; + for (i = 0; i < param_size; i++) + *value += ptr[i] << (i * 8); + return 0; +} + +static int cyttsp5_hid_output_get_param(struct cyttsp5_core_data *cd, + u8 param_id, u32 *value) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = cyttsp5_hid_output_get_param_(cd, param_id, value); + + if (release_exclusive(cd, cd->dev) < 0) + dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); + + return rc; +} + +int _cyttsp5_request_hid_output_get_param(struct device *dev, + int protect, u8 param_id, u32 *value) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return cyttsp5_hid_output_get_param(cd, param_id, value); + + return cyttsp5_hid_output_get_param_(cd, param_id, value); +} + +static int cyttsp5_hid_output_set_param_(struct cyttsp5_core_data *cd, + u8 param_id, u32 value, u8 size) +{ + u8 write_buf[6]; + u8 *ptr = &write_buf[2]; + int rc; + int i; + struct cyttsp5_hid_output hid_output = { + HID_OUTPUT_APP_COMMAND(HID_OUTPUT_SET_PARAM), + .write_buf = write_buf, + }; + + write_buf[0] = param_id; + write_buf[1] = size; + for (i = 0; i < size; i++) { + ptr[i] = value & 0xFF; + value = value >> 8; + } + + hid_output.write_length = 2 + size; + + rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); + if (rc) + return rc; + + if (param_id != cd->response_buf[5] || size != cd->response_buf[6]) + return -EPROTO; + + return 0; +} + +static int cyttsp5_hid_output_set_param(struct cyttsp5_core_data *cd, + u8 param_id, u32 value, u8 size) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = cyttsp5_hid_output_set_param_(cd, param_id, value, size); + + if (release_exclusive(cd, cd->dev) < 0) + dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); + + return rc; +} + +int _cyttsp5_request_hid_output_set_param(struct device *dev, + int protect, u8 param_id, u32 value, u8 size) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return cyttsp5_hid_output_set_param(cd, param_id, value, size); + + return cyttsp5_hid_output_set_param_(cd, param_id, value, size); +} + +static int cyttsp5_hid_output_enter_easywake_state_( + struct cyttsp5_core_data *cd, u8 data, u8 *return_data) +{ + int write_length = 1; + u8 param[1] = { data }; + int rc; + struct cyttsp5_hid_output hid_output = { + HID_OUTPUT_APP_COMMAND(HID_OUTPUT_ENTER_EASYWAKE_STATE), + .write_length = write_length, + .write_buf = param, + }; + + rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); + if (rc) + return rc; + + *return_data = cd->response_buf[5]; + return rc; +} + +static int cyttsp5_hid_output_verify_config_block_crc_( + struct cyttsp5_core_data *cd, u8 ebid, u8 *status, + u16 *calculated_crc, u16 *stored_crc) +{ + int write_length = 1; + u8 param[1] = { ebid }; + u8 *ptr; + int rc; + struct cyttsp5_hid_output hid_output = { + HID_OUTPUT_APP_COMMAND(HID_OUTPUT_VERIFY_CONFIG_BLOCK_CRC), + .write_length = write_length, + .write_buf = param, + }; + + rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); + if (rc) + return rc; + + ptr = &cd->response_buf[5]; + *status = ptr[0]; + *calculated_crc = get_unaligned_le16(&ptr[1]); + *stored_crc = get_unaligned_le16(&ptr[3]); + return 0; +} + +static int cyttsp5_hid_output_verify_config_block_crc( + struct cyttsp5_core_data *cd, u8 ebid, u8 *status, + u16 *calculated_crc, u16 *stored_crc) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = cyttsp5_hid_output_verify_config_block_crc_(cd, ebid, status, + calculated_crc, stored_crc); + + if (release_exclusive(cd, cd->dev) < 0) + dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); + + return rc; +} + +static int _cyttsp5_request_hid_output_verify_config_block_crc( + struct device *dev, int protect, u8 ebid, u8 *status, + u16 *calculated_crc, u16 *stored_crc) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return cyttsp5_hid_output_verify_config_block_crc(cd, ebid, + status, calculated_crc, stored_crc); + + return cyttsp5_hid_output_verify_config_block_crc_(cd, ebid, + status, calculated_crc, stored_crc); +} + +static int cyttsp5_hid_output_get_config_row_size_(struct cyttsp5_core_data *cd, + u16 *row_size) +{ + int rc; + struct cyttsp5_hid_output hid_output = { + HID_OUTPUT_APP_COMMAND(HID_OUTPUT_GET_CONFIG_ROW_SIZE), + }; + + rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); + if (rc) + return rc; + + *row_size = get_unaligned_le16(&cd->response_buf[5]); + return 0; +} + +static int cyttsp5_hid_output_get_config_row_size(struct cyttsp5_core_data *cd, + u16 *row_size) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = cyttsp5_hid_output_get_config_row_size_(cd, row_size); + + if (release_exclusive(cd, cd->dev) < 0) + dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); + + return rc; +} + +static int _cyttsp5_request_hid_output_get_config_row_size(struct device *dev, + int protect, u16 *row_size) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return cyttsp5_hid_output_get_config_row_size(cd, row_size); + + return cyttsp5_hid_output_get_config_row_size_(cd, row_size); +} + +static int cyttsp5_hid_output_read_conf_block_(struct cyttsp5_core_data *cd, + u16 row_number, u16 length, u8 ebid, u8 *read_buf, u16 *crc) +{ + int read_ebid; + int read_length; + int status; + int rc; + int write_length = 5; + u8 write_buf[5]; + u8 cmd_offset = 0; + struct cyttsp5_hid_output hid_output = { + HID_OUTPUT_APP_COMMAND(HID_OUTPUT_READ_CONF_BLOCK), + .write_length = write_length, + .write_buf = write_buf, + }; + + write_buf[cmd_offset++] = LOW_BYTE(row_number); + write_buf[cmd_offset++] = HI_BYTE(row_number); + write_buf[cmd_offset++] = LOW_BYTE(length); + write_buf[cmd_offset++] = HI_BYTE(length); + write_buf[cmd_offset++] = ebid; + + rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); + if (rc) + return rc; + + status = cd->response_buf[5]; + if (status) + return -EINVAL; + + read_ebid = cd->response_buf[6]; + if ((read_ebid != ebid) || (cd->response_buf[9] != 0)) + return -EPROTO; + + read_length = get_unaligned_le16(&cd->response_buf[7]); + if (length < read_length) + length = read_length; + + memcpy(read_buf, &cd->response_buf[10], length); + *crc = get_unaligned_le16(&cd->response_buf[read_length + 10]); + + return 0; +} + +static int cyttsp5_hid_output_read_conf_ver_(struct cyttsp5_core_data *cd, + u16 *config_ver) +{ + int rc; + u8 read_buf[CY_TTCONFIG_VERSION_OFFSET + CY_TTCONFIG_VERSION_SIZE]; + u16 crc; + + rc = cyttsp5_hid_output_read_conf_block_(cd, CY_TTCONFIG_VERSION_ROW, + CY_TTCONFIG_VERSION_OFFSET + CY_TTCONFIG_VERSION_SIZE, + CY_TCH_PARM_EBID, read_buf, &crc); + if (rc) + return rc; + + *config_ver = get_unaligned_le16( + &read_buf[CY_TTCONFIG_VERSION_OFFSET]); + + return 0; +} + +static int cyttsp5_hid_output_write_conf_block_(struct cyttsp5_core_data *cd, + u16 row_number, u16 write_length, u8 ebid, u8 *write_buf, + u8 *security_key, u16 *actual_write_len) +{ + /* row_number + write_len + ebid + security_key + crc */ + int full_write_length = 2 + 2 + 1 + write_length + 8 + 2; + u8 *full_write_buf; + u8 cmd_offset = 0; + u16 crc; + int status; + int rc; + int read_ebid; + u8 *data; + struct cyttsp5_hid_output hid_output = { + HID_OUTPUT_APP_COMMAND(HID_OUTPUT_WRITE_CONF_BLOCK), + .write_length = full_write_length, + .timeout_ms = CY_HID_OUTPUT_WRITE_CONF_BLOCK_TIMEOUT, + }; + + full_write_buf = kzalloc(full_write_length, GFP_KERNEL); + if (!full_write_buf) + return -ENOMEM; + + hid_output.write_buf = full_write_buf; + full_write_buf[cmd_offset++] = LOW_BYTE(row_number); + full_write_buf[cmd_offset++] = HI_BYTE(row_number); + full_write_buf[cmd_offset++] = LOW_BYTE(write_length); + full_write_buf[cmd_offset++] = HI_BYTE(write_length); + full_write_buf[cmd_offset++] = ebid; + data = &full_write_buf[cmd_offset]; + memcpy(data, write_buf, write_length); + cmd_offset += write_length; + memcpy(&full_write_buf[cmd_offset], security_key, 8); + cmd_offset += 8; + crc = _cyttsp5_compute_crc(data, write_length); + full_write_buf[cmd_offset++] = LOW_BYTE(crc); + full_write_buf[cmd_offset++] = HI_BYTE(crc); + + rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); + if (rc) + goto exit; + + status = cd->response_buf[5]; + if (status) { + rc = -EINVAL; + goto exit; + } + + read_ebid = cd->response_buf[6]; + if (read_ebid != ebid) { + rc = -EPROTO; + goto exit; + } + + *actual_write_len = get_unaligned_le16(&cd->response_buf[7]); + +exit: + kfree(full_write_buf); + return rc; +} + +static int cyttsp5_hid_output_write_conf_block(struct cyttsp5_core_data *cd, + u16 row_number, u16 write_length, u8 ebid, u8 *write_buf, + u8 *security_key, u16 *actual_write_len) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = cyttsp5_hid_output_write_conf_block_(cd, row_number, write_length, + ebid, write_buf, security_key, actual_write_len); + + if (release_exclusive(cd, cd->dev) < 0) + dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); + + return rc; +} + +static int _cyttsp5_request_hid_output_write_conf_block(struct device *dev, + int protect, u16 row_number, u16 write_length, u8 ebid, + u8 *write_buf, u8 *security_key, u16 *actual_write_len) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return cyttsp5_hid_output_write_conf_block(cd, row_number, + write_length, ebid, write_buf, security_key, + actual_write_len); + + return cyttsp5_hid_output_write_conf_block_(cd, row_number, + write_length, ebid, write_buf, security_key, + actual_write_len); +} + +static int cyttsp5_hid_output_get_data_structure_( + struct cyttsp5_core_data *cd, u16 read_offset, u16 read_length, + u8 data_id, u8 *status, u8 *data_format, u16 *actual_read_len, + u8 *data) +{ + int rc; + u16 total_read_len = 0; + u16 read_len; + u16 off_buf = 0; + u8 write_buf[5]; + u8 read_data_id; + struct cyttsp5_hid_output hid_output = { + HID_OUTPUT_APP_COMMAND(HID_OUTPUT_GET_DATA_STRUCTURE), + .write_length = 5, + .write_buf = write_buf, + }; + +again: + write_buf[0] = LOW_BYTE(read_offset); + write_buf[1] = HI_BYTE(read_offset); + write_buf[2] = LOW_BYTE(read_length); + write_buf[3] = HI_BYTE(read_length); + write_buf[4] = data_id; + + rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); + if (rc) + return rc; + + if (cd->response_buf[5] != CY_CMD_STATUS_SUCCESS) + goto set_status; + + read_data_id = cd->response_buf[6]; + if (read_data_id != data_id) + return -EPROTO; + + read_len = get_unaligned_le16(&cd->response_buf[7]); + if (read_len && data) { + memcpy(&data[off_buf], &cd->response_buf[10], read_len); + + total_read_len += read_len; + + if (read_len < read_length) { + read_offset += read_len; + off_buf += read_len; + read_length -= read_len; + goto again; + } + } + + if (data_format) + *data_format = cd->response_buf[9]; + if (actual_read_len) + *actual_read_len = total_read_len; +set_status: + if (status) + *status = cd->response_buf[5]; + + return rc; +} + +static int cyttsp5_hid_output_get_data_structure( + struct cyttsp5_core_data *cd, u16 read_offset, u16 read_length, + u8 data_id, u8 *status, u8 *data_format, u16 *actual_read_len, + u8 *data) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = cyttsp5_hid_output_get_data_structure_(cd, read_offset, + read_length, data_id, status, data_format, + actual_read_len, data); + + if (release_exclusive(cd, cd->dev) < 0) + dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); + + return rc; +} + +static int _cyttsp5_request_hid_output_get_data_structure(struct device *dev, + int protect, u16 read_offset, u16 read_length, u8 data_id, + u8 *status, u8 *data_format, u16 *actual_read_len, u8 *data) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return cyttsp5_hid_output_get_data_structure(cd, + read_offset, read_length, data_id, status, + data_format, actual_read_len, data); + + return cyttsp5_hid_output_get_data_structure_(cd, + read_offset, read_length, data_id, status, + data_format, actual_read_len, data); +} + +static int cyttsp5_hid_output_run_selftest_( + struct cyttsp5_core_data *cd, u8 test_id, + u8 write_idacs_to_flash, u8 *status, u8 *summary_result, + u8 *results_available) +{ + int rc; + u8 write_buf[2]; + struct cyttsp5_hid_output hid_output = { + HID_OUTPUT_APP_COMMAND(HID_OUTPUT_RUN_SELF_TEST), + .write_length = 2, + .write_buf = write_buf, + .timeout_ms = CY_HID_OUTPUT_RUN_SELF_TEST_TIMEOUT, + }; + + write_buf[0] = test_id; + write_buf[1] = write_idacs_to_flash; + + rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); + if (rc) + return rc; + + if (status) + *status = cd->response_buf[5]; + if (summary_result) + *summary_result = cd->response_buf[6]; + if (results_available) + *results_available = cd->response_buf[7]; + + return rc; +} + +static int cyttsp5_hid_output_run_selftest( + struct cyttsp5_core_data *cd, u8 test_id, + u8 write_idacs_to_flash, u8 *status, u8 *summary_result, + u8 *results_available) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = cyttsp5_hid_output_run_selftest_(cd, test_id, + write_idacs_to_flash, status, summary_result, + results_available); + + if (release_exclusive(cd, cd->dev) < 0) + dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); + + return rc; +} + +static int _cyttsp5_request_hid_output_run_selftest(struct device *dev, + int protect, u8 test_id, u8 write_idacs_to_flash, u8 *status, + u8 *summary_result, u8 *results_available) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return cyttsp5_hid_output_run_selftest(cd, test_id, + write_idacs_to_flash, status, summary_result, + results_available); + + return cyttsp5_hid_output_run_selftest_(cd, test_id, + write_idacs_to_flash, status, summary_result, + results_available); +} + +static int cyttsp5_hid_output_get_selftest_result_( + struct cyttsp5_core_data *cd, u16 read_offset, u16 read_length, + u8 test_id, u8 *status, u16 *actual_read_len, u8 *data) +{ + int rc; + u16 total_read_len = 0; + u16 read_len; + u16 off_buf = 0; + u8 write_buf[5]; + u8 read_test_id; + bool repeat; + struct cyttsp5_hid_output hid_output = { + HID_OUTPUT_APP_COMMAND(HID_OUTPUT_GET_SELF_TEST_RESULT), + .write_length = 5, + .write_buf = write_buf, + }; + + /* + * Do not repeat reading for Auto Shorts test + * when PIP version < 1.3 + */ + repeat = IS_PIP_VER_GE(&cd->sysinfo, 1, 3) + || test_id != CY_ST_ID_AUTOSHORTS; + +again: + write_buf[0] = LOW_BYTE(read_offset); + write_buf[1] = HI_BYTE(read_offset); + write_buf[2] = LOW_BYTE(read_length); + write_buf[3] = HI_BYTE(read_length); + write_buf[4] = test_id; + + rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); + if (rc) + return rc; + + if (cd->response_buf[5] != CY_CMD_STATUS_SUCCESS) + goto set_status; + + read_test_id = cd->response_buf[6]; + if (read_test_id != test_id) + return -EPROTO; + + read_len = get_unaligned_le16(&cd->response_buf[7]); + if (read_len && data) { + memcpy(&data[off_buf], &cd->response_buf[10], read_len); + + total_read_len += read_len; + + if (repeat && read_len < read_length) { + read_offset += read_len; + off_buf += read_len; + read_length -= read_len; + goto again; + } + } + + if (actual_read_len) + *actual_read_len = total_read_len; +set_status: + if (status) + *status = cd->response_buf[5]; + + return rc; +} + +static int cyttsp5_hid_output_get_selftest_result( + struct cyttsp5_core_data *cd, u16 read_offset, u16 read_length, + u8 test_id, u8 *status, u16 *actual_read_len, u8 *data) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = cyttsp5_hid_output_get_selftest_result_(cd, read_offset, + read_length, test_id, status, actual_read_len, data); + + if (release_exclusive(cd, cd->dev) < 0) + dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); + + return rc; +} + +static int _cyttsp5_request_hid_output_get_selftest_result(struct device *dev, + int protect, u16 read_offset, u16 read_length, u8 test_id, + u8 *status, u16 *actual_read_len, u8 *data) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return cyttsp5_hid_output_get_selftest_result(cd, read_offset, + read_length, test_id, status, actual_read_len, + data); + + return cyttsp5_hid_output_get_selftest_result_(cd, read_offset, + read_length, test_id, status, actual_read_len, + data); +} + +static int cyttsp5_hid_output_calibrate_idacs_(struct cyttsp5_core_data *cd, + u8 mode, u8 *status) +{ + int rc; + int write_length = 1; + u8 write_buf[1]; + u8 cmd_offset = 0; + struct cyttsp5_hid_output hid_output = { + HID_OUTPUT_APP_COMMAND(HID_OUTPUT_CALIBRATE_IDACS), + .write_length = write_length, + .write_buf = write_buf, + .timeout_ms = CY_HID_OUTPUT_CALIBRATE_IDAC_TIMEOUT, + }; + + write_buf[cmd_offset++] = mode; + rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); + if (rc) + return rc; + + *status = cd->response_buf[5]; + if (*status) + return -EINVAL; + + return 0; +} + +static int cyttsp5_hid_output_calibrate_idacs(struct cyttsp5_core_data *cd, + u8 mode, u8 *status) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = cyttsp5_hid_output_calibrate_idacs_(cd, mode, status); + + if (release_exclusive(cd, cd->dev) < 0) + dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); + + return rc; +} + +static int _cyttsp5_request_hid_output_calibrate_idacs(struct device *dev, + int protect, u8 mode, u8 *status) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return cyttsp5_hid_output_calibrate_idacs(cd, mode, status); + + return cyttsp5_hid_output_calibrate_idacs_(cd, mode, status); +} + +static int cyttsp5_hid_output_initialize_baselines_( + struct cyttsp5_core_data *cd, u8 test_id, u8 *status) +{ + int rc; + int write_length = 1; + u8 write_buf[1]; + u8 cmd_offset = 0; + struct cyttsp5_hid_output hid_output = { + HID_OUTPUT_APP_COMMAND(HID_OUTPUT_INITIALIZE_BASELINES), + .write_length = write_length, + .write_buf = write_buf, + }; + + write_buf[cmd_offset++] = test_id; + + rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); + if (rc) + return rc; + + *status = cd->response_buf[5]; + if (*status) + return -EINVAL; + + return rc; +} + +static int cyttsp5_hid_output_initialize_baselines(struct cyttsp5_core_data *cd, + u8 test_id, u8 *status) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = cyttsp5_hid_output_initialize_baselines_(cd, test_id, status); + + if (release_exclusive(cd, cd->dev) < 0) + dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); + + return rc; +} + +static int _cyttsp5_request_hid_output_initialize_baselines(struct device *dev, + int protect, u8 test_id, u8 *status) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return cyttsp5_hid_output_initialize_baselines(cd, test_id, + status); + + return cyttsp5_hid_output_initialize_baselines_(cd, test_id, status); +} + +static int cyttsp5_hid_output_exec_panel_scan_(struct cyttsp5_core_data *cd) +{ + struct cyttsp5_hid_output hid_output = { + HID_OUTPUT_APP_COMMAND(HID_OUTPUT_EXEC_PANEL_SCAN), + }; + + return cyttsp5_hid_send_output_and_wait_(cd, &hid_output); +} + +static int cyttsp5_hid_output_exec_panel_scan(struct cyttsp5_core_data *cd) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = cyttsp5_hid_output_exec_panel_scan_(cd); + + if (release_exclusive(cd, cd->dev) < 0) + dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); + + return rc; +} + +static int _cyttsp5_request_hid_output_exec_panel_scan(struct device *dev, + int protect) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return cyttsp5_hid_output_exec_panel_scan(cd); + + return cyttsp5_hid_output_exec_panel_scan_(cd); +} + +/* @response: set none NULL only if all response required including header */ +static int cyttsp5_hid_output_retrieve_panel_scan_( + struct cyttsp5_core_data *cd, u16 read_offset, u16 read_count, + u8 data_id, u8 *response, u8 *config, u16 *actual_read_len, + u8 *read_buf) +{ + int status; + u8 read_data_id; + int rc; + int write_length = 5; + u8 write_buf[5]; + u8 cmd_offset = 0; + u8 data_elem_size; + int size; + int data_size; + struct cyttsp5_hid_output hid_output = { + HID_OUTPUT_APP_COMMAND(HID_OUTPUT_RETRIEVE_PANEL_SCAN), + .write_length = write_length, + .write_buf = write_buf, + }; + + write_buf[cmd_offset++] = LOW_BYTE(read_offset); + write_buf[cmd_offset++] = HI_BYTE(read_offset); + write_buf[cmd_offset++] = LOW_BYTE(read_count); + write_buf[cmd_offset++] = HI_BYTE(read_count); + write_buf[cmd_offset++] = data_id; + + rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); + if (rc) + return rc; + + status = cd->response_buf[5]; + if (status) + return -EINVAL; + + read_data_id = cd->response_buf[6]; + if (read_data_id != data_id) + return -EPROTO; + + size = get_unaligned_le16(&cd->response_buf[0]); + *actual_read_len = get_unaligned_le16(&cd->response_buf[7]); + *config = cd->response_buf[9]; + + data_elem_size = *config & 0x07; + data_size = *actual_read_len * data_elem_size; + + if (read_buf) + memcpy(read_buf, &cd->response_buf[10], data_size); + if (response) + memcpy(response, cd->response_buf, size); + return rc; +} + +static int cyttsp5_hid_output_retrieve_panel_scan( + struct cyttsp5_core_data *cd, u16 read_offset, u16 read_count, + u8 data_id, u8 *response, u8 *config, u16 *actual_read_len, + u8 *read_buf) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = cyttsp5_hid_output_retrieve_panel_scan_(cd, read_offset, + read_count, data_id, response, config, + actual_read_len, read_buf); + + if (release_exclusive(cd, cd->dev) < 0) + dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); + + return rc; +} + +static int _cyttsp5_request_hid_output_retrieve_panel_scan(struct device *dev, + int protect, u16 read_offset, u16 read_count, u8 data_id, + u8 *response, u8 *config, u16 *actual_read_len, u8 *read_buf) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return cyttsp5_hid_output_retrieve_panel_scan(cd, + read_offset, read_count, data_id, response, + config, actual_read_len, read_buf); + + return cyttsp5_hid_output_retrieve_panel_scan_(cd, + read_offset, read_count, data_id, response, + config, actual_read_len, read_buf); +} + +static int cyttsp5_hid_output_user_cmd_(struct cyttsp5_core_data *cd, + u16 read_len, u8 *read_buf, u16 write_len, u8 *write_buf, + u16 *actual_read_len) +{ + int rc; + u16 size; +#ifdef TTHE_TUNER_SUPPORT + int command_code = 0; + int len; +#endif + struct cyttsp5_hid_output hid_output = { + .length = write_len, + .write_buf = write_buf, + }; + + rc = cyttsp5_hid_send_output_user_and_wait_(cd, &hid_output); + if (rc) + return rc; + + size = get_unaligned_le16(&cd->response_buf[0]); + if (size == 0) + size = 2; + + if (size > read_len) { + *actual_read_len = 0; + return -EINVAL; + } + + memcpy(read_buf, cd->response_buf, size); + *actual_read_len = size; + +#ifdef TTHE_TUNER_SUPPORT + /* print up to cmd code */ + len = HID_OUTPUT_CMD_OFFSET + 1; + if (write_len < len) + len = write_len; + else + command_code = write_buf[HID_OUTPUT_CMD_OFFSET] + & HID_OUTPUT_CMD_MASK; + + /* Do not print for EXEC_PANEL_SCAN & RETRIEVE_PANEL_SCAN commands */ + if (command_code != HID_OUTPUT_EXEC_PANEL_SCAN + && command_code != HID_OUTPUT_RETRIEVE_PANEL_SCAN) + tthe_print(cd, write_buf, len, "CMD="); +#endif + + return 0; +} + +static int cyttsp5_get_config_ver_(struct cyttsp5_core_data *cd) +{ + struct cyttsp5_sysinfo *si = &cd->sysinfo; + int rc; + u16 config_ver = 0; + + rc = cyttsp5_hid_output_suspend_scanning_(cd); + if (rc) + goto error; + + rc = cyttsp5_hid_output_read_conf_ver_(cd, &config_ver); + if (rc) + goto exit; + + si->cydata.fw_ver_conf = config_ver; + +exit: + cyttsp5_hid_output_resume_scanning_(cd); +error: + dev_dbg(cd->dev, "%s: CONFIG_VER:%04X\n", __func__, config_ver); + return rc; +} + +static int cyttsp5_hid_output_user_cmd(struct cyttsp5_core_data *cd, + u16 read_len, u8 *read_buf, u16 write_len, u8 *write_buf, + u16 *actual_read_len) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = cyttsp5_hid_output_user_cmd_(cd, read_len, read_buf, + write_len, write_buf, actual_read_len); + + if (release_exclusive(cd, cd->dev) < 0) + dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); + + return rc; +} + +static int _cyttsp5_request_hid_output_user_cmd(struct device *dev, + int protect, u16 read_len, u8 *read_buf, u16 write_len, + u8 *write_buf, u16 *actual_read_len) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return cyttsp5_hid_output_user_cmd(cd, read_len, read_buf, + write_len, write_buf, actual_read_len); + + return cyttsp5_hid_output_user_cmd_(cd, read_len, read_buf, + write_len, write_buf, actual_read_len); +} + +static int cyttsp5_hid_output_bl_get_information_(struct cyttsp5_core_data *cd, + u8 *return_data) +{ + int rc; + int data_len; + struct cyttsp5_hid_output hid_output = { + HID_OUTPUT_BL_COMMAND(HID_OUTPUT_BL_GET_INFO), + }; + + rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); + if (rc) + return rc; + + data_len = get_unaligned_le16(&cd->input_buf[6]); + if (!data_len) + return -EPROTO; + + memcpy(return_data, &cd->response_buf[8], data_len); + + return 0; +} + +static int cyttsp5_hid_output_bl_get_information(struct cyttsp5_core_data *cd, + u8 *return_data) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = cyttsp5_hid_output_bl_get_information_(cd, return_data); + + if (release_exclusive(cd, cd->dev) < 0) + dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); + + return rc; +} + +static int _cyttsp5_request_hid_output_bl_get_information(struct device *dev, + int protect, u8 *return_data) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return cyttsp5_hid_output_bl_get_information(cd, return_data); + + return cyttsp5_hid_output_bl_get_information_(cd, return_data); +} + +static int cyttsp5_hid_output_bl_initiate_bl_(struct cyttsp5_core_data *cd, + u16 key_size, u8 *key_buf, u16 row_size, u8 *metadata_row_buf) +{ + u16 write_length = key_size + row_size; + u8 *write_buf; + int rc; + struct cyttsp5_hid_output hid_output = { + HID_OUTPUT_BL_COMMAND(HID_OUTPUT_BL_INITIATE_BL), + .write_length = write_length, + .timeout_ms = CY_HID_OUTPUT_BL_INITIATE_BL_TIMEOUT, + }; + + write_buf = kzalloc(write_length, GFP_KERNEL); + if (!write_buf) + return -ENOMEM; + + hid_output.write_buf = write_buf; + + if (key_size) + memcpy(write_buf, key_buf, key_size); + + if (row_size) + memcpy(&write_buf[key_size], metadata_row_buf, row_size); + + rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); + + kfree(write_buf); + return rc; +} + +static int cyttsp5_hid_output_bl_initiate_bl(struct cyttsp5_core_data *cd, + u16 key_size, u8 *key_buf, u16 row_size, u8 *metadata_row_buf) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = cyttsp5_hid_output_bl_initiate_bl_(cd, key_size, key_buf, + row_size, metadata_row_buf); + + if (release_exclusive(cd, cd->dev) < 0) + dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); + + return rc; +} + +static int _cyttsp5_request_hid_output_bl_initiate_bl(struct device *dev, + int protect, u16 key_size, u8 *key_buf, u16 row_size, + u8 *metadata_row_buf) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return cyttsp5_hid_output_bl_initiate_bl(cd, key_size, key_buf, + row_size, metadata_row_buf); + + return cyttsp5_hid_output_bl_initiate_bl_(cd, key_size, key_buf, + row_size, metadata_row_buf); +} + +static int cyttsp5_hid_output_bl_program_and_verify_( + struct cyttsp5_core_data *cd, u16 data_len, u8 *data_buf) +{ + struct cyttsp5_hid_output hid_output = { + HID_OUTPUT_BL_COMMAND(HID_OUTPUT_BL_PROGRAM_AND_VERIFY), + .write_length = data_len, + .write_buf = data_buf, + .timeout_ms = CY_HID_OUTPUT_BL_PROGRAM_AND_VERIFY_TIMEOUT, + }; + + return cyttsp5_hid_send_output_and_wait_(cd, &hid_output); +} + +static int cyttsp5_hid_output_bl_program_and_verify( + struct cyttsp5_core_data *cd, u16 data_len, u8 *data_buf) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = cyttsp5_hid_output_bl_program_and_verify_(cd, data_len, data_buf); + + if (release_exclusive(cd, cd->dev) < 0) + dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); + + return rc; +} + +static int _cyttsp5_request_hid_output_bl_program_and_verify( + struct device *dev, int protect, u16 data_len, u8 *data_buf) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return cyttsp5_hid_output_bl_program_and_verify(cd, data_len, + data_buf); + + return cyttsp5_hid_output_bl_program_and_verify_(cd, data_len, + data_buf); +} + +static int cyttsp5_hid_output_bl_verify_app_integrity_( + struct cyttsp5_core_data *cd, u8 *result) +{ + int rc; + struct cyttsp5_hid_output hid_output = { + HID_OUTPUT_BL_COMMAND(HID_OUTPUT_BL_VERIFY_APP_INTEGRITY), + }; + + rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); + if (rc) { + *result = 0; + return rc; + } + + *result = cd->response_buf[8]; + return 0; +} + +static int cyttsp5_hid_output_bl_verify_app_integrity( + struct cyttsp5_core_data *cd, u8 *result) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = cyttsp5_hid_output_bl_verify_app_integrity_(cd, result); + + if (release_exclusive(cd, cd->dev) < 0) + dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); + + return rc; +} + +static int _cyttsp5_request_hid_output_bl_verify_app_integrity( + struct device *dev, int protect, u8 *result) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return cyttsp5_hid_output_bl_verify_app_integrity(cd, result); + + return cyttsp5_hid_output_bl_verify_app_integrity_(cd, result); +} + +static int cyttsp5_hid_output_bl_launch_app_(struct cyttsp5_core_data *cd) +{ + struct cyttsp5_hid_output hid_output = { + HID_OUTPUT_BL_COMMAND(HID_OUTPUT_BL_LAUNCH_APP), + .reset_expected = 1, + }; + + return cyttsp5_hid_send_output_and_wait_(cd, &hid_output); +} + +static int cyttsp5_hid_output_bl_launch_app(struct cyttsp5_core_data *cd) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = cyttsp5_hid_output_bl_launch_app_(cd); + + if (release_exclusive(cd, cd->dev) < 0) + dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); + + return rc; +} + +static int _cyttsp5_request_hid_output_launch_app(struct device *dev, + int protect) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return cyttsp5_hid_output_bl_launch_app(cd); + + return cyttsp5_hid_output_bl_launch_app_(cd); +} + +static int cyttsp5_hid_output_bl_get_panel_id_( + struct cyttsp5_core_data *cd, u8 *panel_id) +{ + int rc; + struct cyttsp5_hid_output hid_output = { + HID_OUTPUT_BL_COMMAND(HID_OUTPUT_BL_GET_PANEL_ID), + }; + + rc = cyttsp5_hid_send_output_and_wait_(cd, &hid_output); + if (rc == -EPROTO && cd->response_buf[5] == ERROR_COMMAND) { + dev_dbg(cd->dev, "%s: Get Panel ID command not supported\n", + __func__); + *panel_id = PANEL_ID_NOT_ENABLED; + return 0; + } else if (rc < 0) { + dev_err(cd->dev, "%s: Error on Get Panel ID command\n", + __func__); + return rc; + } + + *panel_id = cd->response_buf[8]; + return 0; +} + +static int cyttsp5_hid_output_bl_get_panel_id( + struct cyttsp5_core_data *cd, u8 *panel_id) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = cyttsp5_hid_output_bl_get_panel_id_(cd, panel_id); + + if (release_exclusive(cd, cd->dev) < 0) + dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); + + return rc; +} + +static int _cyttsp5_request_hid_output_bl_get_panel_id( + struct device *dev, int protect, u8 *panel_id) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return cyttsp5_hid_output_bl_get_panel_id(cd, panel_id); + + return cyttsp5_hid_output_bl_get_panel_id_(cd, panel_id); +} + +static int cyttsp5_get_hid_descriptor_(struct cyttsp5_core_data *cd, + struct cyttsp5_hid_desc *desc) +{ + struct device *dev = cd->dev; + int rc; + int t; + u8 cmd[2]; + + /* Read HID descriptor length and version */ + mutex_lock(&cd->system_lock); + cd->hid_cmd_state = 1; + mutex_unlock(&cd->system_lock); + + /* Set HID descriptor register */ + memcpy(cmd, &cd->hid_core.hid_desc_register, + sizeof(cd->hid_core.hid_desc_register)); + + rc = cyttsp5_adap_write_read_specific(cd, 2, cmd, NULL); + if (rc) { + dev_err(dev, "%s: failed to get HID descriptor length and version, rc=%d\n", + __func__, rc); + goto error; + } + + t = wait_event_timeout(cd->wait_q, (cd->hid_cmd_state == 0), + msecs_to_jiffies(CY_HID_GET_HID_DESCRIPTOR_TIMEOUT)); + if (IS_TMO(t)) { + dev_err(cd->dev, "%s: HID get descriptor timed out\n", + __func__); + rc = -ETIME; + goto error; + } + + memcpy((u8 *)desc, cd->response_buf, sizeof(struct cyttsp5_hid_desc)); + + /* Check HID descriptor length and version */ + dev_vdbg(dev, "%s: HID len:%X HID ver:%X\n", __func__, + le16_to_cpu(desc->hid_desc_len), + le16_to_cpu(desc->bcd_version)); + + if (le16_to_cpu(desc->hid_desc_len) != sizeof(*desc) || + le16_to_cpu(desc->bcd_version) != CY_HID_VERSION) { + dev_err(dev, "%s: Unsupported HID version\n", __func__); + return -ENODEV; + } + + goto exit; + +error: + mutex_lock(&cd->system_lock); + cd->hid_cmd_state = 0; + mutex_unlock(&cd->system_lock); +exit: + return rc; +} + +static int cyttsp5_get_hid_descriptor(struct cyttsp5_core_data *cd, + struct cyttsp5_hid_desc *desc) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = cyttsp5_get_hid_descriptor_(cd, desc); + + if (release_exclusive(cd, cd->dev) < 0) + dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); + + return rc; +} + +static int _cyttsp5_request_get_hid_desc(struct device *dev, int protect) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + if (protect) + return cyttsp5_get_hid_descriptor(cd, &cd->hid_desc); + + return cyttsp5_get_hid_descriptor_(cd, &cd->hid_desc); +} + +static int cyttsp5_hw_soft_reset(struct cyttsp5_core_data *cd) +{ + int rc; + + if (cd->hid_desc.hid_desc_len == 0) { + rc = cyttsp5_get_hid_descriptor_(cd, &cd->hid_desc); + if (rc < 0) + return rc; + } + + rc = cyttsp5_hid_cmd_reset_(cd); + if (rc < 0) { + dev_err(cd->dev, "%s: FAILED to execute SOFT reset\n", + __func__); + return rc; + } + dev_dbg(cd->dev, "%s: execute SOFT reset\n", __func__); + return 0; +} + +static int cyttsp5_hw_hard_reset(struct cyttsp5_core_data *cd) +{ + if (cd->cpdata->xres) { + cd->cpdata->xres(cd->cpdata, cd->dev); + dev_dbg(cd->dev, "%s: execute HARD reset\n", __func__); + return 0; + } + dev_err(cd->dev, "%s: FAILED to execute HARD reset\n", __func__); + return -ENODEV; +} + +static int cyttsp5_hw_reset(struct cyttsp5_core_data *cd) +{ + int rc; + + mutex_lock(&cd->system_lock); + rc = cyttsp5_hw_hard_reset(cd); + mutex_unlock(&cd->system_lock); + if (rc == -ENODEV) + rc = cyttsp5_hw_soft_reset(cd); + return rc; +} + +static inline int get_hid_item_data(u8 *data, int item_size) +{ + if (item_size == 1) + return (int)*data; + else if (item_size == 2) + return (int)get_unaligned_le16(data); + else if (item_size == 4) + return (int)get_unaligned_le32(data); + return 0; +} + +static int parse_report_descriptor(struct cyttsp5_core_data *cd, + u8 *report_desc, size_t len) +{ + struct cyttsp5_hid_report *report; + struct cyttsp5_hid_field *field; + u8 *buf = report_desc; + u8 *end = buf + len; + int rc = 0; + int offset = 0; + int i; + u8 report_type; + u32 up_usage; + /* Global items */ + u8 report_id = 0; + u16 usage_page = 0; + int report_count = 0; + int report_size = 0; + int logical_min = 0; + int logical_max = 0; + /* Local items */ + u16 usage = 0; + /* Main items - Collection stack */ + u32 collection_usages[CY_HID_MAX_NESTED_COLLECTIONS] = {0}; + u8 collection_types[CY_HID_MAX_NESTED_COLLECTIONS] = {0}; + /* First collection for header, second for report */ + int logical_collection_count = 0; + int collection_nest = 0; + + dev_vdbg(cd->dev, "%s: Report descriptor length: %zu\n", + __func__, len); + + mutex_lock(&cd->hid_report_lock); + cyttsp5_free_hid_reports_(cd); + + while (buf < end) { + int item_type; + int item_size; + int item_tag; + u8 *data; + + /* Get Item */ + item_size = HID_GET_ITEM_SIZE(buf[0]); + if (item_size == 3) + item_size = 4; + item_type = HID_GET_ITEM_TYPE(buf[0]); + item_tag = HID_GET_ITEM_TAG(buf[0]); + + data = ++buf; + buf += item_size; + + /* Process current item */ + switch (item_type) { + case HID_ITEM_TYPE_GLOBAL: + switch (item_tag) { + case HID_GLOBAL_ITEM_TAG_REPORT_ID: + if (item_size != 1) { + rc = -EINVAL; + goto exit; + } + report_id = get_hid_item_data(data, item_size); + offset = 0; + logical_collection_count = 0; + break; + case HID_GLOBAL_ITEM_TAG_USAGE_PAGE: + if (item_size == 0 || item_size == 4) { + rc = -EINVAL; + goto exit; + } + usage_page = (u16)get_hid_item_data(data, + item_size); + break; + case HID_GLOBAL_ITEM_TAG_LOGICAL_MINIMUM: + if (item_size == 0) { + rc = -EINVAL; + goto exit; + } + logical_min = get_hid_item_data(data, + item_size); + break; + case HID_GLOBAL_ITEM_TAG_LOGICAL_MAXIMUM: + if (item_size == 0) { + rc = -EINVAL; + goto exit; + } + logical_max = get_hid_item_data(data, + item_size); + break; + case HID_GLOBAL_ITEM_TAG_REPORT_COUNT: + if (item_size == 0) { + rc = -EINVAL; + goto exit; + } + report_count = get_hid_item_data(data, + item_size); + break; + case HID_GLOBAL_ITEM_TAG_REPORT_SIZE: + if (item_size == 0) { + rc = -EINVAL; + goto exit; + } + report_size = get_hid_item_data(data, + item_size); + break; + default: + dev_info(cd->dev, + "%s: Unrecognized Global tag %d\n", + __func__, item_tag); + } + break; + case HID_ITEM_TYPE_LOCAL: + switch (item_tag) { + case HID_LOCAL_ITEM_TAG_USAGE: + if (item_size == 0 || item_size == 4) { + rc = -EINVAL; + goto exit; + } + usage = (u16)get_hid_item_data(data, + item_size); + break; + case HID_LOCAL_ITEM_TAG_USAGE_MINIMUM: + if (item_size == 0) { + rc = -EINVAL; + goto exit; + } + /* usage_min = */ + get_hid_item_data(data, item_size); + break; + case HID_LOCAL_ITEM_TAG_USAGE_MAXIMUM: + if (item_size == 0) { + rc = -EINVAL; + goto exit; + } + /* usage_max = */ + get_hid_item_data(data, item_size); + break; + default: + dev_info(cd->dev, + "%s: Unrecognized Local tag %d\n", + __func__, item_tag); + } + break; + case HID_ITEM_TYPE_MAIN: + switch (item_tag) { + case HID_MAIN_ITEM_TAG_BEGIN_COLLECTION: + if (item_size != 1) { + rc = -EINVAL; + goto exit; + } + if (CY_HID_MAX_NESTED_COLLECTIONS == + collection_nest) { + rc = -EINVAL; + goto exit; + } + + up_usage = usage_page << 16 | usage; + + /* Update collection stack */ + collection_usages[collection_nest] = up_usage; + collection_types[collection_nest] = + get_hid_item_data(data, item_size); + + if (collection_types[collection_nest] == + HID_COLLECTION_LOGICAL) + logical_collection_count++; + + collection_nest++; + break; + case HID_MAIN_ITEM_TAG_END_COLLECTION: + if (item_size != 0) { + rc = -EINVAL; + goto exit; + } + if (--collection_nest < 0) { + rc = -EINVAL; + goto exit; + } + break; + case HID_MAIN_ITEM_TAG_INPUT: + report_type = HID_INPUT_REPORT; + goto continue_main_item; + case HID_MAIN_ITEM_TAG_OUTPUT: + report_type = HID_OUTPUT_REPORT; + goto continue_main_item; + case HID_MAIN_ITEM_TAG_FEATURE: + report_type = HID_FEATURE_REPORT; +continue_main_item: + if (item_size != 1) { + rc = -EINVAL; + goto exit; + } + + up_usage = usage_page << 16 | usage; + + /* Get or create report */ + report = cyttsp5_get_hid_report_(cd, + report_type, report_id, true); + if (!report) { + rc = -ENOMEM; + goto exit; + } + if (!report->usage_page && collection_nest > 0) + report->usage_page = + collection_usages + [collection_nest - 1]; + + /* Create field */ + field = cyttsp5_create_hid_field_(report); + if (!field) { + rc = -ENOMEM; + goto exit; + } + + field->report_count = report_count; + field->report_size = report_size; + field->size = report_count * report_size; + field->offset = offset; + field->data_type = + get_hid_item_data(data, item_size); + field->logical_min = logical_min; + field->logical_max = logical_max; + field->usage_page = up_usage; + + for (i = 0; i < collection_nest; i++) { + field->collection_usage_pages + [collection_types[i]] = + collection_usages[i]; + } + + /* Update report's header or record size */ + if (logical_collection_count == 1) { + report->header_size += field->size; + } else if (logical_collection_count == 2) { + field->record_field = true; + field->offset -= report->header_size; + /* Set record field index */ + if (report->record_field_index == 0) + report->record_field_index = + report->num_fields - 1; + report->record_size += field->size; + } + + report->size += field->size; + + offset += field->size; + break; + default: + dev_info(cd->dev, "%s: Unrecognized Main tag %d\n", + __func__, item_tag); + } + + /* Reset all local items */ + usage = 0; + break; + } + } + + if (buf != end) { + dev_err(cd->dev, "%s: Report descriptor length invalid\n", + __func__); + rc = -EINVAL; + goto exit; + } + + if (collection_nest) { + dev_err(cd->dev, "%s: Unbalanced collection items (%d)\n", + __func__, collection_nest); + rc = -EINVAL; + goto exit; + } + +exit: + if (rc) + cyttsp5_free_hid_reports_(cd); + mutex_unlock(&cd->hid_report_lock); + return rc; +} + +static struct cyttsp5_hid_field *find_report_desc_field( + struct cyttsp5_core_data *cd, u32 usage_page, + u32 collection_usage_page) +{ + struct cyttsp5_hid_report *report = NULL; + struct cyttsp5_hid_field *field = NULL; + int i; + int j; + u32 field_cup; + u32 field_up; + + for (i = 0; i < cd->num_hid_reports; i++) { + report = cd->hid_reports[i]; + for (j = 0; j < report->num_fields; j++) { + field = report->fields[j]; + field_cup = field->collection_usage_pages + [HID_COLLECTION_LOGICAL]; + field_up = field->usage_page; + if (field_cup == collection_usage_page + && field_up == usage_page) { + return field; + } + } + } + + return NULL; +} + +static int fill_tch_abs(struct cyttsp5_tch_abs_params *tch_abs, + struct cyttsp5_hid_field *field) +{ + tch_abs->ofs = field->offset / 8; + tch_abs->size = field->report_size / 8; + if (field->report_size % 8) + tch_abs->size += 1; + tch_abs->min = 0; + tch_abs->max = 1 << field->report_size; + tch_abs->bofs = field->offset - (tch_abs->ofs << 3); + + return 0; +} + +static struct cyttsp5_hid_report *find_report_desc(struct cyttsp5_core_data *cd, + u32 usage_page) +{ + struct cyttsp5_hid_report *report = NULL; + int i; + + for (i = 0; i < cd->num_hid_reports; i++) { + if (cd->hid_reports[i]->usage_page == usage_page) { + report = cd->hid_reports[i]; + break; + } + } + + return report; +} + +static int setup_report_descriptor(struct cyttsp5_core_data *cd) +{ + struct cyttsp5_sysinfo *si = &cd->sysinfo; + struct cyttsp5_hid_report *report; + struct cyttsp5_hid_field *field; + int i; + u32 tch_collection_usage_page = HID_CY_TCH_COL_USAGE_PG; + u32 btn_collection_usage_page = HID_CY_BTN_COL_USAGE_PG; + + for (i = CY_TCH_X; i < CY_TCH_NUM_ABS; i++) { + field = find_report_desc_field(cd, + cyttsp5_tch_abs_field_map[i], + tch_collection_usage_page); + if (field) { + dev_vdbg(cd->dev, + " Field %p: rep_cnt:%d rep_sz:%d off:%d data:%02X min:%d max:%d usage_page:%08X\n", + field, field->report_count, field->report_size, + field->offset, field->data_type, + field->logical_min, field->logical_max, + field->usage_page); + fill_tch_abs(&si->tch_abs[i], field); + si->tch_abs[i].report = 1; + dev_vdbg(cd->dev, "%s: ofs:%zu size:%zu min:%zu max:%zu bofs:%zu report:%d", + cyttsp5_tch_abs_string[i], + si->tch_abs[i].ofs, si->tch_abs[i].size, + si->tch_abs[i].min, si->tch_abs[i].max, + si->tch_abs[i].bofs, si->tch_abs[i].report); + + } else { + si->tch_abs[i].report = 0; + } + } + for (i = CY_TCH_TIME; i < CY_TCH_NUM_HDR; i++) { + field = find_report_desc_field(cd, + cyttsp5_tch_hdr_field_map[i], + tch_collection_usage_page); + if (field) { + dev_vdbg(cd->dev, + " Field %p: rep_cnt:%d rep_sz:%d off:%d data:%02X min:%d max:%d usage_page:%08X\n", + field, field->report_count, field->report_size, + field->offset, field->data_type, + field->logical_min, field->logical_max, + field->usage_page); + fill_tch_abs(&si->tch_hdr[i], field); + si->tch_hdr[i].report = 1; + dev_vdbg(cd->dev, "%s: ofs:%zu size:%zu min:%zu max:%zu bofs:%zu report:%d", + cyttsp5_tch_hdr_string[i], + si->tch_hdr[i].ofs, si->tch_hdr[i].size, + si->tch_hdr[i].min, si->tch_hdr[i].max, + si->tch_hdr[i].bofs, si->tch_hdr[i].report); + + } else { + si->tch_hdr[i].report = 0; + } + } + + report = find_report_desc(cd, tch_collection_usage_page); + if (report) { + si->desc.tch_report_id = report->id; + si->desc.tch_record_size = report->record_size / 8; + si->desc.tch_header_size = (report->header_size / 8) + 3; + } else { + si->desc.tch_report_id = HID_TOUCH_REPORT_ID; + si->desc.tch_record_size = TOUCH_REPORT_SIZE; + si->desc.tch_header_size = TOUCH_INPUT_HEADER_SIZE; + } + + report = find_report_desc(cd, btn_collection_usage_page); + if (report) + si->desc.btn_report_id = report->id; + else + si->desc.btn_report_id = HID_BTN_REPORT_ID; + + for (i = 0; i < cd->num_hid_reports; i++) { + struct cyttsp5_hid_report *report = cd->hid_reports[i]; + + switch (report->id) { + case HID_WAKEUP_REPORT_ID: + cd->features.easywake = 1; + break; + case HID_NOISE_METRIC_REPORT_ID: + cd->features.noise_metric = 1; + break; + case HID_TRACKING_HEATMAP_REPOR_ID: + cd->features.tracking_heatmap = 1; + break; + case HID_SENSOR_DATA_REPORT_ID: + cd->features.sensor_data = 1; + break; + default: + break; + } + } + + dev_dbg(cd->dev, "Features: easywake:%d noise_metric:%d tracking_heatmap:%d sensor_data:%d\n", + cd->features.easywake, cd->features.noise_metric, + cd->features.tracking_heatmap, + cd->features.sensor_data); + + return 0; +} + +static int cyttsp5_get_report_descriptor_(struct cyttsp5_core_data *cd) +{ + struct device *dev = cd->dev; + u8 cmd[2]; + int rc; + int t; + + /* Read report descriptor length and version */ + mutex_lock(&cd->system_lock); + cd->hid_cmd_state = 1; + mutex_unlock(&cd->system_lock); + + /* Set report descriptor register */ + memcpy(cmd, &cd->hid_desc.report_desc_register, + sizeof(cd->hid_desc.report_desc_register)); + + rc = cyttsp5_adap_write_read_specific(cd, 2, cmd, NULL); + if (rc) { + dev_err(dev, "%s: failed to get HID descriptor length and version, rc=%d\n", + __func__, rc); + goto error; + } + + t = wait_event_timeout(cd->wait_q, (cd->hid_cmd_state == 0), + msecs_to_jiffies(CY_HID_GET_REPORT_DESCRIPTOR_TIMEOUT)); + if (IS_TMO(t)) { + dev_err(cd->dev, "%s: HID get descriptor timed out\n", + __func__); + rc = -ETIME; + goto error; + } + + cyttsp5_pr_buf(cd->dev, cd->response_buf, + cd->hid_core.hid_report_desc_len, "Report Desc"); + + rc = parse_report_descriptor(cd, cd->response_buf + 3, + get_unaligned_le16(&cd->response_buf[0]) - 3); + if (rc) { + dev_err(cd->dev, "%s: Error parsing report descriptor r=%d\n", + __func__, rc); + } + + dev_dbg(cd->dev, "%s: %d reports found in descriptor\n", __func__, + cd->num_hid_reports); + + for (t = 0; t < cd->num_hid_reports; t++) { + struct cyttsp5_hid_report *report = cd->hid_reports[t]; + int j; + + dev_vdbg(cd->dev, + "Report %d: type:%d id:%02X size:%d fields:%d rec_fld_index:%d hdr_sz:%d rec_sz:%d usage_page:%08X\n", + t, report->type, report->id, + report->size, report->num_fields, + report->record_field_index, report->header_size, + report->record_size, report->usage_page); + + for (j = 0; j < report->num_fields; j++) { + struct cyttsp5_hid_field *field = report->fields[j]; + + dev_vdbg(cd->dev, + " Field %d: rep_cnt:%d rep_sz:%d off:%d data:%02X min:%d max:%d usage_page:%08X\n", + j, field->report_count, field->report_size, + field->offset, field->data_type, + field->logical_min, field->logical_max, + field->usage_page); + + dev_vdbg(cd->dev, " Collections Phys:%08X App:%08X Log:%08X\n", + field->collection_usage_pages + [HID_COLLECTION_PHYSICAL], + field->collection_usage_pages + [HID_COLLECTION_APPLICATION], + field->collection_usage_pages + [HID_COLLECTION_LOGICAL]); + } + } + + rc = setup_report_descriptor(cd); + + /* Free it for now */ + cyttsp5_free_hid_reports_(cd); + + dev_dbg(cd->dev, "%s: %d reports found in descriptor\n", __func__, + cd->num_hid_reports); + + goto exit; + +error: + mutex_lock(&cd->system_lock); + cd->hid_cmd_state = 0; + mutex_unlock(&cd->system_lock); +exit: + return rc; +} + +static int cyttsp5_get_mode(struct cyttsp5_core_data *cd, + struct cyttsp5_hid_desc *desc) +{ + if (CY_HID_APP_REPORT_ID == desc->packet_id) + return CY_MODE_OPERATIONAL; + else if (CY_HID_BL_REPORT_ID == desc->packet_id) + return CY_MODE_BOOTLOADER; + + return CY_MODE_UNKNOWN; +} + +static int _cyttsp5_request_get_mode(struct device *dev, int protect, u8 *mode) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + int rc; + + if (protect) + rc = cyttsp5_get_hid_descriptor(cd, &cd->hid_desc); + else + rc = cyttsp5_get_hid_descriptor_(cd, &cd->hid_desc); + + if (rc) + *mode = CY_MODE_UNKNOWN; + else + *mode = cyttsp5_get_mode(cd, &cd->hid_desc); + + return rc; +} + +static void cyttsp5_queue_startup_(struct cyttsp5_core_data *cd) +{ + if (cd->startup_state == STARTUP_NONE) { + cd->startup_state = STARTUP_QUEUED; + schedule_work(&cd->startup_work); + dev_info(cd->dev, "%s: cyttsp5_startup queued\n", __func__); + } else { + dev_dbg(cd->dev, "%s: startup_state = %d\n", __func__, + cd->startup_state); + } +} + +static void cyttsp5_queue_startup(struct cyttsp5_core_data *cd) +{ + mutex_lock(&cd->system_lock); + cyttsp5_queue_startup_(cd); + mutex_unlock(&cd->system_lock); +} + +static void call_atten_cb(struct cyttsp5_core_data *cd, + enum cyttsp5_atten_type type, int mode) +{ + struct atten_node *atten, *atten_n; + + dev_vdbg(cd->dev, "%s: check list type=%d mode=%d\n", + __func__, type, mode); + spin_lock(&cd->spinlock); + list_for_each_entry_safe(atten, atten_n, + &cd->atten_list[type], node) { + if (!mode || atten->mode & mode) { + spin_unlock(&cd->spinlock); + dev_vdbg(cd->dev, "%s: attention for '%s'", __func__, + dev_name(atten->dev)); + atten->func(atten->dev); + spin_lock(&cd->spinlock); + } + } + spin_unlock(&cd->spinlock); +} + +static void cyttsp5_start_wd_timer(struct cyttsp5_core_data *cd) +{ + if (!CY_WATCHDOG_TIMEOUT) + return; + + mod_timer(&cd->watchdog_timer, jiffies + + msecs_to_jiffies(CY_WATCHDOG_TIMEOUT)); +} + +static void cyttsp5_stop_wd_timer(struct cyttsp5_core_data *cd) +{ + if (!CY_WATCHDOG_TIMEOUT) + return; + + /* + * Ensure we wait until the watchdog timer + * running on a different CPU finishes + */ + del_timer_sync(&cd->watchdog_timer); + cancel_work_sync(&cd->watchdog_work); + del_timer_sync(&cd->watchdog_timer); +} + +static int start_fw_upgrade(void *data) +{ + struct cyttsp5_core_data *cd = (struct cyttsp5_core_data *)data; + + call_atten_cb(cd, CY_ATTEN_LOADER, 0); + return 0; +} + +static void cyttsp5_watchdog_work(struct work_struct *work) +{ + struct cyttsp5_core_data *cd = + container_of(work, struct cyttsp5_core_data, + watchdog_work); + int rc; + /*fix CDT207254 + *if found the current sleep_state is SS_SLEEPING + *then no need to request_exclusive, directly return + */ + if (cd->sleep_state == SS_SLEEPING) + return; + + rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + goto queue_startup; + } + + rc = cyttsp5_hid_output_null_(cd); + + if (release_exclusive(cd, cd->dev) < 0) + dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); + +queue_startup: + if (rc) { + dev_err(cd->dev, + "%s: failed to access device in watchdog timer r=%d\n", + __func__, rc); + + /* Already tried FW upgrade because of watchdog but failed */ + if (cd->startup_retry_count > CY_WATCHDOG_RETRY_COUNT) + return; + + if (cd->startup_retry_count++ < CY_WATCHDOG_RETRY_COUNT) + cyttsp5_queue_startup(cd); + else + kthread_run(start_fw_upgrade, cd, "cyttp5_loader"); + + return; + } + + cyttsp5_start_wd_timer(cd); +} + +static void cyttsp5_watchdog_timer(struct timer_list *t) +{ + struct cyttsp5_core_data *cd = from_timer(cd, t, watchdog_timer); + + if (!cd) + return; + + dev_vdbg(cd->dev, "%s: Watchdog timer triggered\n", __func__); + + if (!work_pending(&cd->watchdog_work)) + schedule_work(&cd->watchdog_work); +} + +static int cyttsp5_put_device_into_easy_wakeup_(struct cyttsp5_core_data *cd) +{ + int rc; + u8 status = 0; + + mutex_lock(&cd->system_lock); + cd->wait_until_wake = 0; + mutex_unlock(&cd->system_lock); + + rc = cyttsp5_hid_output_enter_easywake_state_(cd, + cd->easy_wakeup_gesture, &status); + if (rc || status == 0) + return -EBUSY; + + return rc; +} + +static int cyttsp5_put_device_into_deep_sleep_(struct cyttsp5_core_data *cd) +{ + int rc; + + rc = cyttsp5_hid_cmd_set_power_(cd, HID_POWER_SLEEP); + if (rc) + rc = -EBUSY; + return rc; +} + +static int cyttsp5_put_device_into_sleep_(struct cyttsp5_core_data *cd) +{ + int rc; + + if (IS_DEEP_SLEEP_CONFIGURED(cd->easy_wakeup_gesture)) + rc = cyttsp5_put_device_into_deep_sleep_(cd); + else + rc = cyttsp5_put_device_into_easy_wakeup_(cd); + + return rc; +} + +static int cyttsp5_core_poweroff_device_(struct cyttsp5_core_data *cd) +{ + int rc; + + if (cd->irq_enabled) { + cd->irq_enabled = false; + disable_irq_nosync(cd->irq); + } + + rc = cd->cpdata->power(cd->cpdata, 0, cd->dev, 0); + if (rc < 0) + dev_err(cd->dev, "%s: HW Power down fails r=%d\n", + __func__, rc); + return rc; +} + +static int cyttsp5_core_sleep_(struct cyttsp5_core_data *cd) +{ + int rc; + + mutex_lock(&cd->system_lock); + if (cd->sleep_state == SS_SLEEP_OFF) { + cd->sleep_state = SS_SLEEPING; + } else { + mutex_unlock(&cd->system_lock); + return 1; + } + mutex_unlock(&cd->system_lock); + + /* Ensure watchdog and startup works stopped */ + cyttsp5_stop_wd_timer(cd); + cancel_work_sync(&cd->startup_work); + cyttsp5_stop_wd_timer(cd); + + if (cd->cpdata->flags & CY_CORE_FLAG_POWEROFF_ON_SLEEP) + rc = cyttsp5_core_poweroff_device_(cd); + else + rc = cyttsp5_put_device_into_sleep_(cd); + + mutex_lock(&cd->system_lock); + cd->sleep_state = SS_SLEEP_ON; + mutex_unlock(&cd->system_lock); + + return rc; +} + +static int cyttsp5_core_sleep(struct cyttsp5_core_data *cd) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = cyttsp5_core_sleep_(cd); + + if (release_exclusive(cd, cd->dev) < 0) + dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); + else + dev_vdbg(cd->dev, "%s: pass release exclusive\n", __func__); + + return rc; +} + +static int cyttsp5_wakeup_host(struct cyttsp5_core_data *cd) +{ +#ifndef EASYWAKE_TSG6 + /* TSG5 EasyWake */ + int rc = 0; + int event_id; + int size = get_unaligned_le16(&cd->input_buf[0]); + + /* Validate report */ + if (size != 4 || cd->input_buf[2] != 4) + rc = -EINVAL; + + cd->wake_initiated_by_device = 1; + event_id = cd->input_buf[3]; + + dev_dbg(cd->dev, "%s: e=%d, rc=%d\n", __func__, event_id, rc); + + if (rc) { + cyttsp5_core_sleep_(cd); + goto exit; + } + + /* attention WAKE */ + call_atten_cb(cd, CY_ATTEN_WAKE, 0); +exit: + return rc; +#else + /* TSG6 FW1.3 EasyWake */ + int rc = 0; + int i = 0; + int report_length; + + /* Validate report */ + if (cd->input_buf[2] != 4) + rc = -EINVAL; + + cd->wake_initiated_by_device = 1; + + dev_dbg(cd->dev, "%s: rc=%d\n", __func__, rc); + + if (rc) { + cyttsp5_core_sleep_(cd); + goto exit; + } + + /* Get gesture id and gesture data length */ + cd->gesture_id = cd->input_buf[3]; + report_length = (cd->input_buf[1] << 8) | (cd->input_buf[0]); + cd->gesture_data_length = report_length - 4; + + dev_dbg(cd->dev, "%s: gesture_id = %d, gesture_data_length = %d\n", + __func__, cd->gesture_id, cd->gesture_data_length); + + for (i = 0; i < cd->gesture_data_length; i++) + cd->gesture_data[i] = cd->input_buf[4 + i]; + + /* attention WAKE */ + call_atten_cb(cd, CY_ATTEN_WAKE, 0); +exit: + return rc; +#endif +} + +static void cyttsp5_get_touch_axis(struct cyttsp5_core_data *cd, + int *axis, int size, int max, u8 *data, int bofs) +{ + int nbyte; + int next; + + for (nbyte = 0, *axis = 0, next = 0; nbyte < size; nbyte++) { + *axis = *axis + ((data[next] >> bofs) << (nbyte * 8)); + next++; + } + + *axis &= max - 1; +} + +static int move_tracking_hetmap_data(struct cyttsp5_core_data *cd, + struct cyttsp5_sysinfo *si) +{ +#ifdef TTHE_TUNER_SUPPORT + int size = get_unaligned_le16(&cd->input_buf[0]); + + if (size) + tthe_print(cd, cd->input_buf, size, "THM="); +#endif + memcpy(si->xy_mode, cd->input_buf, SENSOR_HEADER_SIZE); + return 0; +} + +static int move_sensor_data(struct cyttsp5_core_data *cd, + struct cyttsp5_sysinfo *si) +{ +#ifdef TTHE_TUNER_SUPPORT + int size = get_unaligned_le16(&cd->input_buf[0]); + + if (size) + tthe_print(cd, cd->input_buf, size, "sensor_monitor="); +#endif + memcpy(si->xy_mode, cd->input_buf, SENSOR_HEADER_SIZE); + return 0; +} + +static int move_button_data(struct cyttsp5_core_data *cd, + struct cyttsp5_sysinfo *si) +{ +#ifdef TTHE_TUNER_SUPPORT + int size = get_unaligned_le16(&cd->input_buf[0]); + + if (size) + tthe_print(cd, cd->input_buf, size, "OpModeData="); +#endif + memcpy(si->xy_mode, cd->input_buf, BTN_INPUT_HEADER_SIZE); + cyttsp5_pr_buf(cd->dev, (u8 *)si->xy_mode, BTN_INPUT_HEADER_SIZE, + "xy_mode"); + + memcpy(si->xy_data, &cd->input_buf[BTN_INPUT_HEADER_SIZE], + BTN_REPORT_SIZE); + cyttsp5_pr_buf(cd->dev, (u8 *)si->xy_data, BTN_REPORT_SIZE, "xy_data"); + return 0; +} + +static int move_touch_data(struct cyttsp5_core_data *cd, + struct cyttsp5_sysinfo *si) +{ + int max_tch = si->sensing_conf_data.max_tch; + int num_cur_tch; + int length; + struct cyttsp5_tch_abs_params *tch = &si->tch_hdr[CY_TCH_NUM]; +#ifdef TTHE_TUNER_SUPPORT + int size = get_unaligned_le16(&cd->input_buf[0]); + + if (size) + tthe_print(cd, cd->input_buf, size, "OpModeData="); +#endif + + memcpy(si->xy_mode, cd->input_buf, si->desc.tch_header_size); + cyttsp5_pr_buf(cd->dev, (u8 *)si->xy_mode, si->desc.tch_header_size, + "xy_mode"); + + cyttsp5_get_touch_axis(cd, &num_cur_tch, tch->size, + tch->max, si->xy_mode + 3 + tch->ofs, tch->bofs); + if (unlikely(num_cur_tch > max_tch)) + num_cur_tch = max_tch; + + length = num_cur_tch * si->desc.tch_record_size; + + memcpy(si->xy_data, &cd->input_buf[si->desc.tch_header_size], length); + cyttsp5_pr_buf(cd->dev, (u8 *)si->xy_data, length, "xy_data"); + return 0; +} + +static int parse_touch_input(struct cyttsp5_core_data *cd, int size) +{ + struct cyttsp5_sysinfo *si = &cd->sysinfo; + int report_id = cd->input_buf[2]; + int rc = -EINVAL; + + dev_vdbg(cd->dev, "%s: Received touch report\n", __func__); + if (!si->ready) { + dev_err(cd->dev, + "%s: Need system information to parse touches\n", + __func__); + return 0; + } + + if (!si->xy_mode || !si->xy_data) + return rc; + + if (report_id == si->desc.tch_report_id) + rc = move_touch_data(cd, si); + else if (report_id == si->desc.btn_report_id) + rc = move_button_data(cd, si); + else if (report_id == HID_SENSOR_DATA_REPORT_ID) + rc = move_sensor_data(cd, si); + else if (report_id == HID_TRACKING_HEATMAP_REPOR_ID) + rc = move_tracking_hetmap_data(cd, si); + + if (rc) + return rc; + + /* attention IRQ */ + call_atten_cb(cd, CY_ATTEN_IRQ, cd->mode); + + return 0; +} + +static int parse_command_input(struct cyttsp5_core_data *cd, int size) +{ + dev_vdbg(cd->dev, "%s: Received cmd interrupt\n", __func__); + + memcpy(cd->response_buf, cd->input_buf, size); + + mutex_lock(&cd->system_lock); + cd->hid_cmd_state = 0; + mutex_unlock(&cd->system_lock); + wake_up(&cd->wait_q); + + return 0; +} + +static int cyttsp5_parse_input(struct cyttsp5_core_data *cd) +{ + int report_id; + int is_command = 0; + int size; + + size = get_unaligned_le16(&cd->input_buf[0]); + + /* check reset */ + if (size == 0) { + dev_dbg(cd->dev, "%s: Reset complete\n", __func__); + memcpy(cd->response_buf, cd->input_buf, 2); + mutex_lock(&cd->system_lock); + if (!cd->hid_reset_cmd_state && !cd->hid_cmd_state) { + mutex_unlock(&cd->system_lock); + dev_dbg(cd->dev, "%s: Device Initiated Reset\n", + __func__); + return 0; + } + + cd->hid_reset_cmd_state = 0; + if (cd->hid_cmd_state == HID_OUTPUT_START_BOOTLOADER + 1 + || cd->hid_cmd_state == + HID_OUTPUT_BL_LAUNCH_APP + 1 + || cd->hid_cmd_state == + HID_OUTPUT_USER_CMD + 1) + cd->hid_cmd_state = 0; + wake_up(&cd->wait_q); + mutex_unlock(&cd->system_lock); + return 0; + } else if (size == 2 || size >= CY_PIP_1P7_EMPTY_BUF) + /* Before PIP 1.7, empty buffer is 0x0002; + From PIP 1.7, empty buffer is 0xFFXX */ + return 0; + + report_id = cd->input_buf[2]; + dev_vdbg(cd->dev, "%s: report_id:%X\n", __func__, report_id); + + /* Check wake-up report */ + if (report_id == HID_WAKEUP_REPORT_ID) { + cyttsp5_wakeup_host(cd); + return 0; + } + + /* update watchdog expire time */ + mod_timer_pending(&cd->watchdog_timer, jiffies + + msecs_to_jiffies(CY_WATCHDOG_TIMEOUT)); + + if (report_id != cd->sysinfo.desc.tch_report_id + && report_id != cd->sysinfo.desc.btn_report_id + && report_id != HID_SENSOR_DATA_REPORT_ID + && report_id != HID_TRACKING_HEATMAP_REPOR_ID) + is_command = 1; + + if (unlikely(is_command)) { + parse_command_input(cd, size); + return 0; + } + parse_touch_input(cd, size); + return 0; +} + +static int cyttsp5_read_input(struct cyttsp5_core_data *cd) +{ + struct device *dev = cd->dev; + int rc; + int t; + + /* added as workaround to CDT170960: easywake failure */ + /* Interrupt for easywake, wait for bus controller to wake */ + mutex_lock(&cd->system_lock); + if (!IS_DEEP_SLEEP_CONFIGURED(cd->easy_wakeup_gesture)) { + if (cd->sleep_state == SS_SLEEP_ON) { + mutex_unlock(&cd->system_lock); + if (!dev->power.is_suspended) + goto read; + t = wait_event_timeout(cd->wait_q, + (cd->wait_until_wake == 1), + msecs_to_jiffies(2000)); + if (IS_TMO(t)) + cyttsp5_queue_startup(cd); + goto read; + } + } + mutex_unlock(&cd->system_lock); + +read: + rc = cyttsp5_adap_read_default_nosize(cd, cd->input_buf, CY_MAX_INPUT); + if (rc) { + dev_err(dev, "%s: Error getting report, r=%d\n", + __func__, rc); + return rc; + } + dev_vdbg(dev, "%s: Read input successfully\n", __func__); + return rc; +} + +static bool cyttsp5_check_irq_asserted(struct cyttsp5_core_data *cd) +{ +#ifdef ENABLE_WORKAROUND_FOR_GLITCH_AFTER_BL_LAUNCH_APP + /* Workaround for FW defect, CDT165308 + * bl_launch app creates a glitch in IRQ line */ + if (cd->hid_cmd_state == HID_OUTPUT_BL_LAUNCH_APP + 1 + && cd->cpdata->irq_stat){ + /* + in X1S panel and GC1546 panel, the width for the INT + glitch is about 4us,the normal INT width of response + will last more than 200us, so use 10us delay + for distinguish the glitch the normal INT is enough. + */ + udelay(10); + if (cd->cpdata->irq_stat(cd->cpdata, cd->dev) + != CY_IRQ_ASSERTED_VALUE) + return false; + } +#endif + return true; +} + + +static irqreturn_t cyttsp5_irq(int irq, void *handle) +{ + struct cyttsp5_core_data *cd = handle; + int rc; + + if (!cyttsp5_check_irq_asserted(cd)) + return IRQ_HANDLED; + + rc = cyttsp5_read_input(cd); + if (!rc) + cyttsp5_parse_input(cd); + + return IRQ_HANDLED; +} + +int _cyttsp5_subscribe_attention(struct device *dev, + enum cyttsp5_atten_type type, char *id, int (*func)(struct device *), + int mode) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + struct atten_node *atten, *atten_new; + + atten_new = kzalloc(sizeof(*atten_new), GFP_KERNEL); + if (!atten_new) + return -ENOMEM; + + dev_dbg(cd->dev, "%s from '%s'\n", __func__, dev_name(cd->dev)); + + spin_lock(&cd->spinlock); + list_for_each_entry(atten, &cd->atten_list[type], node) { + if (atten->id == id && atten->mode == mode) { + spin_unlock(&cd->spinlock); + kfree(atten_new); + dev_vdbg(cd->dev, "%s: %s=%p %s=%d\n", + __func__, + "already subscribed attention", + dev, "mode", mode); + + return 0; + } + } + + atten_new->id = id; + atten_new->dev = dev; + atten_new->mode = mode; + atten_new->func = func; + + list_add(&atten_new->node, &cd->atten_list[type]); + spin_unlock(&cd->spinlock); + + return 0; +} + +int _cyttsp5_unsubscribe_attention(struct device *dev, + enum cyttsp5_atten_type type, char *id, int (*func)(struct device *), + int mode) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + struct atten_node *atten, *atten_n; + + spin_lock(&cd->spinlock); + list_for_each_entry_safe(atten, atten_n, &cd->atten_list[type], node) { + if (atten->id == id && atten->mode == mode) { + list_del(&atten->node); + spin_unlock(&cd->spinlock); + kfree(atten); + dev_vdbg(cd->dev, "%s: %s=%p %s=%d\n", + __func__, + "unsub for atten->dev", atten->dev, + "atten->mode", atten->mode); + return 0; + } + } + spin_unlock(&cd->spinlock); + + return -ENODEV; +} + +static int _cyttsp5_request_exclusive(struct device *dev, + int timeout_ms) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + return request_exclusive(cd, (void *)dev, timeout_ms); +} + +static int _cyttsp5_release_exclusive(struct device *dev) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + return release_exclusive(cd, (void *)dev); +} + +static int cyttsp5_reset(struct cyttsp5_core_data *cd) +{ + int rc; + + /* reset hardware */ + dev_dbg(cd->dev, "%s: reset hw...\n", __func__); + rc = cyttsp5_hw_reset(cd); + if (rc < 0) + dev_err(cd->dev, "%s: %s dev='%s' r=%d\n", __func__, + "Fail hw reset", dev_name(cd->dev), rc); + return rc; +} + +static int cyttsp5_reset_and_wait(struct cyttsp5_core_data *cd) +{ + int rc; + int t; + + mutex_lock(&cd->system_lock); + cd->hid_reset_cmd_state = 1; + mutex_unlock(&cd->system_lock); + + rc = cyttsp5_reset(cd); + if (rc < 0) + goto error; + + t = wait_event_timeout(cd->wait_q, (cd->hid_reset_cmd_state == 0), + msecs_to_jiffies(CY_HID_RESET_TIMEOUT)); + if (IS_TMO(t)) { + dev_err(cd->dev, "%s: reset timed out\n", + __func__); + rc = -ETIME; + goto error; + } + + goto exit; + +error: + mutex_lock(&cd->system_lock); + cd->hid_reset_cmd_state = 0; + mutex_unlock(&cd->system_lock); +exit: + return rc; +} + +/* + * returns err if refused or timeout(core uses fixed timeout period) occurs; + * blocks until ISR occurs + */ +static int _cyttsp5_request_reset(struct device *dev) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + int rc; + + mutex_lock(&cd->system_lock); + cd->hid_reset_cmd_state = 1; + mutex_unlock(&cd->system_lock); + + rc = cyttsp5_reset(cd); + if (rc < 0) { + dev_err(cd->dev, "%s: Error on h/w reset r=%d\n", + __func__, rc); + mutex_lock(&cd->system_lock); + cd->hid_reset_cmd_state = 0; + mutex_unlock(&cd->system_lock); + } + + return rc; +} + +/* + * returns err if refused ; if no error then restart has completed + * and system is in normal operating mode + */ +static int _cyttsp5_request_restart(struct device *dev, bool wait) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + cyttsp5_queue_startup(cd); + + if (wait) + wait_event(cd->wait_q, cd->startup_state == STARTUP_NONE); + + return 0; +} + +/* + * returns NULL if sysinfo has not been acquired from the device yet + */ +struct cyttsp5_sysinfo *_cyttsp5_request_sysinfo(struct device *dev) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + if (cd->sysinfo.ready) + return &cd->sysinfo; + + return NULL; +} + +static struct cyttsp5_loader_platform_data *_cyttsp5_request_loader_pdata( + struct device *dev) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + return cd->pdata->loader_pdata; +} + +static int _cyttsp5_request_start_wd(struct device *dev) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + cyttsp5_start_wd_timer(cd); + return 0; +} + +static int _cyttsp5_request_stop_wd(struct device *dev) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + cyttsp5_stop_wd_timer(cd); + return 0; +} + +static int cyttsp5_core_wake_device_from_deep_sleep_( + struct cyttsp5_core_data *cd) +{ + int rc; + + rc = cyttsp5_hid_cmd_set_power_(cd, HID_POWER_ON); + if (rc) + rc = -EAGAIN; + + /* Prevent failure on sequential wake/sleep requests from OS */ + msleep(20); + + return rc; +} + +static int cyttsp5_core_wake_device_(struct cyttsp5_core_data *cd) +{ + if (!IS_DEEP_SLEEP_CONFIGURED(cd->easy_wakeup_gesture)) { + mutex_lock(&cd->system_lock); + cd->wait_until_wake = 1; + mutex_unlock(&cd->system_lock); + wake_up(&cd->wait_q); + msleep(20); + + if (cd->wake_initiated_by_device) { + cd->wake_initiated_by_device = 0; + return 0; + } + } + + return cyttsp5_core_wake_device_from_deep_sleep_(cd); +} + +static int cyttsp5_restore_parameters_(struct cyttsp5_core_data *cd) +{ + struct param_node *param; + int rc = 0; + + if (!(cd->cpdata->flags & CY_CORE_FLAG_RESTORE_PARAMETERS)) + goto exit; + + spin_lock(&cd->spinlock); + list_for_each_entry(param, &cd->param_list, node) { + spin_unlock(&cd->spinlock); + dev_vdbg(cd->dev, "%s: Parameter id:%d value:%d\n", + __func__, param->id, param->value); + rc = cyttsp5_hid_output_set_param_(cd, param->id, + param->value, param->size); + if (rc) + goto exit; + spin_lock(&cd->spinlock); + } + spin_unlock(&cd->spinlock); +exit: + return rc; +} + +static int _fast_startup(struct cyttsp5_core_data *cd) +{ + int retry = CY_CORE_STARTUP_RETRY_COUNT; + int rc; + +reset: + if (retry != CY_CORE_STARTUP_RETRY_COUNT) + dev_dbg(cd->dev, "%s: Retry %d\n", __func__, + CY_CORE_STARTUP_RETRY_COUNT - retry); + + rc = cyttsp5_get_hid_descriptor_(cd, &cd->hid_desc); + if (rc < 0) { + dev_err(cd->dev, "%s: Error on getting HID descriptor r=%d\n", + __func__, rc); + if (retry--) + goto reset; + goto exit; + } + cd->mode = cyttsp5_get_mode(cd, &cd->hid_desc); + + if (cd->mode == CY_MODE_BOOTLOADER) { + dev_info(cd->dev, "%s: Bootloader mode\n", __func__); + rc = cyttsp5_hid_output_bl_launch_app_(cd); + if (rc < 0) { + dev_err(cd->dev, "%s: Error on launch app r=%d\n", + __func__, rc); + if (retry--) + goto reset; + goto exit; + } + rc = cyttsp5_get_hid_descriptor_(cd, &cd->hid_desc); + if (rc < 0) { + dev_err(cd->dev, + "%s: Error on getting HID descriptor r=%d\n", + __func__, rc); + if (retry--) + goto reset; + goto exit; + } + cd->mode = cyttsp5_get_mode(cd, &cd->hid_desc); + if (cd->mode == CY_MODE_BOOTLOADER) { + if (retry--) + goto reset; + goto exit; + } + } + + rc = cyttsp5_restore_parameters_(cd); + if (rc) + dev_err(cd->dev, "%s: failed to restore parameters rc=%d\n", + __func__, rc); + +exit: + return rc; +} + +static int cyttsp5_core_poweron_device_(struct cyttsp5_core_data *cd) +{ + struct device *dev = cd->dev; + int rc; + + rc = cd->cpdata->power(cd->cpdata, 1, dev, 0); + if (rc < 0) { + dev_err(dev, "%s: HW Power up fails r=%d\n", __func__, rc); + goto exit; + } + + if (!cd->irq_enabled) { + cd->irq_enabled = true; + enable_irq(cd->irq); + } + + rc = _fast_startup(cd); +exit: + return rc; +} + +static int cyttsp5_core_wake_(struct cyttsp5_core_data *cd) +{ + int rc; + + mutex_lock(&cd->system_lock); + if (cd->sleep_state == SS_SLEEP_ON) { + cd->sleep_state = SS_WAKING; + } else { + mutex_unlock(&cd->system_lock); + return 1; + } + mutex_unlock(&cd->system_lock); + + if (cd->cpdata->flags & CY_CORE_FLAG_POWEROFF_ON_SLEEP) + rc = cyttsp5_core_poweron_device_(cd); + else + rc = cyttsp5_core_wake_device_(cd); + + mutex_lock(&cd->system_lock); + cd->sleep_state = SS_SLEEP_OFF; + mutex_unlock(&cd->system_lock); + + cyttsp5_start_wd_timer(cd); + return rc; +} + +static int cyttsp5_core_wake(struct cyttsp5_core_data *cd) +{ + int rc; + + rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + return rc; + } + + rc = cyttsp5_core_wake_(cd); + + if (release_exclusive(cd, cd->dev) < 0) + dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); + else + dev_vdbg(cd->dev, "%s: pass release exclusive\n", __func__); + + return rc; +} + +static int cyttsp5_get_ic_crc_(struct cyttsp5_core_data *cd, u8 ebid) +{ + struct cyttsp5_sysinfo *si = &cd->sysinfo; + int rc; + u8 status; + u16 calculated_crc = 0; + u16 stored_crc = 0; + + rc = cyttsp5_hid_output_suspend_scanning_(cd); + if (rc) + goto error; + + rc = cyttsp5_hid_output_verify_config_block_crc_(cd, ebid, &status, + &calculated_crc, &stored_crc); + if (rc) + goto exit; + + if (status) { + rc = -EINVAL; + goto exit; + } + + si->ttconfig.crc = stored_crc; + +exit: + cyttsp5_hid_output_resume_scanning_(cd); +error: + dev_dbg(cd->dev, "%s: CRC: ebid:%d, crc:0x%04X\n", + __func__, ebid, si->ttconfig.crc); + return rc; +} + +static int cyttsp5_check_and_deassert_int(struct cyttsp5_core_data *cd) +{ + u16 size; + u8 buf[2]; + u8 *p; + u8 retry = 3; + int rc; + + do { + rc = cyttsp5_adap_read_default(cd, buf, 2); + if (rc < 0) + return rc; + size = get_unaligned_le16(&buf[0]); + + if (size == 2 || size == 0 || size >= CY_PIP_1P7_EMPTY_BUF) + return 0; + + p = kzalloc(size, GFP_KERNEL); + if (!p) + return -ENOMEM; + + rc = cyttsp5_adap_read_default(cd, p, size); + kfree(p); + if (rc < 0) + return rc; + } while (retry--); + + return -EINVAL; +} + +static int cyttsp5_startup_(struct cyttsp5_core_data *cd, bool reset) +{ + int retry = CY_CORE_STARTUP_RETRY_COUNT; + int rc; + bool detected = false; + +#ifdef TTHE_TUNER_SUPPORT + tthe_print(cd, NULL, 0, "enter startup"); +#endif + + cyttsp5_stop_wd_timer(cd); + +reset: + if (retry != CY_CORE_STARTUP_RETRY_COUNT) + dev_dbg(cd->dev, "%s: Retry %d\n", __func__, + CY_CORE_STARTUP_RETRY_COUNT - retry); + + rc = cyttsp5_check_and_deassert_int(cd); + + if (reset || retry != CY_CORE_STARTUP_RETRY_COUNT) { + /* reset hardware */ + rc = cyttsp5_reset_and_wait(cd); + if (rc < 0) { + dev_err(cd->dev, "%s: Error on h/w reset r=%d\n", + __func__, rc); + if (retry--) + goto reset; + goto exit; + } + } + + rc = cyttsp5_get_hid_descriptor_(cd, &cd->hid_desc); + if (rc < 0) { + dev_err(cd->dev, "%s: Error on getting HID descriptor r=%d\n", + __func__, rc); + if (retry--) + goto reset; + goto exit; + } + cd->mode = cyttsp5_get_mode(cd, &cd->hid_desc); + + detected = true; + + /* Switch to bootloader mode to get Panel ID */ + if (cd->mode == CY_MODE_OPERATIONAL) { + rc = cyttsp5_hid_output_start_bootloader_(cd); + if (rc < 0) { + dev_err(cd->dev, "%s: Error on start bootloader r=%d\n", + __func__, rc); + if (retry--) + goto reset; + goto exit; + } + dev_info(cd->dev, "%s: Bootloader mode\n", __func__); + } + + cyttsp5_hid_output_bl_get_panel_id_(cd, &cd->panel_id); + + dev_dbg(cd->dev, "%s: Panel ID: 0x%02X\n", __func__, cd->panel_id); + + rc = cyttsp5_hid_output_bl_launch_app_(cd); + if (rc < 0) { + dev_err(cd->dev, "%s: Error on launch app r=%d\n", + __func__, rc); + if (retry--) + goto reset; + goto exit; + } + + rc = cyttsp5_get_hid_descriptor_(cd, &cd->hid_desc); + if (rc < 0) { + dev_err(cd->dev, + "%s: Error on getting HID descriptor r=%d\n", + __func__, rc); + if (retry--) + goto reset; + goto exit; + } + cd->mode = cyttsp5_get_mode(cd, &cd->hid_desc); + if (cd->mode == CY_MODE_BOOTLOADER) { + if (retry--) + goto reset; + goto exit; + } + + mutex_lock(&cd->system_lock); + /* Read descriptor lengths */ + cd->hid_core.hid_report_desc_len = + le16_to_cpu(cd->hid_desc.report_desc_len); + cd->hid_core.hid_max_input_len = + le16_to_cpu(cd->hid_desc.max_input_len); + cd->hid_core.hid_max_output_len = + le16_to_cpu(cd->hid_desc.max_output_len); + + cd->mode = cyttsp5_get_mode(cd, &cd->hid_desc); + if (cd->mode == CY_MODE_OPERATIONAL) + dev_info(cd->dev, "%s: Operational mode\n", __func__); + else if (cd->mode == CY_MODE_BOOTLOADER) + dev_info(cd->dev, "%s: Bootloader mode\n", __func__); + else if (cd->mode == CY_MODE_UNKNOWN) { + dev_err(cd->dev, "%s: Unknown mode\n", __func__); + rc = -ENODEV; + mutex_unlock(&cd->system_lock); + if (retry--) + goto reset; + goto exit; + } + mutex_unlock(&cd->system_lock); + + dev_info(cd->dev, "%s: Reading report descriptor\n", __func__); + rc = cyttsp5_get_report_descriptor_(cd); + if (rc < 0) { + dev_err(cd->dev, "%s: Error on getting report descriptor r=%d\n", + __func__, rc); + if (retry--) + goto reset; + goto exit; + } + + if (!cd->features.easywake) + cd->easy_wakeup_gesture = CY_CORE_EWG_NONE; + + rc = cyttsp5_hid_output_get_sysinfo_(cd); + if (rc) { + dev_err(cd->dev, "%s: Error on getting sysinfo r=%d\n", + __func__, rc); + if (retry--) + goto reset; + goto exit; + } + + dev_info(cd->dev, "cyttsp5 Protocol Version: %d.%d\n", + cd->sysinfo.cydata.pip_ver_major, + cd->sysinfo.cydata.pip_ver_minor); + + /* Read config version directly if PIP version < 1.2 */ + if (!IS_PIP_VER_GE(&cd->sysinfo, 1, 2)) { + rc = cyttsp5_get_config_ver_(cd); + if (rc) + dev_err(cd->dev, "%s: failed to read config version rc=%d\n", + __func__, rc); + } + + rc = cyttsp5_get_ic_crc_(cd, CY_TCH_PARM_EBID); + if (rc) + dev_err(cd->dev, "%s: failed to crc data rc=%d\n", + __func__, rc); + + rc = cyttsp5_restore_parameters_(cd); + if (rc) + dev_err(cd->dev, "%s: failed to restore parameters rc=%d\n", + __func__, rc); + + /* attention startup */ + call_atten_cb(cd, CY_ATTEN_STARTUP, 0); + +exit: + if (!rc) + cd->startup_retry_count = 0; + + cyttsp5_start_wd_timer(cd); + + if (!detected) + rc = -ENODEV; + +#ifdef TTHE_TUNER_SUPPORT + tthe_print(cd, NULL, 0, "exit startup"); +#endif + + return rc; +} + +static int cyttsp5_startup(struct cyttsp5_core_data *cd, bool reset) +{ + int rc; + + mutex_lock(&cd->system_lock); + cd->startup_state = STARTUP_RUNNING; + mutex_unlock(&cd->system_lock); + + rc = request_exclusive(cd, cd->dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(cd->dev, "%s: fail get exclusive ex=%p own=%p\n", + __func__, cd->exclusive_dev, cd->dev); + goto exit; + } + + rc = cyttsp5_startup_(cd, reset); + + if (release_exclusive(cd, cd->dev) < 0) + /* Don't return fail code, mode is already changed. */ + dev_err(cd->dev, "%s: fail to release exclusive\n", __func__); + else + dev_vdbg(cd->dev, "%s: pass release exclusive\n", __func__); + +exit: + mutex_lock(&cd->system_lock); + cd->startup_state = STARTUP_NONE; + mutex_unlock(&cd->system_lock); + + /* Wake the waiters for end of startup */ + wake_up(&cd->wait_q); + + return rc; +} + +static void cyttsp5_startup_work_function(struct work_struct *work) +{ + struct cyttsp5_core_data *cd = container_of(work, + struct cyttsp5_core_data, startup_work); + int rc; + + rc = cyttsp5_startup(cd, true); + if (rc < 0) + dev_err(cd->dev, "%s: Fail queued startup r=%d\n", + __func__, rc); +} + +/* + * CONFIG_PM_RUNTIME option is removed in 3.19.0. + */ +#if defined(CONFIG_PM_RUNTIME) || \ + (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)) +static int cyttsp5_core_rt_suspend(struct device *dev) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + int rc; + + rc = cyttsp5_core_sleep(cd); + if (rc < 0) { + dev_err(dev, "%s: Error on sleep\n", __func__); + return -EAGAIN; + } + return 0; +} + +static int cyttsp5_core_rt_resume(struct device *dev) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + int rc; + + rc = cyttsp5_core_wake(cd); + if (rc < 0) { + dev_err(dev, "%s: Error on wake\n", __func__); + return -EAGAIN; + } + + return 0; +} +#endif + +#if defined(CONFIG_PM_SLEEP) +static int cyttsp5_core_suspend(struct device *dev) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + if (pm_suspend_target_state == PM_SUSPEND_MEM) { + cyttsp5_core_sleep(cd); + pinctrl_pm_select_sleep_state(dev); + /* + * Disable interrupt here to avoid that pending IRQ makes + * the entering to low power state fail. + */ + disable_irq(cd->irq); + } else { + /* + * We need it to be a wakeup source for other suspend + * types than 'mem'. + */ + if (device_may_wakeup(dev)) + enable_irq_wake(cd->irq); + } + + return 0; +} + +static int cyttsp5_core_resume(struct device *dev) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + if (pm_suspend_target_state == PM_SUSPEND_MEM) { + enable_irq(cd->irq); + pinctrl_pm_select_default_state(dev); + cyttsp5_core_wake(cd); + } else { + if (device_may_wakeup(dev)) + disable_irq_wake(cd->irq); + } + + return 0; +} +#endif + +#if NEED_SUSPEND_NOTIFIER +static int cyttsp5_pm_notifier(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct cyttsp5_core_data *cd = container_of(nb, + struct cyttsp5_core_data, pm_notifier); + + if (action == PM_SUSPEND_PREPARE) { + dev_dbg(cd->dev, "%s: Suspend prepare\n", __func__); + + /* + * If not runtime PM suspended, either call runtime + * PM suspend callback or wait until it finishes + */ + if (!pm_runtime_suspended(cd->dev)) + pm_runtime_suspend(cd->dev); + + (void) cyttsp5_core_suspend(cd->dev); + } + + return NOTIFY_DONE; +} +#endif + +const struct dev_pm_ops cyttsp5_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(cyttsp5_core_suspend, cyttsp5_core_resume) + SET_RUNTIME_PM_OPS(cyttsp5_core_rt_suspend, cyttsp5_core_rt_resume, + NULL) +}; +EXPORT_SYMBOL_GPL(cyttsp5_pm_ops); + +/* + * Show Firmware version via sysfs + */ +static ssize_t cyttsp5_ic_ver_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + struct cyttsp5_cydata *cydata = &cd->sysinfo.cydata; + + return sprintf(buf, + "%s: 0x%02X\n" + "%s: 0x%02X\n" + "%s: 0x%08X\n" + "%s: 0x%04X\n" + "%s: 0x%02X\n" + "%s: 0x%02X\n" + "%s: 0x%02X\n" + "%s: 0x%02X\n", + "Firmware Major Version", cydata->fw_ver_major, + "Firmware Minor Version", cydata->fw_ver_minor, + "Revision Control Number", cydata->revctrl, + "Firmware Configuration Version", cydata->fw_ver_conf, + "Bootloader Major Version", cydata->bl_ver_major, + "Bootloader Minor Version", cydata->bl_ver_minor, + "Protocol Major Version", cydata->pip_ver_major, + "Protocol Minor Version", cydata->pip_ver_minor); +} + +/* + * Show Driver version via sysfs + */ +static ssize_t cyttsp5_drv_ver_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, CY_MAX_PRBUF_SIZE, + "Driver: %s\nVersion: %s\nDate: %s\n", + cy_driver_core_name, cy_driver_core_version, + cy_driver_core_date); +} + +/* + * HW reset via sysfs + */ +static ssize_t cyttsp5_hw_reset_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + int rc; + + rc = cyttsp5_startup(cd, true); + if (rc < 0) + dev_err(dev, "%s: HW reset failed r=%d\n", + __func__, rc); + + return size; +} + +/* + * Show IRQ status via sysfs + */ +static ssize_t cyttsp5_hw_irq_stat_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + int retval; + + if (cd->cpdata->irq_stat) { + retval = cd->cpdata->irq_stat(cd->cpdata, dev); + switch (retval) { + case 0: + return snprintf(buf, CY_MAX_PRBUF_SIZE, + "Interrupt line is LOW.\n"); + case 1: + return snprintf(buf, CY_MAX_PRBUF_SIZE, + "Interrupt line is HIGH.\n"); + default: + return snprintf(buf, CY_MAX_PRBUF_SIZE, + "Function irq_stat() returned %d.\n", retval); + } + } + + return snprintf(buf, CY_MAX_PRBUF_SIZE, + "Function irq_stat() undefined.\n"); +} + +/* + * Show IRQ enable/disable status via sysfs + */ +static ssize_t cyttsp5_drv_irq_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + ssize_t ret; + + mutex_lock(&cd->system_lock); + if (cd->irq_enabled) + ret = snprintf(buf, CY_MAX_PRBUF_SIZE, + "Driver interrupt is ENABLED\n"); + else + ret = snprintf(buf, CY_MAX_PRBUF_SIZE, + "Driver interrupt is DISABLED\n"); + mutex_unlock(&cd->system_lock); + + return ret; +} + +/* + * Enable/disable IRQ via sysfs + */ +static ssize_t cyttsp5_drv_irq_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + unsigned long value; + int retval = 0; + + retval = kstrtoul(buf, 10, &value); + if (retval < 0) { + dev_err(dev, "%s: Invalid value\n", __func__); + goto cyttsp5_drv_irq_store_error_exit; + } + + mutex_lock(&cd->system_lock); + switch (value) { + case 0: + if (cd->irq_enabled) { + cd->irq_enabled = false; + /* Disable IRQ */ + disable_irq_nosync(cd->irq); + dev_info(dev, "%s: Driver IRQ now disabled\n", + __func__); + } else + dev_info(dev, "%s: Driver IRQ already disabled\n", + __func__); + break; + + case 1: + if (cd->irq_enabled == false) { + cd->irq_enabled = true; + /* Enable IRQ */ + enable_irq(cd->irq); + dev_info(dev, "%s: Driver IRQ now enabled\n", + __func__); + } else + dev_info(dev, "%s: Driver IRQ already enabled\n", + __func__); + break; + + default: + dev_err(dev, "%s: Invalid value\n", __func__); + } + mutex_unlock(&(cd->system_lock)); + +cyttsp5_drv_irq_store_error_exit: + + return size; +} + +/* + * Debugging options via sysfs + */ +static ssize_t cyttsp5_drv_debug_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + unsigned long value; + int rc; + u8 return_data[8]; + static u8 wd_disabled; + + rc = kstrtoul(buf, 10, &value); + if (rc < 0) { + dev_err(dev, "%s: Invalid value\n", __func__); + goto cyttsp5_drv_debug_store_exit; + } + + /* Start watchdog timer command */ + if (value == CY_DBG_HID_START_WD) { + dev_info(dev, "%s: start watchdog (cd=%p)\n", __func__, cd); + wd_disabled = 0; + cyttsp5_start_wd_timer(cd); + goto cyttsp5_drv_debug_store_exit; + } + + /* Stop watchdog timer temporarily */ + cyttsp5_stop_wd_timer(cd); + + if (value == CY_DBG_HID_STOP_WD) { + dev_info(dev, "%s: stop watchdog (cd=%p)\n", __func__, cd); + wd_disabled = 1; + goto cyttsp5_drv_debug_store_exit; + } + + switch (value) { + case CY_DBG_SUSPEND: + dev_info(dev, "%s: SUSPEND (cd=%p)\n", __func__, cd); + rc = cyttsp5_core_sleep(cd); + if (rc) + dev_err(dev, "%s: Suspend failed rc=%d\n", + __func__, rc); + else + dev_info(dev, "%s: Suspend succeeded\n", __func__); + break; + + case CY_DBG_RESUME: + dev_info(dev, "%s: RESUME (cd=%p)\n", __func__, cd); + rc = cyttsp5_core_wake(cd); + if (rc) + dev_err(dev, "%s: Resume failed rc=%d\n", + __func__, rc); + else + dev_info(dev, "%s: Resume succeeded\n", __func__); + break; + case CY_DBG_SOFT_RESET: + dev_info(dev, "%s: SOFT RESET (cd=%p)\n", __func__, cd); + rc = cyttsp5_hw_soft_reset(cd); + break; + case CY_DBG_RESET: + dev_info(dev, "%s: HARD RESET (cd=%p)\n", __func__, cd); + rc = cyttsp5_hw_hard_reset(cd); + break; + case CY_DBG_HID_RESET: + dev_info(dev, "%s: hid_reset (cd=%p)\n", __func__, cd); + cyttsp5_hid_cmd_reset(cd); + break; + case CY_DBG_HID_SET_POWER_ON: + dev_info(dev, "%s: hid_set_power_on (cd=%p)\n", __func__, cd); + cyttsp5_hid_cmd_set_power(cd, HID_POWER_ON); + wd_disabled = 0; + break; + case CY_DBG_HID_SET_POWER_SLEEP: + dev_info(dev, "%s: hid_set_power_off (cd=%p)\n", __func__, cd); + wd_disabled = 1; + cyttsp5_hid_cmd_set_power(cd, HID_POWER_SLEEP); + break; + case CY_DBG_HID_NULL: + dev_info(dev, "%s: hid_null (cd=%p)\n", __func__, cd); + cyttsp5_hid_output_null(cd); + break; + case CY_DBG_HID_ENTER_BL: + dev_info(dev, "%s: start_bootloader (cd=%p)\n", __func__, cd); + cyttsp5_hid_output_start_bootloader(cd); + break; + case CY_DBG_HID_SYSINFO: + dev_info(dev, "%s: get_sysinfo (cd=%p)\n", __func__, cd); + cyttsp5_hid_output_get_sysinfo(cd); + break; + case CY_DBG_HID_SUSPEND_SCAN: + dev_info(dev, "%s: suspend_scanning (cd=%p)\n", __func__, cd); + cyttsp5_hid_output_suspend_scanning(cd); + break; + case CY_DBG_HID_RESUME_SCAN: + dev_info(dev, "%s: resume_scanning (cd=%p)\n", __func__, cd); + cyttsp5_hid_output_resume_scanning(cd); + break; + case HID_OUTPUT_BL_VERIFY_APP_INTEGRITY: + dev_info(dev, "%s: verify app integ (cd=%p)\n", __func__, cd); + cyttsp5_hid_output_bl_verify_app_integrity(cd, &return_data[0]); + break; + case HID_OUTPUT_BL_GET_INFO: + dev_info(dev, "%s: bl get info (cd=%p)\n", __func__, cd); + cyttsp5_hid_output_bl_get_information(cd, return_data); + break; + case HID_OUTPUT_BL_PROGRAM_AND_VERIFY: + dev_info(dev, "%s: program and verify (cd=%p)\n", __func__, cd); + cyttsp5_hid_output_bl_program_and_verify(cd, 0, NULL); + break; + case HID_OUTPUT_BL_LAUNCH_APP: + dev_info(dev, "%s: launch app (cd=%p)\n", __func__, cd); + cyttsp5_hid_output_bl_launch_app(cd); + break; + case HID_OUTPUT_BL_INITIATE_BL: + dev_info(dev, "%s: initiate bl (cd=%p)\n", __func__, cd); + cyttsp5_hid_output_bl_initiate_bl(cd, 0, NULL, 0, NULL); + break; +#ifdef TTHE_TUNER_SUPPORT + case CY_TTHE_TUNER_EXIT: + cd->tthe_exit = 1; + wake_up(&cd->wait_q); + kfree(cd->tthe_buf); + cd->tthe_buf = NULL; + cd->tthe_exit = 0; + break; + case CY_TTHE_BUF_CLEAN: + if (cd->tthe_buf) + memset(cd->tthe_buf, 0, CY_MAX_PRBUF_SIZE); + else + dev_info(dev, "%s : tthe_buf not existed\n", __func__); + break; +#endif + default: + dev_err(dev, "%s: Invalid value\n", __func__); + } + + /* Enable watchdog timer */ + if (!wd_disabled) + cyttsp5_start_wd_timer(cd); + +cyttsp5_drv_debug_store_exit: + return size; +} + +/* + * Show system status on deep sleep status via sysfs + */ +static ssize_t cyttsp5_sleep_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + ssize_t ret; + + mutex_lock(&cd->system_lock); + if (cd->sleep_state == SS_SLEEP_ON) + ret = snprintf(buf, CY_MAX_PRBUF_SIZE, "off\n"); + else + ret = snprintf(buf, CY_MAX_PRBUF_SIZE, "on\n"); + mutex_unlock(&cd->system_lock); + + return ret; +} + +static ssize_t cyttsp5_easy_wakeup_gesture_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + ssize_t ret; + + mutex_lock(&cd->system_lock); + ret = snprintf(buf, CY_MAX_PRBUF_SIZE, "0x%02X\n", + cd->easy_wakeup_gesture); + mutex_unlock(&cd->system_lock); + return ret; +} + +static ssize_t cyttsp5_easy_wakeup_gesture_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + unsigned long value; + int ret; + + if (!cd->features.easywake) + return -EINVAL; + + ret = kstrtoul(buf, 10, &value); + if (ret < 0) + return ret; + + if (value > 0xFF) + return -EINVAL; + + pm_runtime_get_sync(dev); + + mutex_lock(&cd->system_lock); + if (cd->sysinfo.ready && IS_PIP_VER_GE(&cd->sysinfo, 1, 2)) + cd->easy_wakeup_gesture = (u8)value; + else + ret = -ENODEV; + mutex_unlock(&cd->system_lock); + + pm_runtime_put(dev); + + if (ret) + return ret; + + return size; +} + +#ifdef EASYWAKE_TSG6 +/* + * Show easywake gesture id via sysfs + */ +static ssize_t cyttsp5_easy_wakeup_gesture_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + ssize_t ret; + + mutex_lock(&cd->system_lock); + ret = snprintf(buf, CY_MAX_PRBUF_SIZE, "0x%02X\n", + cd->gesture_id); + mutex_unlock(&cd->system_lock); + return ret; +} + +/* + * Show easywake gesture data via sysfs + * The format: + * x1(LSB), x1(MSB),y1(LSB), y1(MSB),x2(LSB), x2(MSB),y2(LSB), y2(MSB),... + */ +static ssize_t cyttsp5_easy_wakeup_gesture_data_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + ssize_t ret = 0; + int i; + + mutex_lock(&cd->system_lock); + + for (i = 0; i < cd->gesture_data_length; i++) + ret += snprintf(buf + ret, CY_MAX_PRBUF_SIZE - ret, + "0x%02X\n", cd->gesture_data[i]); + + ret += snprintf(buf + ret, CY_MAX_PRBUF_SIZE - ret, + "(%d bytes)\n", cd->gesture_data_length); + + mutex_unlock(&cd->system_lock); + return ret; +} +#endif + +/* Show Panel ID via sysfs */ +static ssize_t cyttsp5_panel_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + ssize_t ret; + + ret = snprintf(buf, CY_MAX_PRBUF_SIZE, "0x%02X\n", + cd->panel_id); + return ret; +} + +/* Show platform data via sysfs */ +static ssize_t cyttsp5_platform_data_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cyttsp5_platform_data *pdata = dev_get_platdata(dev); + ssize_t ret; + + ret = sprintf(buf, + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n" + "%s: %d\n", + "Interrupt GPIO", pdata->core_pdata->irq_gpio, + "Reset GPIO", pdata->core_pdata->rst_gpio, + "Level trigger delay (us)", pdata->core_pdata->level_irq_udelay, + "HID descriptor register", pdata->core_pdata->hid_desc_register, + "Vendor ID", pdata->core_pdata->vendor_id, + "Product ID", pdata->core_pdata->product_id, + "Easy wakeup gesture", pdata->core_pdata->easy_wakeup_gesture, + "Vkeys x", pdata->mt_pdata->vkeys_x, + "Vkeys y", pdata->mt_pdata->vkeys_y, + "Core data flags", pdata->core_pdata->flags, + "MT data flags", pdata->mt_pdata->flags, + "Loader data flags", pdata->loader_pdata->flags); + return ret; +} + +static struct device_attribute attributes[] = { + __ATTR(ic_ver, S_IRUGO, cyttsp5_ic_ver_show, NULL), + __ATTR(drv_ver, S_IRUGO, cyttsp5_drv_ver_show, NULL), + __ATTR(hw_reset, S_IWUSR, NULL, cyttsp5_hw_reset_store), + __ATTR(hw_irq_stat, S_IRUSR, cyttsp5_hw_irq_stat_show, NULL), + __ATTR(drv_irq, S_IRUSR | S_IWUSR, cyttsp5_drv_irq_show, + cyttsp5_drv_irq_store), + __ATTR(drv_debug, S_IWUSR, NULL, cyttsp5_drv_debug_store), + __ATTR(sleep_status, S_IRUSR, cyttsp5_sleep_status_show, NULL), + __ATTR(easy_wakeup_gesture, S_IRUSR | S_IWUSR, + cyttsp5_easy_wakeup_gesture_show, + cyttsp5_easy_wakeup_gesture_store), +#ifdef EASYWAKE_TSG6 + __ATTR(easy_wakeup_gesture_id, S_IRUSR, + cyttsp5_easy_wakeup_gesture_id_show, NULL), + __ATTR(easy_wakeup_gesture_data, S_IRUSR, + cyttsp5_easy_wakeup_gesture_data_show, NULL), +#endif + __ATTR(panel_id, S_IRUGO, cyttsp5_panel_id_show, NULL), + __ATTR(platform_data, S_IRUGO, cyttsp5_platform_data_show, NULL), +}; + +static int add_sysfs_interfaces(struct device *dev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(attributes); i++) + if (device_create_file(dev, attributes + i)) + goto undo; + return 0; +undo: + for (i--; i >= 0; i--) + device_remove_file(dev, attributes + i); + dev_err(dev, "%s: failed to create sysfs interface\n", __func__); + return -ENODEV; +} + +static void remove_sysfs_interfaces(struct device *dev) +{ + u32 i; + + for (i = 0; i < ARRAY_SIZE(attributes); i++) + device_remove_file(dev, attributes + i); +} + +#ifdef TTHE_TUNER_SUPPORT +static int tthe_debugfs_open(struct inode *inode, struct file *filp) +{ + struct cyttsp5_core_data *cd = inode->i_private; + + filp->private_data = inode->i_private; + + if (cd->tthe_buf) + return -EBUSY; + + cd->tthe_buf = kzalloc(CY_MAX_PRBUF_SIZE, GFP_KERNEL); + if (!cd->tthe_buf) + return -ENOMEM; + + return 0; +} + +static int tthe_debugfs_close(struct inode *inode, struct file *filp) +{ + struct cyttsp5_core_data *cd = filp->private_data; + + filp->private_data = NULL; + + kfree(cd->tthe_buf); + cd->tthe_buf = NULL; + + return 0; +} + +static ssize_t tthe_debugfs_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct cyttsp5_core_data *cd = filp->private_data; + int size; + int ret; + + wait_event_interruptible(cd->wait_q, + cd->tthe_buf_len != 0 || cd->tthe_exit); + mutex_lock(&cd->tthe_lock); + if (cd->tthe_exit) { + mutex_unlock(&cd->tthe_lock); + return 0; + } + if (count > cd->tthe_buf_len) + size = cd->tthe_buf_len; + else + size = count; + if (!size) { + mutex_unlock(&cd->tthe_lock); + return 0; + } + + ret = copy_to_user(buf, cd->tthe_buf, cd->tthe_buf_len); + if (ret == size) + return -EFAULT; + size -= ret; + cd->tthe_buf_len -= size; + mutex_unlock(&cd->tthe_lock); + *ppos += size; + return size; +} + +static const struct file_operations tthe_debugfs_fops = { + .open = tthe_debugfs_open, + .release = tthe_debugfs_close, + .read = tthe_debugfs_read, +}; +#endif + +static struct cyttsp5_core_nonhid_cmd _cyttsp5_core_nonhid_cmd = { + .start_bl = _cyttsp5_request_hid_output_start_bl, + .suspend_scanning = _cyttsp5_request_hid_output_suspend_scanning, + .resume_scanning = _cyttsp5_request_hid_output_resume_scanning, + .get_param = _cyttsp5_request_hid_output_get_param, + .set_param = _cyttsp5_request_hid_output_set_param, + .verify_config_block_crc = + _cyttsp5_request_hid_output_verify_config_block_crc, + .get_config_row_size = _cyttsp5_request_hid_output_get_config_row_size, + .get_data_structure = _cyttsp5_request_hid_output_get_data_structure, + .run_selftest = _cyttsp5_request_hid_output_run_selftest, + .get_selftest_result = _cyttsp5_request_hid_output_get_selftest_result, + .calibrate_idacs = _cyttsp5_request_hid_output_calibrate_idacs, + .initialize_baselines = + _cyttsp5_request_hid_output_initialize_baselines, + .exec_panel_scan = _cyttsp5_request_hid_output_exec_panel_scan, + .retrieve_panel_scan = _cyttsp5_request_hid_output_retrieve_panel_scan, + .write_conf_block = _cyttsp5_request_hid_output_write_conf_block, + .user_cmd = _cyttsp5_request_hid_output_user_cmd, + .get_bl_info = _cyttsp5_request_hid_output_bl_get_information, + .initiate_bl = _cyttsp5_request_hid_output_bl_initiate_bl, + .launch_app = _cyttsp5_request_hid_output_launch_app, + .prog_and_verify = _cyttsp5_request_hid_output_bl_program_and_verify, + .verify_app_integrity = + _cyttsp5_request_hid_output_bl_verify_app_integrity, + .get_panel_id = _cyttsp5_request_hid_output_bl_get_panel_id, +}; + +static struct cyttsp5_core_commands _cyttsp5_core_commands = { + .subscribe_attention = _cyttsp5_subscribe_attention, + .unsubscribe_attention = _cyttsp5_unsubscribe_attention, + .request_exclusive = _cyttsp5_request_exclusive, + .release_exclusive = _cyttsp5_release_exclusive, + .request_reset = _cyttsp5_request_reset, + .request_restart = _cyttsp5_request_restart, + .request_sysinfo = _cyttsp5_request_sysinfo, + .request_loader_pdata = _cyttsp5_request_loader_pdata, + .request_stop_wd = _cyttsp5_request_stop_wd, + .request_start_wd = _cyttsp5_request_start_wd, + .request_get_hid_desc = _cyttsp5_request_get_hid_desc, + .request_get_mode = _cyttsp5_request_get_mode, +#ifdef TTHE_TUNER_SUPPORT + .request_tthe_print = _cyttsp5_request_tthe_print, +#endif + .nonhid_cmd = &_cyttsp5_core_nonhid_cmd, +}; + +struct cyttsp5_core_commands *cyttsp5_get_commands(void) +{ + return &_cyttsp5_core_commands; +} +EXPORT_SYMBOL_GPL(cyttsp5_get_commands); + +static DEFINE_MUTEX(core_list_lock); +static LIST_HEAD(core_list); +static DEFINE_MUTEX(module_list_lock); +static LIST_HEAD(module_list); +static int core_number; + +static int cyttsp5_probe_module(struct cyttsp5_core_data *cd, + struct cyttsp5_module *module) +{ + struct module_node *module_node; + int rc = 0; + + module_node = kzalloc(sizeof(*module_node), GFP_KERNEL); + if (!module_node) + return -ENOMEM; + + module_node->module = module; + + mutex_lock(&cd->module_list_lock); + list_add(&module_node->node, &cd->module_list); + mutex_unlock(&cd->module_list_lock); + + rc = module->probe(cd->dev, &module_node->data); + if (rc) { + /* + * Remove from the list when probe fails + * in order not to call release + */ + mutex_lock(&cd->module_list_lock); + list_del(&module_node->node); + mutex_unlock(&cd->module_list_lock); + kfree(module_node); + goto exit; + } + +exit: + return rc; +} + +static void cyttsp5_release_module(struct cyttsp5_core_data *cd, + struct cyttsp5_module *module) +{ + struct module_node *m, *m_n; + + mutex_lock(&cd->module_list_lock); + list_for_each_entry_safe(m, m_n, &cd->module_list, node) + if (m->module == module) { + module->release(cd->dev, m->data); + list_del(&m->node); + kfree(m); + break; + } + mutex_unlock(&cd->module_list_lock); +} + +static void cyttsp5_probe_modules(struct cyttsp5_core_data *cd) +{ + struct cyttsp5_module *m; + int rc = 0; + + mutex_lock(&module_list_lock); + list_for_each_entry(m, &module_list, node) { + rc = cyttsp5_probe_module(cd, m); + if (rc) + dev_err(cd->dev, "%s: Probe fails for module %s\n", + __func__, m->name); + } + mutex_unlock(&module_list_lock); +} + +static void cyttsp5_release_modules(struct cyttsp5_core_data *cd) +{ + struct cyttsp5_module *m; + + mutex_lock(&module_list_lock); + list_for_each_entry(m, &module_list, node) + cyttsp5_release_module(cd, m); + mutex_unlock(&module_list_lock); +} + +struct cyttsp5_core_data *cyttsp5_get_core_data(char *id) +{ + struct cyttsp5_core_data *d; + + list_for_each_entry(d, &core_list, node) + if (!strncmp(d->core_id, id, 20)) + return d; + return NULL; +} +EXPORT_SYMBOL_GPL(cyttsp5_get_core_data); + +static void cyttsp5_add_core(struct device *dev) +{ + struct cyttsp5_core_data *d; + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + mutex_lock(&core_list_lock); + list_for_each_entry(d, &core_list, node) + if (d->dev == dev) + goto unlock; + + list_add(&cd->node, &core_list); +unlock: + mutex_unlock(&core_list_lock); +} + +static void cyttsp5_del_core(struct device *dev) +{ + struct cyttsp5_core_data *d, *d_n; + + mutex_lock(&core_list_lock); + list_for_each_entry_safe(d, d_n, &core_list, node) + if (d->dev == dev) { + list_del(&d->node); + goto unlock; + } +unlock: + mutex_unlock(&core_list_lock); +} + +int cyttsp5_register_module(struct cyttsp5_module *module) +{ + struct cyttsp5_module *m; + struct cyttsp5_core_data *cd; + + int rc = 0; + + if (!module || !module->probe || !module->release) + return -EINVAL; + + mutex_lock(&module_list_lock); + list_for_each_entry(m, &module_list, node) + if (m == module) { + rc = -EEXIST; + goto unlock; + } + + list_add(&module->node, &module_list); + + /* Probe the module for each core */ + mutex_lock(&core_list_lock); + list_for_each_entry(cd, &core_list, node) + cyttsp5_probe_module(cd, module); + mutex_unlock(&core_list_lock); + +unlock: + mutex_unlock(&module_list_lock); + return rc; +} +EXPORT_SYMBOL_GPL(cyttsp5_register_module); + +void cyttsp5_unregister_module(struct cyttsp5_module *module) +{ + struct cyttsp5_module *m, *m_n; + struct cyttsp5_core_data *cd; + + if (!module) + return; + + mutex_lock(&module_list_lock); + + /* Release the module for each core */ + mutex_lock(&core_list_lock); + list_for_each_entry(cd, &core_list, node) + cyttsp5_release_module(cd, module); + mutex_unlock(&core_list_lock); + + list_for_each_entry_safe(m, m_n, &module_list, node) + if (m == module) { + list_del(&m->node); + break; + } + + mutex_unlock(&module_list_lock); +} +EXPORT_SYMBOL_GPL(cyttsp5_unregister_module); + +void *cyttsp5_get_module_data(struct device *dev, struct cyttsp5_module *module) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + struct module_node *m; + void *data = NULL; + + mutex_lock(&cd->module_list_lock); + list_for_each_entry(m, &cd->module_list, node) + if (m->module == module) { + data = m->data; + break; + } + mutex_unlock(&cd->module_list_lock); + + return data; +} +EXPORT_SYMBOL(cyttsp5_get_module_data); + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void cyttsp5_early_suspend(struct early_suspend *h) +{ + struct cyttsp5_core_data *cd = + container_of(h, struct cyttsp5_core_data, es); + + call_atten_cb(cd, CY_ATTEN_SUSPEND, 0); +} + +static void cyttsp5_late_resume(struct early_suspend *h) +{ + struct cyttsp5_core_data *cd = + container_of(h, struct cyttsp5_core_data, es); + + call_atten_cb(cd, CY_ATTEN_RESUME, 0); +} + +static void cyttsp5_setup_early_suspend(struct cyttsp5_core_data *cd) +{ + cd->es.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; + cd->es.suspend = cyttsp5_early_suspend; + cd->es.resume = cyttsp5_late_resume; + + register_early_suspend(&cd->es); +} +#elif defined(CONFIG_FB) +static int fb_notifier_callback(struct notifier_block *self, + unsigned long event, void *data) +{ + struct cyttsp5_core_data *cd = + container_of(self, struct cyttsp5_core_data, fb_notifier); + struct fb_event *evdata = data; + int *blank; + + if (event != FB_EVENT_BLANK || !evdata) + goto exit; + + blank = evdata->data; + if (*blank == FB_BLANK_UNBLANK) { + dev_info(cd->dev, "%s: UNBLANK!\n", __func__); + if (cd->fb_state != FB_ON) { + call_atten_cb(cd, CY_ATTEN_RESUME, 0); + cd->fb_state = FB_ON; + } + } else if (*blank == FB_BLANK_POWERDOWN) { + dev_info(cd->dev, "%s: POWERDOWN!\n", __func__); + if (cd->fb_state != FB_OFF) { + call_atten_cb(cd, CY_ATTEN_SUSPEND, 0); + cd->fb_state = FB_OFF; + } + } + +exit: + return 0; +} + +static void cyttsp5_setup_fb_notifier(struct cyttsp5_core_data *cd) +{ + int rc; + + cd->fb_state = FB_ON; + + cd->fb_notifier.notifier_call = fb_notifier_callback; + + rc = fb_register_client(&cd->fb_notifier); + if (rc) + dev_err(cd->dev, "Unable to register fb_notifier: %d\n", rc); +} +#endif + +static int cyttsp5_setup_irq_gpio(struct cyttsp5_core_data *cd) +{ + struct device *dev = cd->dev; + unsigned long irq_flags; + int rc; + + /* Initialize IRQ */ + cd->irq = gpio_to_irq(cd->cpdata->irq_gpio); + if (cd->irq < 0) + return -EINVAL; + + cd->irq_enabled = true; + + dev_dbg(dev, "%s: initialize threaded irq=%d\n", __func__, cd->irq); + if (cd->cpdata->level_irq_udelay > 0) + /* use level triggered interrupts */ + irq_flags = IRQF_TRIGGER_LOW | IRQF_ONESHOT; + else + /* use edge triggered interrupts */ + irq_flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT; + + rc = request_threaded_irq(cd->irq, NULL, cyttsp5_irq, irq_flags, + dev_name(dev), cd); + if (rc < 0) + dev_err(dev, "%s: Error, could not request irq\n", __func__); + + return rc; +} + +int cyttsp5_probe(const struct cyttsp5_bus_ops *ops, struct device *dev, + u16 irq, size_t xfer_buf_size) +{ + struct cyttsp5_core_data *cd; + struct cyttsp5_platform_data *pdata = dev_get_platdata(dev); + enum cyttsp5_atten_type type; + int rc = 0; + + if (!pdata || !pdata->core_pdata || !pdata->mt_pdata) { + dev_err(dev, "%s: Missing platform data\n", __func__); + rc = -ENODEV; + goto error_no_pdata; + } + + if (pdata->core_pdata->flags & CY_CORE_FLAG_POWEROFF_ON_SLEEP) { + if (!pdata->core_pdata->power) { + dev_err(dev, "%s: Missing platform data function\n", + __func__); + rc = -ENODEV; + goto error_no_pdata; + } + } + + /* get context and debug print buffers */ + cd = kzalloc(sizeof(*cd), GFP_KERNEL); + if (!cd) { + rc = -ENOMEM; + goto error_alloc_data; + } + + /* Initialize device info */ + cd->dev = dev; + cd->pdata = pdata; + cd->cpdata = pdata->core_pdata; + cd->bus_ops = ops; + scnprintf(cd->core_id, 20, "%s%d", CYTTSP5_CORE_NAME, core_number++); + + /* Initialize mutexes and spinlocks */ + mutex_init(&cd->module_list_lock); + mutex_init(&cd->system_lock); + mutex_init(&cd->adap_lock); + mutex_init(&cd->hid_report_lock); + spin_lock_init(&cd->spinlock); + + /* Initialize module list */ + INIT_LIST_HEAD(&cd->module_list); + + /* Initialize attention lists */ + for (type = 0; type < CY_ATTEN_NUM_ATTEN; type++) + INIT_LIST_HEAD(&cd->atten_list[type]); + + /* Initialize parameter list */ + INIT_LIST_HEAD(&cd->param_list); + + /* Initialize wait queue */ + init_waitqueue_head(&cd->wait_q); + + /* Initialize works */ + INIT_WORK(&cd->startup_work, cyttsp5_startup_work_function); + INIT_WORK(&cd->watchdog_work, cyttsp5_watchdog_work); + + /* Initialize HID specific data */ + cd->hid_core.hid_vendor_id = (cd->cpdata->vendor_id) ? + cd->cpdata->vendor_id : CY_HID_VENDOR_ID; + cd->hid_core.hid_product_id = (cd->cpdata->product_id) ? + cd->cpdata->product_id : CY_HID_APP_PRODUCT_ID; + cd->hid_core.hid_desc_register = + cpu_to_le16(cd->cpdata->hid_desc_register); + + /* Set platform easywake value */ + cd->easy_wakeup_gesture = cd->cpdata->easy_wakeup_gesture; + + /* Set Panel ID to Not Enabled */ + cd->panel_id = PANEL_ID_NOT_ENABLED; + + dev_set_drvdata(dev, cd); + cyttsp5_add_core(dev); + + cd->vdd = regulator_get(cd->dev, "vdd"); + if (IS_ERR(cd->vdd)) { + rc = PTR_ERR(cd->vdd); + goto error_vdd_get; + } + + rc = regulator_enable(cd->vdd); + if (rc) + goto error_vdd_enable; + + /* Call platform init function */ + if (cd->cpdata->init) { + dev_dbg(cd->dev, "%s: Init HW\n", __func__); + rc = cd->cpdata->init(cd->cpdata, 1, cd->dev); + } else { + dev_info(cd->dev, "%s: No HW INIT function\n", __func__); + rc = 0; + } + if (rc < 0) + dev_err(cd->dev, "%s: HW Init fail r=%d\n", __func__, rc); + + /* Call platform detect function */ + if (cd->cpdata->detect) { + dev_info(cd->dev, "%s: Detect HW\n", __func__); + rc = cd->cpdata->detect(cd->cpdata, cd->dev, + cyttsp5_platform_detect_read); + if (rc) { + dev_info(cd->dev, "%s: No HW detected\n", __func__); + rc = -ENODEV; + goto error_detect; + } + } + + /* Setup watchdog timer */ + timer_setup(&cd->watchdog_timer, cyttsp5_watchdog_timer, 0); + + rc = cyttsp5_setup_irq_gpio(cd); + if (rc < 0) { + dev_err(dev, "%s: Error, could not setup IRQ\n", __func__); + goto error_setup_irq; + } + + dev_dbg(dev, "%s: add sysfs interfaces\n", __func__); + rc = add_sysfs_interfaces(dev); + if (rc < 0) { + dev_err(dev, "%s: Error, fail sysfs init\n", __func__); + goto error_attr_create; + } + +#ifdef TTHE_TUNER_SUPPORT + mutex_init(&cd->tthe_lock); + cd->tthe_debugfs = debugfs_create_file(CYTTSP5_TTHE_TUNER_FILE_NAME, + 0644, NULL, cd, &tthe_debugfs_fops); +#endif + rc = device_init_wakeup(dev, 1); + if (rc < 0) + dev_err(dev, "%s: Error, device_init_wakeup rc:%d\n", + __func__, rc); + + pm_runtime_get_noresume(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + /* + * call startup directly to ensure that the device + * is tested before leaving the probe + */ + dev_dbg(dev, "%s: call startup\n", __func__); + rc = cyttsp5_startup(cd, false); + + pm_runtime_put_sync(dev); + + /* Do not fail probe if startup fails but the device is detected */ + if (rc == -ENODEV) { + dev_err(cd->dev, "%s: Fail initial startup r=%d\n", + __func__, rc); + goto error_startup; + } + + rc = cyttsp5_mt_probe(dev); + if (rc < 0) { + dev_err(dev, "%s: Error, fail mt probe\n", __func__); + goto error_startup; + } + + rc = cyttsp5_btn_probe(dev); + if (rc < 0) { + dev_err(dev, "%s: Error, fail btn probe\n", __func__); + goto error_startup_mt; + } + + rc = cyttsp5_proximity_probe(dev); + if (rc < 0) { + dev_err(dev, "%s: Error, fail proximity probe\n", __func__); + goto error_startup_btn; + } + + /* Probe registered modules */ + cyttsp5_probe_modules(cd); + +#ifdef CONFIG_HAS_EARLYSUSPEND + cyttsp5_setup_early_suspend(cd); +#elif defined(CONFIG_FB) + if (!cd->cpdata->fb_blanking_disabled) { + cyttsp5_setup_fb_notifier(cd); + } +#endif + +#if NEED_SUSPEND_NOTIFIER + cd->pm_notifier.notifier_call = cyttsp5_pm_notifier; + register_pm_notifier(&cd->pm_notifier); +#endif + + return 0; + +error_startup_btn: + cyttsp5_btn_release(dev); +error_startup_mt: + cyttsp5_mt_release(dev); +error_startup: + pm_runtime_disable(dev); +#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 16, 0)) + device_wakeup_disable(dev); +#endif + device_init_wakeup(dev, 0); + cancel_work_sync(&cd->startup_work); + cyttsp5_stop_wd_timer(cd); + cyttsp5_free_si_ptrs(cd); + remove_sysfs_interfaces(dev); +error_attr_create: + free_irq(cd->irq, cd); + del_timer(&cd->watchdog_timer); +error_setup_irq: +error_detect: + if (cd->cpdata->init) + cd->cpdata->init(cd->cpdata, 0, dev); + regulator_disable(cd->vdd); +error_vdd_enable: + regulator_put(cd->vdd); +error_vdd_get: + cyttsp5_del_core(dev); + dev_set_drvdata(dev, NULL); + kfree(cd); +error_alloc_data: +error_no_pdata: + dev_err(dev, "%s failed.\n", __func__); + return rc; +} +EXPORT_SYMBOL_GPL(cyttsp5_probe); + +int cyttsp5_release(struct cyttsp5_core_data *cd) +{ + struct device *dev = cd->dev; + + /* Release successfully probed modules */ + cyttsp5_release_modules(cd); + + cyttsp5_proximity_release(dev); + cyttsp5_btn_release(dev); + cyttsp5_mt_release(dev); + +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&cd->es); +#elif defined(CONFIG_FB) + fb_unregister_client(&cd->fb_notifier); +#endif + +#if NEED_SUSPEND_NOTIFIER + unregister_pm_notifier(&cd->pm_notifier); +#endif + + /* + * Suspend the device before freeing the startup_work and stopping + * the watchdog since sleep function restarts watchdog on failure + */ + pm_runtime_suspend(dev); + pm_runtime_disable(dev); + + cancel_work_sync(&cd->startup_work); + + cyttsp5_stop_wd_timer(cd); + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 16, 0)) + device_wakeup_disable(dev); +#endif + device_init_wakeup(dev, 0); + +#ifdef TTHE_TUNER_SUPPORT + mutex_lock(&cd->tthe_lock); + cd->tthe_exit = 1; + wake_up(&cd->wait_q); + mutex_unlock(&cd->tthe_lock); + debugfs_remove(cd->tthe_debugfs); +#endif + remove_sysfs_interfaces(dev); + free_irq(cd->irq, cd); + if (cd->cpdata->init) + cd->cpdata->init(cd->cpdata, 0, dev); + regulator_disable(cd->vdd); + regulator_put(cd->vdd); + dev_set_drvdata(dev, NULL); + cyttsp5_del_core(dev); + cyttsp5_free_si_ptrs(cd); + cyttsp5_free_hid_reports(cd); + kfree(cd); + return 0; +} +EXPORT_SYMBOL_GPL(cyttsp5_release); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Parade TrueTouch(R) Standard Product Core Driver"); +MODULE_AUTHOR("Parade Technologies "); diff --git a/drivers/input/touchscreen/cyttsp5_debug.c b/drivers/input/touchscreen/cyttsp5_debug.c new file mode 100644 index 000000000000..f4ebba784936 --- /dev/null +++ b/drivers/input/touchscreen/cyttsp5_debug.c @@ -0,0 +1,393 @@ +/* + * cyttsp5_debug.c + * Parade TrueTouch(TM) Standard Product V5 Debug Module. + * For use with Parade touchscreen controllers. + * Supported parts include: + * CYTMA5XX + * CYTMA448 + * CYTMA445A + * CYTT21XXX + * CYTT31XXX + * + * Copyright (C) 2015 Parade Technologies + * Copyright (C) 2012-2015 Cypress Semiconductor + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Contact Parade Technologies at www.paradetech.com + * + */ + +#include "cyttsp5_regs.h" + +#define CYTTSP5_DEBUG_NAME "cyttsp5_debug" + +struct cyttsp5_debug_data { + struct device *dev; + struct cyttsp5_sysinfo *si; + uint32_t interrupt_count; + uint32_t formated_output; + struct mutex sysfs_lock; + u8 pr_buf[CY_MAX_PRBUF_SIZE]; +}; + +static struct cyttsp5_core_commands *cmd; + +static struct cyttsp5_module debug_module; + +static inline struct cyttsp5_debug_data *cyttsp5_get_debug_data( + struct device *dev) +{ + return cyttsp5_get_module_data(dev, &debug_module); +} + +/* + * This function provide output of combined xy_mode and xy_data. + * Required by TTHE. + */ +static void cyttsp5_pr_buf_op_mode(struct cyttsp5_debug_data *dd, u8 *pr_buf, + struct cyttsp5_sysinfo *si, u8 cur_touch) +{ + const char fmt[] = "%02X "; + int max = (CY_MAX_PRBUF_SIZE - 1) - sizeof(CY_PR_TRUNCATED); + u8 report_id = si->xy_mode[2]; + int header_size = 0; + int report_size = 0; + int total_size = 0; + int i, k; + + if (report_id == si->desc.tch_report_id) { + header_size = si->desc.tch_header_size; + report_size = cur_touch * si->desc.tch_record_size; + } else if (report_id == si->desc.btn_report_id) { + header_size = BTN_INPUT_HEADER_SIZE; + report_size = BTN_REPORT_SIZE; + } + total_size = header_size + report_size; + + pr_buf[0] = 0; + for (i = k = 0; i < header_size && i < max; i++, k += 3) + scnprintf(pr_buf + k, CY_MAX_PRBUF_SIZE, fmt, si->xy_mode[i]); + + for (i = 0; i < report_size && i < max; i++, k += 3) + scnprintf(pr_buf + k, CY_MAX_PRBUF_SIZE, fmt, si->xy_data[i]); + + pr_info("%s=%s%s\n", "cyttsp5_OpModeData", pr_buf, + total_size <= max ? "" : CY_PR_TRUNCATED); +} + +static void cyttsp5_debug_print(struct device *dev, u8 *pr_buf, u8 *sptr, + int size, const char *data_name) +{ + int i, j; + int elem_size = sizeof("XX ") - 1; + int max = (CY_MAX_PRBUF_SIZE - 1) / elem_size; + int limit = size < max ? size : max; + + if (limit < 0) + limit = 0; + + pr_buf[0] = 0; + for (i = j = 0; i < limit; i++, j += elem_size) + scnprintf(pr_buf + j, CY_MAX_PRBUF_SIZE - j, "%02X ", sptr[i]); + + if (size) + pr_info("%s[0..%d]=%s%s\n", data_name, size - 1, pr_buf, + size <= max ? "" : CY_PR_TRUNCATED); + else + pr_info("%s[]\n", data_name); +} + +static void cyttsp5_debug_formated(struct device *dev, u8 *pr_buf, + struct cyttsp5_sysinfo *si, u8 num_cur_tch) +{ + u8 report_id = si->xy_mode[2]; + int header_size = 0; + int report_size = 0; + u8 data_name[] = "touch[99]"; + int max_print_length = 20; + int i; + + if (report_id == si->desc.tch_report_id) { + header_size = si->desc.tch_header_size; + report_size = num_cur_tch * si->desc.tch_record_size; + } else if (report_id == si->desc.btn_report_id) { + header_size = BTN_INPUT_HEADER_SIZE; + report_size = BTN_REPORT_SIZE; + } + + /* xy_mode */ + cyttsp5_debug_print(dev, pr_buf, si->xy_mode, header_size, "xy_mode"); + + /* xy_data */ + if (report_size > max_print_length) { + pr_info("xy_data[0..%d]:\n", report_size); + for (i = 0; i < report_size - max_print_length; + i += max_print_length) { + cyttsp5_debug_print(dev, pr_buf, si->xy_data + i, + max_print_length, " "); + } + if (report_size - i) + cyttsp5_debug_print(dev, pr_buf, si->xy_data + i, + report_size - i, " "); + } else { + cyttsp5_debug_print(dev, pr_buf, si->xy_data, report_size, + "xy_data"); + } + + /* touches */ + if (report_id == si->desc.tch_report_id) { + for (i = 0; i < num_cur_tch; i++) { + scnprintf(data_name, sizeof(data_name) - 1, + "touch[%u]", i); + cyttsp5_debug_print(dev, pr_buf, + si->xy_data + (i * si->desc.tch_record_size), + si->desc.tch_record_size, data_name); + } + } + + /* buttons */ + if (report_id == si->desc.btn_report_id) + cyttsp5_debug_print(dev, pr_buf, si->xy_data, report_size, + "button"); +} + +/* read xy_data for all touches for debug */ +static int cyttsp5_xy_worker(struct cyttsp5_debug_data *dd) +{ + struct device *dev = dd->dev; + struct cyttsp5_sysinfo *si = dd->si; + u8 report_reg = si->xy_mode[TOUCH_COUNT_BYTE_OFFSET]; + u8 num_cur_tch = GET_NUM_TOUCHES(report_reg); + uint32_t formated_output; + + mutex_lock(&dd->sysfs_lock); + dd->interrupt_count++; + formated_output = dd->formated_output; + mutex_unlock(&dd->sysfs_lock); + + /* Interrupt */ + pr_info("Interrupt(%u)\n", dd->interrupt_count); + + if (formated_output) + cyttsp5_debug_formated(dev, dd->pr_buf, si, num_cur_tch); + else + /* print data for TTHE */ + cyttsp5_pr_buf_op_mode(dd, dd->pr_buf, si, num_cur_tch); + + pr_info("\n"); + + return 0; +} + +static int cyttsp5_debug_attention(struct device *dev) +{ + struct cyttsp5_debug_data *dd = cyttsp5_get_debug_data(dev); + struct cyttsp5_sysinfo *si = dd->si; + u8 report_id = si->xy_mode[2]; + int rc = 0; + + if (report_id != si->desc.tch_report_id + && report_id != si->desc.btn_report_id) + return 0; + + /* core handles handshake */ + rc = cyttsp5_xy_worker(dd); + if (rc < 0) + dev_err(dev, "%s: xy_worker error r=%d\n", __func__, rc); + + return rc; +} + +static ssize_t cyttsp5_interrupt_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cyttsp5_debug_data *dd = cyttsp5_get_debug_data(dev); + int val; + + mutex_lock(&dd->sysfs_lock); + val = dd->interrupt_count; + mutex_unlock(&dd->sysfs_lock); + + return scnprintf(buf, CY_MAX_PRBUF_SIZE, "Interrupt Count: %d\n", val); +} + +static ssize_t cyttsp5_interrupt_count_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct cyttsp5_debug_data *dd = cyttsp5_get_debug_data(dev); + + mutex_lock(&dd->sysfs_lock); + dd->interrupt_count = 0; + mutex_unlock(&dd->sysfs_lock); + return size; +} + +static DEVICE_ATTR(int_count, S_IRUSR | S_IWUSR, + cyttsp5_interrupt_count_show, cyttsp5_interrupt_count_store); + +static ssize_t cyttsp5_formated_output_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cyttsp5_debug_data *dd = cyttsp5_get_debug_data(dev); + int val; + + mutex_lock(&dd->sysfs_lock); + val = dd->formated_output; + mutex_unlock(&dd->sysfs_lock); + + return scnprintf(buf, CY_MAX_PRBUF_SIZE, + "Formated debug output: %x\n", val); +} + +static ssize_t cyttsp5_formated_output_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct cyttsp5_debug_data *dd = cyttsp5_get_debug_data(dev); + unsigned long value; + int rc; + + rc = kstrtoul(buf, 10, &value); + if (rc < 0) { + dev_err(dev, "%s: Invalid value\n", __func__); + return size; + } + + /* Expecting only 0 or 1 */ + if (value != 0 && value != 1) { + dev_err(dev, "%s: Invalid value %lu\n", __func__, value); + return size; + } + + mutex_lock(&dd->sysfs_lock); + dd->formated_output = value; + mutex_unlock(&dd->sysfs_lock); + return size; +} + +static DEVICE_ATTR(formated_output, S_IRUSR | S_IWUSR, + cyttsp5_formated_output_show, cyttsp5_formated_output_store); + +static int cyttsp5_debug_probe(struct device *dev, void **data) +{ + struct cyttsp5_debug_data *dd; + int rc; + + /* get context and debug print buffers */ + dd = kzalloc(sizeof(*dd), GFP_KERNEL); + if (!dd) { + rc = -ENOMEM; + goto cyttsp5_debug_probe_alloc_failed; + } + + rc = device_create_file(dev, &dev_attr_int_count); + if (rc) { + dev_err(dev, "%s: Error, could not create int_count\n", + __func__); + goto cyttsp5_debug_probe_create_int_count_failed; + } + + rc = device_create_file(dev, &dev_attr_formated_output); + if (rc) { + dev_err(dev, "%s: Error, could not create formated_output\n", + __func__); + goto cyttsp5_debug_probe_create_formated_failed; + } + + mutex_init(&dd->sysfs_lock); + dd->dev = dev; + *data = dd; + + dd->si = cmd->request_sysinfo(dev); + if (!dd->si) { + dev_err(dev, "%s: Fail get sysinfo pointer from core\n", + __func__); + rc = -ENODEV; + goto cyttsp5_debug_probe_sysinfo_failed; + } + + rc = cmd->subscribe_attention(dev, CY_ATTEN_IRQ, CYTTSP5_DEBUG_NAME, + cyttsp5_debug_attention, CY_MODE_OPERATIONAL); + if (rc < 0) { + dev_err(dev, "%s: Error, could not subscribe attention cb\n", + __func__); + goto cyttsp5_debug_probe_subscribe_failed; + } + + return 0; + +cyttsp5_debug_probe_subscribe_failed: +cyttsp5_debug_probe_sysinfo_failed: + device_remove_file(dev, &dev_attr_formated_output); +cyttsp5_debug_probe_create_formated_failed: + device_remove_file(dev, &dev_attr_int_count); +cyttsp5_debug_probe_create_int_count_failed: + kfree(dd); +cyttsp5_debug_probe_alloc_failed: + dev_err(dev, "%s failed.\n", __func__); + return rc; +} + +static void cyttsp5_debug_release(struct device *dev, void *data) +{ + struct cyttsp5_debug_data *dd = data; + int rc; + + rc = cmd->unsubscribe_attention(dev, CY_ATTEN_IRQ, CYTTSP5_DEBUG_NAME, + cyttsp5_debug_attention, CY_MODE_OPERATIONAL); + if (rc < 0) { + dev_err(dev, "%s: Error, could not un-subscribe attention\n", + __func__); + goto cyttsp5_debug_release_exit; + } + +cyttsp5_debug_release_exit: + device_remove_file(dev, &dev_attr_formated_output); + device_remove_file(dev, &dev_attr_int_count); + kfree(dd); +} + +static struct cyttsp5_module debug_module = { + .name = CYTTSP5_DEBUG_NAME, + .probe = cyttsp5_debug_probe, + .release = cyttsp5_debug_release, +}; + +static int __init cyttsp5_debug_init(void) +{ + int rc; + + cmd = cyttsp5_get_commands(); + if (!cmd) + return -EINVAL; + + rc = cyttsp5_register_module(&debug_module); + if (rc < 0) { + pr_err("%s: Error, failed registering module\n", + __func__); + return rc; + } + + pr_info("%s: Parade TTSP Debug Driver (Built %s) rc=%d\n", + __func__, CY_DRIVER_VERSION, rc); + return 0; +} +module_init(cyttsp5_debug_init); + +static void __exit cyttsp5_debug_exit(void) +{ + cyttsp5_unregister_module(&debug_module); +} +module_exit(cyttsp5_debug_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Parade TrueTouch(R) Standard Product Debug Driver"); +MODULE_AUTHOR("Parade Technologies "); diff --git a/drivers/input/touchscreen/cyttsp5_device_access.c b/drivers/input/touchscreen/cyttsp5_device_access.c new file mode 100644 index 000000000000..fa5b8196b8d3 --- /dev/null +++ b/drivers/input/touchscreen/cyttsp5_device_access.c @@ -0,0 +1,5134 @@ +/* + * cyttsp5_device_access.c + * Parade TrueTouch(TM) Standard Product V5 Device Access Module. + * Configuration and Test command/status user interface. + * For use with Parade touchscreen controllers. + * Supported parts include: + * CYTMA5XX + * CYTMA448 + * CYTMA445A + * CYTT21XXX + * CYTT31XXX + * + * Copyright (C) 2015 Parade Technologies + * Copyright (C) 2012-2015 Cypress Semiconductor + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Contact Parade Technologies at www.paradetech.com + * + */ + +#include "cyttsp5_regs.h" +#include + +#include +#include +#include + +#define CMCP_THRESHOLD_FILE_NAME "cyttsp5_thresholdfile.csv" + +/* Max test case number */ +#define MAX_CASE_NUM (22) + +/* ASCII */ +#define ASCII_LF (0x0A) +#define ASCII_CR (0x0D) +#define ASCII_COMMA (0x2C) +#define ASCII_ZERO (0x30) +#define ASCII_NINE (0x39) + +/* Max characters of test case name */ +#define NAME_SIZE_MAX (50) + +/* Max sensor and button number */ +#define MAX_BUTTONS (HID_SYSINFO_MAX_BTN) +#define MAX_SENSORS (1024) +#define MAX_TX_SENSORS (128) +#define MAX_RX_SENSORS (128) + +/* Multiply by 2 for double (min, max) values */ +#define TABLE_BUTTON_MAX_SIZE (MAX_BUTTONS * 2) +#define TABLE_SENSOR_MAX_SIZE (MAX_SENSORS * 2) +#define TABLE_TX_MAX_SIZE (MAX_TX_SENSORS*2) +#define TABLE_RX_MAX_SIZE (MAX_RX_SENSORS*2) + +#define CM_PANEL_DATA_OFFSET (6) +#define CM_BTN_DATA_OFFSET (6) +#define CP_PANEL_DATA_OFFSET (6) +#define CP_BTN_DATA_OFFSET (6) +#define MAX_BUF_LEN (50000) + +/* cmcp csv file information */ +struct configuration { + u32 cm_range_limit_row; + u32 cm_range_limit_col; + u32 cm_min_limit_cal; + u32 cm_max_limit_cal; + u32 cm_max_delta_sensor_percent; + u32 cm_max_delta_button_percent; + u32 min_sensor_rx; + u32 max_sensor_rx; + u32 min_sensor_tx; + u32 max_sensor_tx; + u32 min_button; + u32 max_button; + u32 max_delta_sensor; + u32 cp_max_delta_sensor_rx_percent; + u32 cp_max_delta_sensor_tx_percent; + u32 cm_min_max_table_button[TABLE_BUTTON_MAX_SIZE]; + u32 cp_min_max_table_button[TABLE_BUTTON_MAX_SIZE]; + u32 cm_min_max_table_sensor[TABLE_SENSOR_MAX_SIZE]; + u32 cp_min_max_table_rx[TABLE_RX_MAX_SIZE]; + u32 cp_min_max_table_tx[TABLE_TX_MAX_SIZE]; + u32 cm_min_max_table_button_size; + u32 cp_min_max_table_button_size; + u32 cm_min_max_table_sensor_size; + u32 cp_min_max_table_rx_size; + u32 cp_min_max_table_tx_size; + u32 cp_max_delta_button_percent; + u32 cm_max_table_gradient_cols_percent[TABLE_TX_MAX_SIZE]; + u32 cm_max_table_gradient_cols_percent_size; + u32 cm_max_table_gradient_rows_percent[TABLE_RX_MAX_SIZE]; + u32 cm_max_table_gradient_rows_percent_size; + u32 cm_excluding_row_edge; + u32 cm_excluding_col_edge; + u32 rx_num; + u32 tx_num; + u32 btn_num; + u32 cm_enabled; + u32 cp_enabled; + u32 is_valid_or_not; +}; + +/* Test case search definition */ +struct test_case_search { + char name[NAME_SIZE_MAX]; /* Test case name */ + u32 name_size; /* Test case name size */ + u32 offset; /* Test case offset */ +}; + +/* Test case field definition */ +struct test_case_field { + char *name; /* Test case name */ + u32 name_size; /* Test case name size */ + u32 type; /* Test case type */ + u32 *bufptr; /* Buffer to store value information */ + u32 exist_or_not;/* Test case exist or not */ + u32 data_num; /* Buffer data number */ + u32 line_num; /* Buffer line number */ +}; + +/* Test case type */ +enum test_case_type { + TEST_CASE_TYPE_NO, + TEST_CASE_TYPE_ONE, + TEST_CASE_TYPE_MUL, + TEST_CASE_TYPE_MUL_LINES, +}; + +/* Test case order in test_case_field_array */ +enum case_order { + CM_TEST_INPUTS, + CM_EXCLUDING_COL_EDGE, + CM_EXCLUDING_ROW_EDGE, + CM_GRADIENT_CHECK_COL, + CM_GRADIENT_CHECK_ROW, + CM_RANGE_LIMIT_ROW, + CM_RANGE_LIMIT_COL, + CM_MIN_LIMIT_CAL, + CM_MAX_LIMIT_CAL, + CM_MAX_DELTA_SENSOR_PERCENT, + CM_MAX_DELTA_BUTTON_PERCENT, + PER_ELEMENT_MIN_MAX_TABLE_BUTTON, + PER_ELEMENT_MIN_MAX_TABLE_SENSOR, + CP_TEST_INPUTS, + CP_MAX_DELTA_SENSOR_RX_PERCENT, + CP_MAX_DELTA_SENSOR_TX_PERCENT, + CP_MAX_DELTA_BUTTON_PERCENT, + CP_PER_ELEMENT_MIN_MAX_BUTTON, + MIN_BUTTON, + MAX_BUTTON, + PER_ELEMENT_MIN_MAX_RX, + PER_ELEMENT_MIN_MAX_TX, + CASE_ORDER_MAX, +}; + +enum cmcp_test_item { + CMCP_FULL = 0, + CMCP_CM_PANEL, + CMCP_CP_PANEL, + CMCP_CM_BTN, + CMCP_CP_BTN, +}; + +#define CM_ENABLED 0x10 +#define CP_ENABLED 0x20 +#define CM_PANEL (0x01 | CM_ENABLED) +#define CP_PANEL (0x02 | CP_ENABLED) +#define CM_BTN (0x04 | CM_ENABLED) +#define CP_BTN (0x08 | CP_ENABLED) +#define CMCP_FULL_CASE\ + (CM_PANEL | CP_PANEL | CM_BTN | CP_BTN | CM_ENABLED | CP_ENABLED) + +#define CYTTSP5_DEVICE_ACCESS_NAME "cyttsp5_device_access" +#define CYTTSP5_INPUT_ELEM_SZ (sizeof("0xHH") + 1) + +#define STATUS_SUCCESS 0 +#define STATUS_FAIL -1 +#define PIP_CMD_MAX_LENGTH ((1 << 16) - 1) + +#ifdef TTHE_TUNER_SUPPORT +struct heatmap_param { + bool scan_start; + enum scan_data_type_list data_type; /* raw, base, diff */ + int num_element; +}; +#endif +#define ABS(x) (((x) < 0) ? -(x) : (x)) + +#define CY_MAX_CONFIG_BYTES 256 +#define CYTTSP5_TTHE_TUNER_GET_PANEL_DATA_FILE_NAME "get_panel_data" +#define TTHE_TUNER_MAX_BUF (CY_MAX_PRBUF_SIZE * 3) + +struct cyttsp5_device_access_data { + struct device *dev; + struct cyttsp5_sysinfo *si; + struct mutex sysfs_lock; + u8 status; + u16 response_length; + bool sysfs_nodes_created; + struct kobject mfg_test; + u8 panel_scan_data_id; + u8 get_idac_data_id; + u8 calibrate_sensing_mode; + u8 calibrate_initialize_baselines; + u8 baseline_sensing_mode; +#ifdef TTHE_TUNER_SUPPORT + struct heatmap_param heatmap; + struct dentry *tthe_get_panel_data_debugfs; + struct mutex debugfs_lock; + u8 tthe_get_panel_data_buf[TTHE_TUNER_MAX_BUF]; + u8 tthe_get_panel_data_is_open; +#endif + struct dentry *cmcp_results_debugfs; + + struct dentry *base_dentry; + struct dentry *mfg_test_dentry; + u8 ic_buf[CY_MAX_PRBUF_SIZE]; + u8 response_buf[CY_MAX_PRBUF_SIZE]; + struct mutex cmcp_threshold_lock; + u8 *cmcp_threshold_data; + int cmcp_threshold_size; + bool cmcp_threshold_loading; + struct work_struct cmcp_threshold_update; + struct completion builtin_cmcp_threshold_complete; + int builtin_cmcp_threshold_status; + bool is_manual_upgrade_enabled; + struct configuration *configs; + struct cmcp_data *cmcp_info; + struct result *result; + struct test_case_search *test_search_array; + struct test_case_field *test_field_array; + int cmcp_test_items; + int test_executed; + int cmcp_range_check; + int cmcp_force_calibrate; + int cmcp_test_in_progress; +}; + +struct cmcp_data { + struct gd_sensor *gd_sensor_col; + struct gd_sensor *gd_sensor_row; + int32_t *cm_data_panel; + int32_t *cp_tx_data_panel; + int32_t *cp_rx_data_panel; + int32_t *cp_tx_cal_data_panel; + int32_t *cp_rx_cal_data_panel; + int32_t cp_sensor_rx_delta; + int32_t cp_sensor_tx_delta; + int32_t cp_button_delta; + int32_t *cm_btn_data; + int32_t *cp_btn_data; + int32_t *cm_sensor_column_delta; + int32_t *cm_sensor_row_delta; + int32_t cp_btn_cal; + int32_t cm_btn_cal; + int32_t cp_button_ave; + int32_t cm_ave_data_panel; + int32_t cp_tx_ave_data_panel; + int32_t cp_rx_ave_data_panel; + int32_t cm_cal_data_panel; + int32_t cm_ave_data_btn; + int32_t cm_cal_data_btn; + int32_t cm_delta_data_btn; + int32_t cm_sensor_delta; + + int32_t tx_num; + int32_t rx_num; + int32_t btn_num; +}; + +struct result { + int32_t sensor_assignment; + int32_t config_ver; + int32_t revision_ctrl; + int32_t device_id_high; + int32_t device_id_low; + bool cm_test_run; + bool cp_test_run; + /* Sensor Cm validation */ + bool cm_test_pass; + bool cm_sensor_validation_pass; + bool cm_sensor_row_delta_pass; + bool cm_sensor_col_delta_pass; + bool cm_sensor_gd_row_pass; + bool cm_sensor_gd_col_pass; + bool cm_sensor_calibration_pass; + bool cm_sensor_delta_pass; + bool cm_button_validation_pass; + bool cm_button_delta_pass; + + int32_t *cm_sensor_raw_data; + int32_t cm_sensor_calibration; + int32_t cm_sensor_delta; + int32_t *cm_button_raw_data; + int32_t cm_button_delta; + + /* Sensor Cp validation */ + bool cp_test_pass; + bool cp_sensor_delta_pass; + bool cp_sensor_rx_delta_pass; + bool cp_sensor_tx_delta_pass; + bool cp_sensor_average_pass; + bool cp_button_delta_pass; + bool cp_button_average_pass; + bool cp_rx_validation_pass; + bool cp_tx_validation_pass; + bool cp_button_validation_pass; + + int32_t *cp_sensor_rx_raw_data; + int32_t *cp_sensor_tx_raw_data; + int32_t cp_sensor_rx_delta; + int32_t cp_sensor_tx_delta; + int32_t cp_sensor_rx_calibration; + int32_t cp_sensor_tx_calibration; + int32_t *cp_button_raw_data; + int32_t cp_button_delta; + + /*other validation*/ + bool short_test_pass; + bool test_summary; + uint8_t *cm_open_pwc; +}; + +static struct cyttsp5_core_commands *cmd; + +static struct cyttsp5_module device_access_module; + +static ssize_t cyttsp5_run_and_get_selftest_result_noprint(struct device *dev, + char *buf, size_t buf_len, u8 test_id, u16 read_length, + bool get_result_on_pass); + +static int _cyttsp5_calibrate_idacs_cmd(struct device *dev, + u8 sensing_mode, u8 *status); + +static inline struct cyttsp5_device_access_data *cyttsp5_get_device_access_data( + struct device *dev) +{ + return cyttsp5_get_module_data(dev, &device_access_module); +} + +static ssize_t cyttsp5_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cyttsp5_device_access_data *dad + = cyttsp5_get_device_access_data(dev); + u8 val; + + mutex_lock(&dad->sysfs_lock); + val = dad->status; + mutex_unlock(&dad->sysfs_lock); + + return scnprintf(buf, CY_MAX_PRBUF_SIZE, "%d\n", val); +} + +static DEVICE_ATTR(status, S_IRUSR, cyttsp5_status_show, NULL); + +static ssize_t cyttsp5_response_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cyttsp5_device_access_data *dad + = cyttsp5_get_device_access_data(dev); + int i; + ssize_t num_read; + int index; + + mutex_lock(&dad->sysfs_lock); + index = scnprintf(buf, CY_MAX_PRBUF_SIZE, + "Status %d\n", dad->status); + if (!dad->status) + goto error; + + num_read = dad->response_length; + + for (i = 0; i < num_read; i++) + index += scnprintf(buf + index, CY_MAX_PRBUF_SIZE - index, + "0x%02X\n", dad->response_buf[i]); + + index += scnprintf(buf + index, CY_MAX_PRBUF_SIZE - index, + "(%zd bytes)\n", num_read); + +error: + mutex_unlock(&dad->sysfs_lock); + return index; +} + +static DEVICE_ATTR(response, S_IRUSR, cyttsp5_response_show, NULL); + +/* + * Gets user input from sysfs and parse it + * return size of parsed output buffer + */ +static int cyttsp5_ic_parse_input(struct device *dev, const char *buf, + size_t buf_size, u8 *ic_buf, size_t ic_buf_size) +{ + const char *pbuf = buf; + unsigned long value; + char scan_buf[CYTTSP5_INPUT_ELEM_SZ]; + u32 i = 0; + u32 j; + int last = 0; + int ret; + + dev_dbg(dev, "%s: pbuf=%p buf=%p size=%zu %s=%zu buf=%s\n", __func__, + pbuf, buf, buf_size, "scan buf size", + CYTTSP5_INPUT_ELEM_SZ, buf); + + while (pbuf <= (buf + buf_size)) { + if (i >= CY_MAX_CONFIG_BYTES) { + dev_err(dev, "%s: %s size=%d max=%d\n", __func__, + "Max cmd size exceeded", i, + CY_MAX_CONFIG_BYTES); + return -EINVAL; + } + if (i >= ic_buf_size) { + dev_err(dev, "%s: %s size=%d buf_size=%zu\n", __func__, + "Buffer size exceeded", i, ic_buf_size); + return -EINVAL; + } + while (((*pbuf == ' ') || (*pbuf == ',')) + && (pbuf < (buf + buf_size))) { + last = *pbuf; + pbuf++; + } + + if (pbuf >= (buf + buf_size)) + break; + + memset(scan_buf, 0, CYTTSP5_INPUT_ELEM_SZ); + if ((last == ',') && (*pbuf == ',')) { + dev_err(dev, "%s: %s \",,\" not allowed.\n", __func__, + "Invalid data format."); + return -EINVAL; + } + for (j = 0; j < (CYTTSP5_INPUT_ELEM_SZ - 1) + && (pbuf < (buf + buf_size)) + && (*pbuf != ' ') + && (*pbuf != ','); j++) { + last = *pbuf; + scan_buf[j] = *pbuf++; + } + + ret = kstrtoul(scan_buf, 16, &value); + if (ret < 0) { + dev_err(dev, "%s: %s '%s' %s%s i=%d r=%d\n", __func__, + "Invalid data format. ", scan_buf, + "Use \"0xHH,...,0xHH\"", " instead.", + i, ret); + return ret; + } + + ic_buf[i] = value; + i++; + } + + return i; +} + +static ssize_t cyttsp5_command_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct cyttsp5_device_access_data *dad + = cyttsp5_get_device_access_data(dev); + ssize_t length; + int rc; + + mutex_lock(&dad->sysfs_lock); + dad->status = 0; + dad->response_length = 0; + length = cyttsp5_ic_parse_input(dev, buf, size, dad->ic_buf, + CY_MAX_PRBUF_SIZE); + if (length <= 0) { + dev_err(dev, "%s: %s Group Data store\n", __func__, + "Malformed input for"); + goto exit; + } + + /* write ic_buf to log */ + cyttsp5_pr_buf(dev, dad->ic_buf, length, "ic_buf"); + + pm_runtime_get_sync(dev); + rc = cmd->nonhid_cmd->user_cmd(dev, 1, CY_MAX_PRBUF_SIZE, + dad->response_buf, length, dad->ic_buf, + &dad->response_length); + pm_runtime_put(dev); + if (rc) { + dad->response_length = 0; + dev_err(dev, "%s: Failed to store command\n", __func__); + } else { + dad->status = 1; + } + +exit: + mutex_unlock(&dad->sysfs_lock); + dev_vdbg(dev, "%s: return size=%zu\n", __func__, size); + return size; +} + +static DEVICE_ATTR(command, S_IWUSR, NULL, cyttsp5_command_store); + +static int cmcp_check_config_fw_match(struct device *dev, + struct configuration *configuration) +{ + struct cyttsp5_device_access_data *dad + = cyttsp5_get_device_access_data(dev); + int32_t tx_num = dad->configs->tx_num; + int32_t rx_num = dad->configs->rx_num; + int32_t button_num = dad->configs->btn_num; + int ret = 0; + + if (tx_num != dad->si->sensing_conf_data.tx_num) { + dev_err(dev, "%s: TX number mismatch!\n", __func__); + ret = -EINVAL; + } + + if (rx_num != dad->si->sensing_conf_data.rx_num) { + dev_err(dev, "%s: RX number mismatch!\n", __func__); + ret = -EINVAL; + } + + if (button_num != dad->si->num_btns) { + dev_err(dev, "%s: Button number mismatch!\n", __func__); + ret = -EINVAL; + } + + return ret; +} + +static int validate_cm_test_results(struct device *dev, + struct configuration *configuration, struct cmcp_data *cmcp_info, + struct result *result, bool *pass, int test_item) +{ + int32_t tx_num = cmcp_info->tx_num; + int32_t rx_num = cmcp_info->rx_num; + int32_t button_num = cmcp_info->btn_num; + uint32_t sensor_num = tx_num * rx_num; + int32_t *cm_sensor_data = cmcp_info->cm_data_panel; + int32_t cm_button_delta; + int32_t cm_sensor_calibration; + int32_t *cm_button_data = cmcp_info->cm_btn_data; + struct gd_sensor *gd_sensor_col = cmcp_info->gd_sensor_col; + struct gd_sensor *gd_sensor_row = cmcp_info->gd_sensor_row; + int32_t *cm_sensor_column_delta = cmcp_info->cm_sensor_column_delta; + int32_t *cm_sensor_row_delta = cmcp_info->cm_sensor_row_delta; + int ret = 0; + int i, j; + + dev_vdbg(dev, "%s: start\n", __func__); + + if ((test_item & CM_PANEL) == CM_PANEL) { + dev_vdbg(dev, "Check each sensor Cm data for min max value\n "); + + /* Check each sensor Cm data for min/max values */ + result->cm_sensor_validation_pass = true; + + for (i = 0; i < sensor_num; i++) { + int row = i % rx_num; + int col = i / rx_num; + int32_t cm_sensor_min = + configuration->cm_min_max_table_sensor[(row*tx_num+col)*2]; + int32_t cm_sensor_max = + configuration->cm_min_max_table_sensor[(row*tx_num+col)*2+1]; + if ((cm_sensor_data[i] < cm_sensor_min) + || (cm_sensor_data[i] > cm_sensor_max)) { + dev_err(dev, "%s: Sensor[%d,%d]:%d (%d,%d)\n", + "Cm sensor min/max test", + row, col, + cm_sensor_data[i], + cm_sensor_min, cm_sensor_max); + result->cm_sensor_validation_pass = false; + } + } + + /*check cm gradient column data*/ + result->cm_sensor_gd_col_pass = true; + for (i = 0; + i < configuration->cm_max_table_gradient_cols_percent_size; + i++) { + if ((gd_sensor_col + i)->gradient_val > + 10 * configuration->cm_max_table_gradient_cols_percent[i]){ + dev_err(dev, + "%s: cm_max_table_gradient_cols_percent[%d]:%d, gradient_val:%d\n", + __func__, + i, + configuration->cm_max_table_gradient_cols_percent[i], + (gd_sensor_col + i)->gradient_val); + result->cm_sensor_gd_col_pass = false; + } + } + + /*check cm gradient row data*/ + result->cm_sensor_gd_row_pass = true; + for (j = 0; + j < configuration->cm_max_table_gradient_rows_percent_size; + j++) { + if ((gd_sensor_row + j)->gradient_val > + 10 * configuration->cm_max_table_gradient_rows_percent[j]) { + dev_err(dev, + "%s: cm_max_table_gradient_rows_percent[%d]:%d, gradient_val:%d\n", + __func__, + j, configuration->cm_max_table_gradient_rows_percent[j], + (gd_sensor_row + j)->gradient_val); + result->cm_sensor_gd_row_pass = false; + } + } + + result->cm_sensor_row_delta_pass = true; + result->cm_sensor_col_delta_pass = true; + result->cm_sensor_calibration_pass = true; + result->cm_sensor_delta_pass = true; + + + /* + Check each row Cm data + with neighbor for difference + */ + for (i = 0; i < tx_num; i++) { + for (j = 1; j < rx_num; j++) { + int32_t cm_sensor_row_diff = + ABS(cm_sensor_data[i * rx_num + j] - + cm_sensor_data[i * rx_num + j - 1]); + cm_sensor_row_delta[i * rx_num + j - 1] = + cm_sensor_row_diff; + if (cm_sensor_row_diff + > configuration->cm_range_limit_row) { + dev_err(dev, + "%s: Sensor[%d,%d]:%d (%d)\n", + "Cm sensor row range limit test", + j, i, + cm_sensor_row_diff, + configuration->cm_range_limit_row); + result->cm_sensor_row_delta_pass = false; + } + } + } + + /* Check each column Cm data + with neighbor for difference + */ + for (i = 1; i < tx_num; i++) { + for (j = 0; j < rx_num; j++) { + int32_t cm_sensor_col_diff = + ABS((int)cm_sensor_data[i * rx_num + j] - + (int)cm_sensor_data[(i - 1) * rx_num + j]); + cm_sensor_column_delta[(i - 1) * rx_num + j] = + cm_sensor_col_diff; + if (cm_sensor_col_diff > + configuration->cm_range_limit_col) { + dev_err(dev, + "%s: Sensor[%d,%d]:%d (%d)\n", + "Cm sensor column range limit test", + j, i, + cm_sensor_col_diff, + configuration->cm_range_limit_col); + result->cm_sensor_col_delta_pass = false; + } + } + } + + /* Check sensor calculated Cm for min/max values */ + cm_sensor_calibration = cmcp_info->cm_cal_data_panel; + if (cm_sensor_calibration < + configuration->cm_min_limit_cal + || cm_sensor_calibration > + configuration->cm_max_limit_cal) { + dev_err(dev, "%s: Cm_cal:%d (%d,%d)\n", + "Cm sensor Cm_cal min/max test", + cm_sensor_calibration, + configuration->cm_min_limit_cal, + configuration->cm_max_limit_cal); + result->cm_sensor_calibration_pass = false; + } + + /* Check sensor Cm delta for range limit */ + if (cmcp_info->cm_sensor_delta + > 10 * configuration->cm_max_delta_sensor_percent) { + dev_err(dev, + "%s: Cm_sensor_delta:%d (%d)\n", + "Cm sensor delta range limit test", + cmcp_info->cm_sensor_delta, + configuration->cm_max_delta_sensor_percent); + result->cm_sensor_delta_pass = false; + } + + result->cm_test_pass = result->cm_sensor_gd_col_pass + && result->cm_sensor_gd_row_pass + && result->cm_sensor_validation_pass + && result->cm_sensor_row_delta_pass + && result->cm_sensor_col_delta_pass + && result->cm_sensor_calibration_pass + && result->cm_sensor_delta_pass; + } + + if (((test_item & CM_BTN) == CM_BTN) && (cmcp_info->btn_num)) { + /* Check each button Cm data for min/max values */ + result->cm_button_validation_pass = true; + for (i = 0; i < button_num; i++) { + int32_t cm_button_min = + configuration->cm_min_max_table_button[i * 2]; + int32_t cm_button_max = + configuration->cm_min_max_table_button[i * 2 + 1]; + if ((cm_button_data[i] <= cm_button_min) + || (cm_button_data[i] >= cm_button_max)) { + dev_err(dev, + "%s: Button[%d]:%d (%d,%d)\n", + "Cm button min/max test", + i, + cm_button_data[i], + cm_button_min, cm_button_max); + result->cm_button_validation_pass = false; + } + } + + /* Check button Cm delta for range limit */ + result->cm_button_delta_pass = true; + + cm_button_delta = ABS((cmcp_info->cm_ave_data_btn - + cmcp_info->cm_cal_data_btn) * 100 / + cmcp_info->cm_ave_data_btn); + if (cm_button_delta > + configuration->cm_max_delta_button_percent) { + dev_err(dev, + "%s: Cm_button_delta:%d (%d)\n", + "Cm button delta range limit test", + cm_button_delta, + configuration->cm_max_delta_button_percent); + result->cm_button_delta_pass = false; + } + + result->cm_test_pass = result->cm_test_pass + && result->cm_button_validation_pass + && result->cm_button_delta_pass; + } + + if (pass) + *pass = result->cm_test_pass; + + return ret; +} +static int validate_cp_test_results(struct device *dev, + struct configuration *configuration, struct cmcp_data *cmcp_info, + struct result *result, bool *pass, int test_item) +{ + int i = 0; + uint32_t configuration_rx_num; + uint32_t configuration_tx_num; + int32_t *cp_sensor_tx_data = cmcp_info->cp_tx_data_panel; + int32_t *cp_sensor_rx_data = cmcp_info->cp_rx_data_panel; + int32_t cp_button_delta; + int32_t cp_button_average; + + result->cp_test_pass = true; + configuration_rx_num = configuration->cp_min_max_table_rx_size/2; + configuration_tx_num = configuration->cp_min_max_table_tx_size/2; + + dev_vdbg(dev, "%s start\n", __func__); + + if ((test_item & CP_PANEL) == CP_PANEL) { + int32_t cp_sensor_tx_delta; + int32_t cp_sensor_rx_delta; + + /* Check Sensor Cp delta for range limit */ + result->cp_sensor_delta_pass = true; + /*check cp_sensor_tx_delta */ + for (i = 0; i < configuration_tx_num; i++) { + cp_sensor_tx_delta = + ABS((cmcp_info->cp_tx_cal_data_panel[i]- + cmcp_info->cp_tx_data_panel[i]) * 100 / + cmcp_info->cp_tx_data_panel[i]); + + if (cp_sensor_tx_delta > + configuration->cp_max_delta_sensor_tx_percent) { + dev_err(dev, + "%s: Cp_sensor_tx_delta:%d (%d)\n", + "Cp sensor delta range limit test", + cp_sensor_tx_delta, + configuration->cp_max_delta_sensor_tx_percent); + result->cp_sensor_delta_pass = false; + } + } + + /*check cp_sensor_rx_delta */ + for (i = 0; i < configuration_rx_num; i++) { + cp_sensor_rx_delta = + ABS((cmcp_info->cp_rx_cal_data_panel[i] - + cmcp_info->cp_rx_data_panel[i]) * 100 / + cmcp_info->cp_rx_data_panel[i]); + if (cp_sensor_rx_delta > + configuration->cp_max_delta_sensor_rx_percent) { + dev_err(dev, + "%s: Cp_sensor_rx_delta:%d(%d)\n", + "Cp sensor delta range limit test", + cp_sensor_rx_delta, + configuration->cp_max_delta_sensor_rx_percent); + result->cp_sensor_delta_pass = false; + } + } + + /* Check sensor Cp rx for min/max values */ + result->cp_rx_validation_pass = true; + for (i = 0; i < configuration_rx_num; i++) { + int32_t cp_rx_min = + configuration->cp_min_max_table_rx[i * 2]; + int32_t cp_rx_max = + configuration->cp_min_max_table_rx[i * 2 + 1]; + if ((cp_sensor_rx_data[i] <= cp_rx_min) + || (cp_sensor_rx_data[i] >= cp_rx_max)) { + dev_err(dev, + "%s: Cp Rx[%d]:%d (%d,%d)\n", + "Cp Rx min/max test", + i, + (int)cp_sensor_rx_data[i], + cp_rx_min, cp_rx_max); + result->cp_rx_validation_pass = false; + } + } + + /* Check sensor Cp tx for min/max values */ + result->cp_tx_validation_pass = true; + for (i = 0; i < configuration_tx_num; i++) { + int32_t cp_tx_min = + configuration->cp_min_max_table_tx[i * 2]; + int32_t cp_tx_max = + configuration->cp_min_max_table_tx[i * 2 + 1]; + if ((cp_sensor_tx_data[i] <= cp_tx_min) + || (cp_sensor_tx_data[i] >= cp_tx_max)) { + dev_err(dev, + "%s: Cp Tx[%d]:%d(%d,%d)\n", + "Cp Tx min/max test", + i, + cp_sensor_tx_data[i], + cp_tx_min, cp_tx_max); + result->cp_tx_validation_pass = false; + } + } + + result->cp_test_pass = result->cp_test_pass + && result->cp_sensor_delta_pass + && result->cp_rx_validation_pass + && result->cp_tx_validation_pass; + } + + if (((test_item & CP_BTN) == CP_BTN) && (cmcp_info->btn_num)) { + result->cp_button_delta_pass = true; + + /* Check button Cp delta for range limit */ + cp_button_delta = ABS((cmcp_info->cp_btn_cal + - cmcp_info->cp_button_ave) * 100 / + cmcp_info->cp_button_ave); + if (cp_button_delta > + configuration->cp_max_delta_button_percent) { + dev_err(dev, + "%s: Cp_button_delta:%d (%d)\n", + "Cp button delta range limit test", + cp_button_delta, + configuration->cp_max_delta_button_percent); + result->cp_button_delta_pass = false; + } + + /* Check button Cp average for min/max values */ + result->cp_button_average_pass = true; + cp_button_average = cmcp_info->cp_button_ave; + if (cp_button_average < configuration->min_button + || cp_button_average > + configuration->max_button) { + dev_err(dev, + "%s: Button Cp average fails min/max test\n", + __func__); + dev_err(dev, + "%s: Cp_button_average:%d (%d,%d)\n", + "Cp button average min/max test", + cp_button_average, + configuration->min_button, + configuration->max_button); + result->cp_button_average_pass = false; + } + + /* Check each button Cp data for min/max values */ + result->cp_button_validation_pass = true; + for (i = 0; i < cmcp_info->btn_num; i++) { + int32_t cp_button_min = + configuration->cp_min_max_table_button[i * 2]; + int32_t cp_button_max = + configuration->cp_min_max_table_button[i * 2 + 1]; + if ((cmcp_info->cp_btn_data[i] <= cp_button_min) + || (cmcp_info->cp_btn_data[i] >= cp_button_max)) { + dev_err(dev, + "%s: Button[%d]:%d (%d,%d)\n", + "Cp button min/max test", + i, + cmcp_info->cp_btn_data[i], + cp_button_min, cp_button_max); + result->cp_button_validation_pass = false; + } + } + + result->cp_test_pass = result->cp_test_pass + && result->cp_button_delta_pass + && result->cp_button_average_pass + && result->cp_button_validation_pass; + } + + if (pass) + *pass = result->cp_test_pass; + + return 0; +} + +static void calculate_gradient_row(struct gd_sensor *gd_sensor_row_head, + uint16_t row_num, int exclude_row_edge, int exclude_col_edge) +{ + int i = 0; + uint16_t cm_min_cur = 0; + uint16_t cm_max_cur = 0; + uint16_t cm_ave_cur = 0; + uint16_t cm_ave_next = 0; + uint16_t cm_ave_prev = 0; + struct gd_sensor *p = gd_sensor_row_head; + + if (exclude_row_edge) { + for (i = 0; i < row_num; i++) { + if (!exclude_col_edge) { + cm_ave_cur = (p + i)->cm_ave; + cm_min_cur = (p + i)->cm_min; + cm_max_cur = (p + i)->cm_max; + if (i < (row_num-1)) + cm_ave_next = (p + i+1)->cm_ave; + if (i > 0) + cm_ave_prev = (p + i-1)->cm_ave; + } else { + cm_ave_cur = (p + i)->cm_ave_exclude_edge; + cm_min_cur = (p + i)->cm_min_exclude_edge; + cm_max_cur = (p + i)->cm_max_exclude_edge; + if (i < (row_num-1)) + cm_ave_next = + (p + i+1)->cm_ave_exclude_edge; + if (i > 0) + cm_ave_prev = + (p + i-1)->cm_ave_exclude_edge; + } + + if (cm_ave_cur == 0) + cm_ave_cur = 1; + + /*multiple 1000 to increate accuracy*/ + if ((i == 0) || (i == (row_num-1))) { + (p + i)->gradient_val = + (cm_max_cur - cm_min_cur) * 1000 / + cm_ave_cur; + } else if (i == 1) { + (p + i)->gradient_val = (cm_max_cur - cm_min_cur + + ABS(cm_ave_cur - cm_ave_next)) * 1000 / + cm_ave_cur; + } else { + (p + i)->gradient_val = (cm_max_cur - cm_min_cur + + ABS(cm_ave_cur - cm_ave_prev)) * 1000 / + cm_ave_cur; + } + } + } else if (!exclude_row_edge) { + for (i = 0; i < row_num; i++) { + if (!exclude_col_edge) { + cm_ave_cur = (p + i)->cm_ave; + cm_min_cur = (p + i)->cm_min; + cm_max_cur = (p + i)->cm_max; + if (i < (row_num-1)) + cm_ave_next = (p + i+1)->cm_ave; + if (i > 0) + cm_ave_prev = (p + i-1)->cm_ave; + } else { + cm_ave_cur = (p + i)->cm_ave_exclude_edge; + cm_min_cur = (p + i)->cm_min_exclude_edge; + cm_max_cur = (p + i)->cm_max_exclude_edge; + if (i < (row_num-1)) + cm_ave_next = + (p + i+1)->cm_ave_exclude_edge; + if (i > 0) + cm_ave_prev = + (p + i-1)->cm_ave_exclude_edge; + } + + if (cm_ave_cur == 0) + cm_ave_cur = 1; + /*multiple 1000 to increate accuracy*/ + if (i <= 1) + (p + i)->gradient_val = (cm_max_cur - cm_min_cur + + ABS(cm_ave_cur - cm_ave_next)) * 1000 / + cm_ave_cur; + else + (p + i)->gradient_val = (cm_max_cur - cm_min_cur + + ABS(cm_ave_cur - cm_ave_prev)) * 1000 / + cm_ave_cur; + } + } +} + +static void calculate_gradient_col(struct gd_sensor *gd_sensor_row_head, + uint16_t col_num, int exclude_row_edge, int exclude_col_edge) +{ + int i = 0; + int32_t cm_min_cur = 0; + int32_t cm_max_cur = 0; + int32_t cm_ave_cur = 0; + int32_t cm_ave_next = 0; + int32_t cm_ave_prev = 0; + struct gd_sensor *p = gd_sensor_row_head; + + if (!exclude_col_edge) { + for (i = 0; i < col_num; i++) { + if (!exclude_row_edge) { + cm_ave_cur = (p + i)->cm_ave; + cm_min_cur = (p + i)->cm_min; + cm_max_cur = (p + i)->cm_max; + if (i < (col_num-1)) + cm_ave_next = (p + i+1)->cm_ave; + if (i > 0) + cm_ave_prev = (p + i-1)->cm_ave; + } else { + cm_ave_cur = (p + i)->cm_ave_exclude_edge; + cm_min_cur = (p + i)->cm_min_exclude_edge; + cm_max_cur = (p + i)->cm_max_exclude_edge; + if (i < (col_num-1)) + cm_ave_next = + (p + i+1)->cm_ave_exclude_edge; + if (i > 0) + cm_ave_prev = + (p + i-1)->cm_ave_exclude_edge; + } + if (cm_ave_cur == 0) + cm_ave_cur = 1; + /*multiple 1000 to increate accuracy*/ + if (i <= 1) + (p + i)->gradient_val = (cm_max_cur - cm_min_cur + + ABS(cm_ave_cur - cm_ave_next)) * 1000 / + cm_ave_cur; + else + (p + i)->gradient_val = (cm_max_cur - cm_min_cur + + ABS(cm_ave_cur - cm_ave_prev)) * 1000 / + cm_ave_cur; + } + } else if (exclude_col_edge) { + for (i = 0; i < col_num; i++) { + if (!exclude_row_edge) { + cm_ave_cur = (p + i)->cm_ave; + cm_min_cur = (p + i)->cm_min; + cm_max_cur = (p + i)->cm_max; + if (i < (col_num-1)) + cm_ave_next = (p + i+1)->cm_ave; + if (i > 0) + cm_ave_prev = (p + i-1)->cm_ave; + } else { + cm_ave_cur = (p + i)->cm_ave_exclude_edge; + cm_min_cur = (p + i)->cm_min_exclude_edge; + cm_max_cur = (p + i)->cm_max_exclude_edge; + if (i < (col_num-1)) + cm_ave_next = + (p + i+1)->cm_ave_exclude_edge; + if (i > 0) + cm_ave_prev = + (p + i-1)->cm_ave_exclude_edge; + } + + if (cm_ave_cur == 0) + cm_ave_cur = 1; + /*multiple 1000 to increate accuracy*/ + if ((i == 0) || (i == (col_num - 1))) + (p + i)->gradient_val = + (cm_max_cur - cm_min_cur) * 1000 / + cm_ave_cur; + else if (i == 1) + (p + i)->gradient_val = + (cm_max_cur - cm_min_cur + + ABS(cm_ave_cur - cm_ave_next)) + * 1000 / cm_ave_cur; + else + (p + i)->gradient_val = + (cm_max_cur - cm_min_cur + + ABS(cm_ave_cur - cm_ave_prev)) + * 1000 / cm_ave_cur; + } + } +} + +static void fill_gd_sensor_table(struct gd_sensor *head, int32_t index, + int32_t cm_max, int32_t cm_min, int32_t cm_ave, + int32_t cm_max_exclude_edge, int32_t cm_min_exclude_edge, + int32_t cm_ave_exclude_edge) +{ + (head + index)->cm_max = cm_max; + (head + index)->cm_min = cm_min; + (head + index)->cm_ave = cm_ave; + (head + index)->cm_ave_exclude_edge = cm_ave_exclude_edge; + (head + index)->cm_max_exclude_edge = cm_max_exclude_edge; + (head + index)->cm_min_exclude_edge = cm_min_exclude_edge; +} + +static void calculate_gd_info(struct gd_sensor *gd_sensor_col, + struct gd_sensor *gd_sensor_row, int tx_num, int rx_num, + int32_t *cm_sensor_data, int cm_excluding_row_edge, + int cm_excluding_col_edge) +{ + int32_t cm_max; + int32_t cm_min; + int32_t cm_ave; + int32_t cm_max_exclude_edge; + int32_t cm_min_exclude_edge; + int32_t cm_ave_exclude_edge; + int32_t cm_data; + int i; + int j; + + /*calculate all the gradient related info for column*/ + for (i = 0; i < tx_num; i++) { + /*re-initialize for a new col*/ + cm_max = cm_sensor_data[i * rx_num]; + cm_min = cm_max; + cm_ave = 0; + cm_max_exclude_edge = cm_sensor_data[i * rx_num + 1]; + cm_min_exclude_edge = cm_max_exclude_edge; + cm_ave_exclude_edge = 0; + + for (j = 0; j < rx_num; j++) { + cm_data = cm_sensor_data[i * rx_num + j]; + if (cm_data > cm_max) + cm_max = cm_data; + if (cm_data < cm_min) + cm_min = cm_data; + cm_ave += cm_data; + /*calculate exclude edge data*/ + if ((j > 0) && (j < (rx_num-1))) { + if (cm_data > cm_max_exclude_edge) + cm_max_exclude_edge = cm_data; + if (cm_data < cm_min_exclude_edge) + cm_min_exclude_edge = cm_data; + cm_ave_exclude_edge += cm_data; + } + } + cm_ave /= rx_num; + cm_ave_exclude_edge /= (rx_num-2); + fill_gd_sensor_table(gd_sensor_col, i, cm_max, cm_min, cm_ave, + cm_max_exclude_edge, cm_min_exclude_edge, cm_ave_exclude_edge); + } + + calculate_gradient_col(gd_sensor_col, tx_num, cm_excluding_row_edge, + cm_excluding_col_edge); + + /*calculate all the gradient related info for row*/ + for (j = 0; j < rx_num; j++) { + /*re-initialize for a new row*/ + cm_max = cm_sensor_data[j]; + cm_min = cm_max; + cm_ave = 0; + cm_max_exclude_edge = cm_sensor_data[rx_num + j]; + cm_min_exclude_edge = cm_max_exclude_edge; + cm_ave_exclude_edge = 0; + for (i = 0; i < tx_num; i++) { + cm_data = cm_sensor_data[i * rx_num + j]; + if (cm_data > cm_max) + cm_max = cm_data; + if (cm_data < cm_min) + cm_min = cm_data; + cm_ave += cm_data; + /*calculate exclude edge data*/ + if ((i > 0) && (i < (tx_num-1))) { + if (cm_data > cm_max_exclude_edge) + cm_max_exclude_edge = cm_data; + if (cm_data < cm_min_exclude_edge) + cm_min_exclude_edge = cm_data; + cm_ave_exclude_edge += cm_data; + } + } + cm_ave /= tx_num; + cm_ave_exclude_edge /= (tx_num-2); + fill_gd_sensor_table(gd_sensor_row, j, cm_max, cm_min, cm_ave, + cm_max_exclude_edge, cm_min_exclude_edge, cm_ave_exclude_edge); + } + calculate_gradient_row(gd_sensor_row, rx_num, cm_excluding_row_edge, + cm_excluding_col_edge); +} + +static int cyttsp5_get_cmcp_info(struct cyttsp5_device_access_data *dad, + struct cmcp_data *cmcp_info) +{ + struct device *dev; + int32_t *cm_data_panel = cmcp_info->cm_data_panel; + int32_t *cp_tx_data_panel = cmcp_info->cp_tx_data_panel; + int32_t *cp_rx_data_panel = cmcp_info->cp_rx_data_panel; + int32_t *cp_tx_cal_data_panel = cmcp_info->cp_tx_cal_data_panel; + int32_t *cp_rx_cal_data_panel = cmcp_info->cp_rx_cal_data_panel; + int32_t *cm_btn_data = cmcp_info->cm_btn_data; + int32_t *cp_btn_data = cmcp_info->cp_btn_data; + struct gd_sensor *gd_sensor_col = cmcp_info->gd_sensor_col; + struct gd_sensor *gd_sensor_row = cmcp_info->gd_sensor_row; + struct result *result = dad->result; + int32_t cp_btn_cal = 0; + int32_t cm_btn_cal = 0; + int32_t cp_btn_ave = 0; + int32_t cm_ave_data_panel = 0; + int32_t cm_ave_data_btn = 0; + int32_t cm_delta_data_btn = 0; + int32_t cp_tx_ave_data_panel = 0; + int32_t cp_rx_ave_data_panel = 0; + u8 tmp_buf[3]; + int tx_num; + int rx_num; + int btn_num; + int rc = 0; + int i; + + dev = dad->dev; + cmcp_info->tx_num = dad->si->sensing_conf_data.tx_num; + cmcp_info->rx_num = dad->si->sensing_conf_data.rx_num; + cmcp_info->btn_num = dad->si->num_btns; + + tx_num = cmcp_info->tx_num; + rx_num = cmcp_info->rx_num; + btn_num = cmcp_info->btn_num; + dev_vdbg(dev, "%s tx_num=%d", __func__, tx_num); + dev_vdbg(dev, "%s rx_num=%d", __func__, rx_num); + dev_vdbg(dev, "%s btn_num=%d", __func__, btn_num); + + /*short test*/ + result->short_test_pass = true; + rc = cyttsp5_run_and_get_selftest_result_noprint( + dev, tmp_buf, sizeof(tmp_buf), + CY_ST_ID_AUTOSHORTS, PIP_CMD_MAX_LENGTH, false); + if (rc) { + dev_err(dev, "short test not supported"); + goto exit; + } + if (dad->ic_buf[1] != 0) + result->short_test_pass = false; + + /*Get cm_panel data*/ + rc = cyttsp5_run_and_get_selftest_result_noprint( + dev, tmp_buf, sizeof(tmp_buf), + CY_ST_ID_CM_PANEL, PIP_CMD_MAX_LENGTH, true); + if (rc) { + dev_err(dev, "Get CM Panel not supported"); + goto exit; + } + if (cm_data_panel != NULL) { + for (i = 0; i < tx_num * rx_num; i++) { + cm_data_panel[i] = + 10*(dad->ic_buf[CM_PANEL_DATA_OFFSET+i*2] + 256 + * dad->ic_buf[CM_PANEL_DATA_OFFSET+i*2+1]); + dev_vdbg(dev, + "cm_data_panel[%d]=%d\n", + i, cm_data_panel[i]); + cm_ave_data_panel += cm_data_panel[i]; + } + cm_ave_data_panel /= (tx_num * rx_num); + cmcp_info->cm_ave_data_panel = cm_ave_data_panel; + cmcp_info->cm_cal_data_panel = + 10*(dad->ic_buf[CM_PANEL_DATA_OFFSET+i*2] + +256 * dad->ic_buf[CM_PANEL_DATA_OFFSET+i*2+1]); + /*multiple 1000 to increate accuracy*/ + cmcp_info->cm_sensor_delta = ABS((cmcp_info->cm_ave_data_panel - + cmcp_info->cm_cal_data_panel) * 1000 / + cmcp_info->cm_ave_data_panel); + } + + /*calculate gradient panel sensor column/row here*/ + calculate_gd_info(gd_sensor_col, gd_sensor_row, tx_num, rx_num, + cm_data_panel, 1, 1); + for (i = 0; i < tx_num; i++) { + dev_vdbg(dev, + "i=%d max=%d,min=%d,ave=%d, gradient=%d", + i, gd_sensor_col[i].cm_max, gd_sensor_col[i].cm_min, + gd_sensor_col[i].cm_ave, gd_sensor_col[i].gradient_val); + } + + for (i = 0; i < rx_num; i++) { + dev_vdbg(dev, + "i=%d max=%d,min=%d,ave=%d, gradient=%d", + i, gd_sensor_row[i].cm_max, gd_sensor_row[i].cm_min, + gd_sensor_row[i].cm_ave, gd_sensor_row[i].gradient_val); + } + + /*Get cp data*/ + rc = cyttsp5_run_and_get_selftest_result_noprint( + dev, tmp_buf, sizeof(tmp_buf), + CY_ST_ID_CP_PANEL, PIP_CMD_MAX_LENGTH, true); + if (rc) { + dev_err(dev, "Get CP Panel not supported"); + goto exit; + } + /*Get cp_tx_data_panel*/ + if (cp_tx_data_panel != NULL) { + for (i = 0; i < tx_num; i++) { + cp_tx_data_panel[i] = + 10*(dad->ic_buf[CP_PANEL_DATA_OFFSET+i*2] + + 256 * dad->ic_buf[CP_PANEL_DATA_OFFSET+i*2+1]); + dev_vdbg(dev, + "cp_tx_data_panel[%d]=%d\n", + i, cp_tx_data_panel[i]); + cp_tx_ave_data_panel += cp_tx_data_panel[i]; + } + cp_tx_ave_data_panel /= tx_num; + cmcp_info->cp_tx_ave_data_panel = cp_tx_ave_data_panel; + } + + /*Get cp_tx_cal_data_panel*/ + if (cp_tx_cal_data_panel != NULL) { + for (i = 0; i < tx_num; i++) { + cp_tx_cal_data_panel[i] = + 10*(dad->ic_buf[CP_PANEL_DATA_OFFSET+tx_num*2+i*2] + + 256 * dad->ic_buf[CP_PANEL_DATA_OFFSET+tx_num*2+i*2+1]); + dev_vdbg(dev, " cp_tx_cal_data_panel[%d]=%d\n", + i, cp_tx_cal_data_panel[i]); + } + } + + /*get cp_sensor_tx_delta,using the first sensor cal value for temp */ + /*multiple 1000 to increase accuracy*/ + cmcp_info->cp_sensor_tx_delta = ABS((cp_tx_cal_data_panel[0] + - cp_tx_ave_data_panel) * 1000 / cp_tx_ave_data_panel); + + /*Get cp_rx_data_panel*/ + if (cp_rx_data_panel != NULL) { + for (i = 0; i < rx_num; i++) { + cp_rx_data_panel[i] = + 10*(dad->ic_buf[CP_PANEL_DATA_OFFSET+tx_num*4+i*2] + + 256 * dad->ic_buf[CP_PANEL_DATA_OFFSET+tx_num*4+i*2+1]); + dev_vdbg(dev, + "cp_rx_data_panel[%d]=%d\n", i, cp_rx_data_panel[i]); + cp_rx_ave_data_panel += cp_rx_data_panel[i]; + } + cp_rx_ave_data_panel /= rx_num; + cmcp_info->cp_rx_ave_data_panel = cp_rx_ave_data_panel; + } + + /*Get cp_rx_cal_data_panel*/ + if (cp_rx_cal_data_panel != NULL) { + for (i = 0; i < rx_num; i++) { + cp_rx_cal_data_panel[i] = + 10 * (dad->ic_buf[CP_PANEL_DATA_OFFSET+tx_num*4+rx_num*2+i*2] + + 256 * + dad->ic_buf[CP_PANEL_DATA_OFFSET+tx_num*4+rx_num*2+i*2+1]); + dev_vdbg(dev, + "cp_rx_cal_data_panel[%d]=%d\n", + i, + cp_rx_cal_data_panel[i]); + } + } + + /*get cp_sensor_rx_delta,using the first sensor cal value for temp */ + /*multiple 1000 to increase accuracy*/ + cmcp_info->cp_sensor_rx_delta = ABS((cp_rx_cal_data_panel[0] + - cp_rx_ave_data_panel) * 1000 / cp_rx_ave_data_panel); + + if (btn_num == 0) + goto skip_button_test; + + /*get cm btn data*/ + rc = cyttsp5_run_and_get_selftest_result_noprint( + dev, tmp_buf, sizeof(tmp_buf), + CY_ST_ID_CM_BUTTON, PIP_CMD_MAX_LENGTH, true); + if (rc) { + dev_err(dev, "Get CM BTN not supported"); + goto exit; + } + if (cm_btn_data != NULL) { + for (i = 0; i < btn_num; i++) { + cm_btn_data[i] = + 10 * (dad->ic_buf[CM_BTN_DATA_OFFSET+i*2] + + 256 * dad->ic_buf[CM_BTN_DATA_OFFSET+i*2+1]); + dev_vdbg(dev, + " cm_btn_data[%d]=%d\n", + i, cm_btn_data[i]); + cm_ave_data_btn += cm_btn_data[i]; + } + cm_ave_data_btn /= btn_num; + cm_btn_cal = 10*(dad->ic_buf[CM_BTN_DATA_OFFSET+i*2] + + 256 * dad->ic_buf[CM_BTN_DATA_OFFSET+i*2+1]); + /*multiple 1000 to increase accuracy*/ + cm_delta_data_btn = ABS((cm_ave_data_btn-cm_btn_cal) + * 1000 / cm_ave_data_btn); + dev_vdbg(dev, " cm_btn_cal=%d\n", cm_btn_cal); + + cmcp_info->cm_ave_data_btn = cm_ave_data_btn; + cmcp_info->cm_cal_data_btn = cm_btn_cal; + cmcp_info->cm_delta_data_btn = cm_delta_data_btn; + } + + /*get cp btn data*/ + rc = cyttsp5_run_and_get_selftest_result_noprint( + dev, tmp_buf, sizeof(tmp_buf), + CY_ST_ID_CP_BUTTON, PIP_CMD_MAX_LENGTH, true); + if (rc) { + dev_err(dev, "Get CP BTN not supported"); + goto exit; + } + if (cp_btn_data != NULL) { + for (i = 0; i < btn_num; i++) { + cp_btn_data[i] = + 10 * (dad->ic_buf[CP_BTN_DATA_OFFSET+i*2] + + 256 * dad->ic_buf[CP_BTN_DATA_OFFSET+i*2+1]); + cp_btn_ave += cp_btn_data[i]; + dev_vdbg(dev, + "cp_btn_data[%d]=%d\n", + i, cp_btn_data[i]); + } + cp_btn_ave /= btn_num; + cp_btn_cal = 10*(dad->ic_buf[CP_BTN_DATA_OFFSET+i*2] + + 256 * dad->ic_buf[CP_BTN_DATA_OFFSET+i*2+1]); + cmcp_info->cp_button_ave = cp_btn_ave; + cmcp_info->cp_btn_cal = cp_btn_cal; + /*multiple 1000 to increase accuracy*/ + cmcp_info->cp_button_delta = ABS((cp_btn_cal + - cp_btn_ave) * 1000 / cp_btn_ave); + dev_vdbg(dev, " cp_btn_cal=%d\n", cp_btn_cal); + dev_vdbg(dev, " cp_btn_ave=%d\n", cp_btn_ave); + } +skip_button_test: +exit: + return rc; +} + +static void cyttsp5_free_cmcp_buf(struct cmcp_data *cmcp_info) +{ + if (cmcp_info->gd_sensor_col != NULL) + kfree(cmcp_info->gd_sensor_col); + if (cmcp_info->gd_sensor_row != NULL) + kfree(cmcp_info->gd_sensor_row); + if (cmcp_info->cm_data_panel != NULL) + kfree(cmcp_info->cm_data_panel); + if (cmcp_info->cp_tx_data_panel != NULL) + kfree(cmcp_info->cp_tx_data_panel); + if (cmcp_info->cp_rx_data_panel != NULL) + kfree(cmcp_info->cp_rx_data_panel); + if (cmcp_info->cp_tx_cal_data_panel != NULL) + kfree(cmcp_info->cp_tx_cal_data_panel); + if (cmcp_info->cp_rx_cal_data_panel != NULL) + kfree(cmcp_info->cp_rx_cal_data_panel); + if (cmcp_info->cm_btn_data != NULL) + kfree(cmcp_info->cm_btn_data); + if (cmcp_info->cp_btn_data != NULL) + kfree(cmcp_info->cp_btn_data); + if (cmcp_info->cm_sensor_column_delta != NULL) + kfree(cmcp_info->cm_sensor_column_delta); + if (cmcp_info->cm_sensor_row_delta != NULL) + kfree(cmcp_info->cm_sensor_row_delta); +} + +static int cyttsp5_cmcp_get_test_item(int item_input) +{ + int test_item = 0; + + switch (item_input) { + case CMCP_FULL: + test_item = CMCP_FULL_CASE; + break; + case CMCP_CM_PANEL: + test_item = CM_PANEL; + break; + case CMCP_CP_PANEL: + test_item = CP_PANEL; + break; + case CMCP_CM_BTN: + test_item = CM_BTN; + break; + case CMCP_CP_BTN: + test_item = CP_BTN; + break; + } + return test_item; +} + +static ssize_t cyttsp5_cmcp_test_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cyttsp5_device_access_data *dad + = cyttsp5_get_device_access_data(dev); + struct cmcp_data *cmcp_info = dad->cmcp_info; + struct result *result = dad->result; + struct configuration *configuration = dad->configs; + bool final_pass = true; + static const char * const cmcp_test_case_array[] = {"Full Cm/Cp test", + "Cm panel test", "Cp panel test", + "Cm button test", "Cp button test"}; + int index = 0; + int test_item = 0; + int no_builtin_file = 0; + int rc; + u8 status; + int self_test_id_supported = 0; + + dev = dad->dev; + if ((configuration == NULL) || (cmcp_info == NULL)) + goto exit; + + mutex_lock(&dad->sysfs_lock); + + if (dad->cmcp_test_in_progress) { + mutex_unlock(&dad->sysfs_lock); + goto cmcp_not_ready; + } + dad->cmcp_test_in_progress = 1; + + dad->test_executed = 0; + test_item = cyttsp5_cmcp_get_test_item(dad->cmcp_test_items); + + if (dad->builtin_cmcp_threshold_status < 0) { + dev_err(dev, "%s: No cmcp threshold file.\n", __func__); + no_builtin_file = 1; + mutex_unlock(&dad->sysfs_lock); + goto start_testing; + } + + if (dad->cmcp_test_items < 0) { + dev_vdbg(dev, + "%s: Invalid test item! Should be 0~4!\n", __func__); + mutex_unlock(&dad->sysfs_lock); + goto invalid_item; + } + + dev_vdbg(dev, "%s: Test item is %s, %d\n", __func__, + cmcp_test_case_array[dad->cmcp_test_items], test_item); + + if ((dad->si->num_btns == 0) + && ((dad->cmcp_test_items == CMCP_CM_BTN) + || (dad->cmcp_test_items == CMCP_CP_BTN))) { + dev_vdbg(dev, "%s: FW doesn't support button!\n", __func__); + mutex_unlock(&dad->sysfs_lock); + goto invalid_item_btn; + } + + mutex_unlock(&dad->sysfs_lock); + + if (cmcp_check_config_fw_match(dev, configuration)) + goto mismatch; + +start_testing: + dev_vdbg(dev, "%s: Start Cm/Cp test!\n", __func__); + result->cm_test_pass = true; + result->cp_test_pass = true; + /*stop watchdog*/ + rc = cmd->request_stop_wd(dev); + if (rc) + dev_err(dev, "stop watchdog failed"); + /*force single tx*/ + rc = cmd->nonhid_cmd->set_param(dev, 0, 0x1F, 1, 1); + if (rc) + dev_err(dev, "force single tx failed"); + /*suspend_scanning */ + rc = cmd->nonhid_cmd->suspend_scanning(dev, 0); + if (rc) + dev_err(dev, "suspend_scanning failed"); + /*do calibration*/ + if (!dad->cmcp_force_calibrate) { + dev_vdbg(dev, "do calibration in single tx mode"); + rc = _cyttsp5_calibrate_idacs_cmd(dev, 0, &status); + if (rc < 0) { + dev_err(dev, "%s: Error on calibrate idacs for mutual r=%d\n", + __func__, rc); + } + rc = _cyttsp5_calibrate_idacs_cmd(dev, 1, &status); + if (rc < 0) { + dev_err(dev, "%s: Error on calibrate idacs for buttons r=%d\n", + __func__, rc); + } + rc = _cyttsp5_calibrate_idacs_cmd(dev, 2, &status); + if (rc < 0) { + dev_err(dev, "%s: Error on calibrate idacs for self r=%d\n", + __func__, rc); + } + } + /*resume_scanning */ + rc = cmd->nonhid_cmd->resume_scanning(dev, 0); + if (rc) + dev_err(dev, "resume_scanning failed"); + + /*get all cmcp data from FW*/ + self_test_id_supported = + cyttsp5_get_cmcp_info(dad, cmcp_info); + if (self_test_id_supported) + dev_err(dev, "cyttsp5_get_cmcp_info failed"); + + /*restore to multi tx*/ + rc = cmd->nonhid_cmd->set_param(dev, 0, 0x1F, 0, 1); + if (rc) + dev_err(dev, "restore multi tx failed"); + + /*suspend_scanning */ + rc = cmd->nonhid_cmd->suspend_scanning(dev, 0); + if (rc) + dev_err(dev, "suspend_scanning failed"); + /*do calibration*/ + if (!dad->cmcp_force_calibrate) { + dev_vdbg(dev, "do calibration in multi tx mode"); + rc = _cyttsp5_calibrate_idacs_cmd(dev, 0, &status); + if (rc < 0) { + dev_err(dev, "%s: Error on calibrate idacs for mutual r=%d\n", + __func__, rc); + } + rc = _cyttsp5_calibrate_idacs_cmd(dev, 1, &status); + if (rc < 0) { + dev_err(dev, "%s: Error on calibrate idacs for buttons r=%d\n", + __func__, rc); + } + rc = _cyttsp5_calibrate_idacs_cmd(dev, 2, &status); + if (rc < 0) { + dev_err(dev, "%s: Error on calibrate idacs for self r=%d\n", + __func__, rc); + } + + } + /*resume_scanning */ + rc = cmd->nonhid_cmd->resume_scanning(dev, 0); + if (rc) + dev_err(dev, "resume_scanning failed"); + + /*start watchdog*/ + rc = cmd->request_start_wd(dev); + if (rc) + dev_err(dev, "start watchdog failed"); + + if (self_test_id_supported) + goto self_test_id_failed; + + if (no_builtin_file) + goto no_builtin; + + if (test_item && CM_ENABLED) + validate_cm_test_results(dev, configuration, cmcp_info, + result, &final_pass, test_item); + + if (test_item && CP_ENABLED) + validate_cp_test_results(dev, configuration, cmcp_info, + result, &final_pass, test_item); +no_builtin: + if ((dad->cmcp_test_items == CMCP_FULL) + && (dad->cmcp_range_check == 0)) { + /*full test and full check*/ + result->test_summary = + result->cm_test_pass + && result->cp_test_pass + && result->short_test_pass; + } else if ((dad->cmcp_test_items == CMCP_FULL) + && (dad->cmcp_range_check == 1)) { + /*full test and basic check*/ + result->test_summary = + result->cm_sensor_gd_col_pass + && result->cm_sensor_gd_row_pass + && result->cm_sensor_validation_pass + && result->cp_rx_validation_pass + && result->cp_tx_validation_pass + && result->short_test_pass; + } else if (dad->cmcp_test_items == CMCP_CM_PANEL) { + /*cm panel test result only*/ + result->test_summary = + result->cm_sensor_gd_col_pass + && result->cm_sensor_gd_row_pass + && result->cm_sensor_validation_pass + && result->cm_sensor_row_delta_pass + && result->cm_sensor_col_delta_pass + && result->cm_sensor_calibration_pass + && result->cm_sensor_delta_pass; + } else if (dad->cmcp_test_items == CMCP_CP_PANEL) { + /*cp panel test result only*/ + result->test_summary = + result->cp_sensor_delta_pass + && result->cp_rx_validation_pass + && result->cp_tx_validation_pass; + } else if (dad->cmcp_test_items == CMCP_CM_BTN) { + /*cm button test result only*/ + result->test_summary = + result->cm_button_validation_pass + && result->cm_button_delta_pass; + } else if (dad->cmcp_test_items == CMCP_CP_BTN) { + /*cp button test result only*/ + result->test_summary = + result->cp_button_delta_pass + && result->cp_button_average_pass + && result->cp_button_validation_pass; + } + mutex_lock(&dad->sysfs_lock); + dad->test_executed = 1; + mutex_unlock(&dad->sysfs_lock); + + dev_vdbg(dev, "%s: Finish Cm/Cp test!\n", __func__); + index = snprintf(buf, CY_MAX_PRBUF_SIZE, + "Status 1\n"); + goto cmcp_ready; +mismatch: + index = snprintf(buf, CY_MAX_PRBUF_SIZE, + "Status 2\nInput cmcp threshold file mismatches with FW\n"); + goto cmcp_ready; +invalid_item_btn: + index = snprintf(buf, CY_MAX_PRBUF_SIZE, + "Status 3\nFW doesn't support button!\n"); + goto cmcp_ready; +invalid_item: + index = snprintf(buf, CY_MAX_PRBUF_SIZE, + "Status 4\nWrong test item or range check input!\nOnly support below items:\n0 - Cm/Cp Panel & Button with Gradient (Typical)\n1 - Cm Panel with Gradient\n2 - Cp Panel\n3 - Cm Button\n4 - Cp Button\nOnly support below range check:\n0 - Full Range Checking (default)\n1 - Basic Range Checking(TSG5 style)\n"); + goto cmcp_ready; +self_test_id_failed: + index = snprintf(buf, CY_MAX_PRBUF_SIZE, + "Status 5\nget self test ID not supported!"); + goto cmcp_ready; +cmcp_not_ready: + index = snprintf(buf, CY_MAX_PRBUF_SIZE, + "Status 0\n"); + goto cmcp_ready; +cmcp_ready: + mutex_lock(&dad->sysfs_lock); + dad->cmcp_test_in_progress = 0; + mutex_unlock(&dad->sysfs_lock); +exit: + return index; +} + +static ssize_t cyttsp5_cmcp_test_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct cyttsp5_device_access_data *dad + = cyttsp5_get_device_access_data(dev); + u8 test_item = 0; + u8 range_check = 0; + u8 force_calibrate = 0; + int ret; + static const char * const cmcp_test_case_array[] = {"Full Cm/Cp test", + "Cm panel test", "Cp panel test", + "Cm button test", "Cp button test"}; + static const char * const cmcp_test_range_check_array[] = { + "Full (default)", "Basic"}; + static const char * const cmcp_test_force_cal_array[] = { + "Calibrate When Testing (default)", "No Calibration"}; + ssize_t length = 0; + + pm_runtime_get_sync(dev); + mutex_lock(&dad->sysfs_lock); + + length = cyttsp5_ic_parse_input(dev, buf, size, dad->ic_buf, + CY_MAX_PRBUF_SIZE); + if (length <= 0 || length > 3) { + dev_err(dev, "%s: Input format error!\n", __func__); + dad->cmcp_test_items = -EINVAL; + ret = -EINVAL; + goto error; + } + + /* Get test item */ + test_item = dad->ic_buf[0]; + /* Get range check */ + if (length >= 2) + range_check = dad->ic_buf[1]; + /* Get force calibration */ + if (length == 3) + force_calibrate = dad->ic_buf[2]; + + /* Test item limitation: + 0: Perform all Tests + 1: CM Panel with Gradient + 2: CP Panel + 3: CM Button + 4: CP Button + Ranage check limitation: + 0: full check + 1: basic check + Force calibrate limitation: + 0: do calibration + 1: don't do calibration + */ + if ((test_item < 0) || (test_item > 4) || (range_check > 1) + || (force_calibrate > 1)) { + dev_err(dev, + "%s: Test item should be 0~4; Range check should be 0~1; Force calibrate should be 0~1\n", + __func__); + dad->cmcp_test_items = -EINVAL; + ret = -EINVAL; + goto error; + } + /*if it is not all Test, then + range_check should be 0 + because other test does + not has concept of basic + check + */ + if (test_item > 0 && test_item < 5) + range_check = 0; + dad->cmcp_test_items = test_item; + dad->cmcp_range_check = range_check; + dad->cmcp_force_calibrate = force_calibrate; + dev_vdbg(dev, "%s: Test item is %s; Range check is %s; Force calibrate is %s.\n", + __func__, + cmcp_test_case_array[test_item], + cmcp_test_range_check_array[range_check], + cmcp_test_force_cal_array[force_calibrate]); + +error: + mutex_unlock(&dad->sysfs_lock); + pm_runtime_put(dev); + + if (ret) + return ret; + + return size; +} + +static DEVICE_ATTR(cmcp_test, S_IRUSR | S_IWUSR, + cyttsp5_cmcp_test_show, cyttsp5_cmcp_test_store); + +int prepare_print_string(char *out_buf, char *in_buf, int index) +{ + if ((out_buf == NULL) || (in_buf == NULL)) + return index; + index += scnprintf(&out_buf[index], MAX_BUF_LEN - index, + "%s", in_buf); + return index; +} + +int prepare_print_data(char *out_buf, int32_t *in_buf, int index, int data_num) +{ + int i; + + if ((out_buf == NULL) || (in_buf == NULL)) + return index; + for (i = 0; i < data_num; i++) + index += scnprintf(&out_buf[index], MAX_BUF_LEN - index, + "%d,", in_buf[i]); + return index; +} + +int save_header(char *out_buf, int index, struct result *result) +{ + struct timex txc; + struct rtc_time tm; + char time_buf[100] = {0}; + + do_gettimeofday(&(txc.time)); + rtc_time_to_tm(txc.time.tv_sec, &tm); + scnprintf(time_buf, 100, "%d/%d/%d,TIME,%d:%d:%d,", tm.tm_year+1900, + tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); + + index = prepare_print_string(out_buf, ",.header,\n", index); + index = prepare_print_string(out_buf, ",DATE,", index); + index = prepare_print_string(out_buf, &time_buf[0], index); + index = prepare_print_string(out_buf, ",\n", index); + index = prepare_print_string(out_buf, ",SW_VERSION,", index); + index = prepare_print_string(out_buf, CY_DRIVER_VERSION, index); + index = prepare_print_string(out_buf, ",\n", index); + index = prepare_print_string(out_buf, ",.end,\n", index); + index = prepare_print_string(out_buf, ",.engineering data,\n", index); + + return index; +} + +static int print_silicon_id(char *out_buf, char *in_buf, int index) +{ + index = prepare_print_string(out_buf, ",1,", index); + index = prepare_print_string(out_buf, &in_buf[0], index); + return index; +} + +int save_engineering_data(struct device *dev, char *out_buf, int index, + struct cmcp_data *cmcp_info, struct configuration *configuration, + struct result *result, int test_item, int no_builtin_file) +{ + int i; + int j; + int tx_num = cmcp_info->tx_num; + int rx_num = cmcp_info->rx_num; + int btn_num = cmcp_info->btn_num; + int tmp = 0; + uint32_t fw_revision_control; + uint32_t fw_config_ver; + char device_id[20] = {0}; + struct cyttsp5_device_access_data *dad + = cyttsp5_get_device_access_data(dev); + + fw_revision_control = dad->si->cydata.revctrl; + fw_config_ver = dad->si->cydata.fw_ver_conf; + /*calculate silicon id*/ + result->device_id_low = 0; + result->device_id_high = 0; + + for (i = 0; i < 4; i++) + result->device_id_low = + (result->device_id_low << 8) + dad->si->cydata.mfg_id[i]; + + for (i = 4; i < 8; i++) + result->device_id_high = + (result->device_id_high << 8) + dad->si->cydata.mfg_id[i]; + + scnprintf(device_id, 20, "%x%x", + result->device_id_high, result->device_id_low); + + /*print test summary*/ + index = print_silicon_id(out_buf, &device_id[0], index); + if (result->test_summary) + index = prepare_print_string(out_buf, ",PASS,\n", index); + else + index = prepare_print_string(out_buf, ",FAIL,\n", index); + + /*revision ctrl number*/ + index = print_silicon_id(out_buf, &device_id[0], index); + index = prepare_print_string(out_buf, ",FW revision Control,", index); + index = prepare_print_data(out_buf, &fw_revision_control, index, 1); + index = prepare_print_string(out_buf, "\n", index); + + /*config version*/ + index = print_silicon_id(out_buf, &device_id[0], index); + index = prepare_print_string(out_buf, ",CONFIG_VER,", index); + index = prepare_print_data(out_buf, &fw_config_ver, index, 1); + index = prepare_print_string(out_buf, "\n", index); + + /*short test*/ + index = print_silicon_id(out_buf, &device_id[0], index); + if (result->short_test_pass) + index = prepare_print_string(out_buf, ",Shorts,PASS,\n", index); + else + index = prepare_print_string(out_buf, ",Shorts,FAIL,\n", index); + + if ((test_item & CM_ENABLED) == CM_ENABLED) { + /*print BUTNS_CM_DATA_ROW00*/ + if (((test_item & CM_BTN) == CM_BTN) && (btn_num > 0)) { + index = print_silicon_id(out_buf, &device_id[0], index); + index = prepare_print_string(out_buf, + ",Sensor Cm Validation,BUTNS_CM_DATA_ROW00,", + index); + index = prepare_print_data(out_buf, + &cmcp_info->cm_btn_data[0], + index, + btn_num); + index = prepare_print_string(out_buf, "\n", index); + } + + if ((test_item & CM_PANEL) == CM_PANEL) { + /*print CM_DATA_ROW*/ + for (i = 0; i < rx_num; i++) { + index = print_silicon_id(out_buf, &device_id[0], + index); + index = prepare_print_string(out_buf, + ",Sensor Cm Validation,CM_DATA_ROW", + index); + index = prepare_print_data(out_buf, &i, + index, 1); + for (j = 0; j < tx_num; j++) + index = prepare_print_data(out_buf, + &cmcp_info->cm_data_panel[j*rx_num+i], + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + } + + if (!no_builtin_file) { + /*print CM_MAX_GRADIENT_COLS_PERCENT*/ + index = print_silicon_id(out_buf, + &device_id[0], index); + index = prepare_print_string(out_buf, + ",Sensor Cm Validation,CM_MAX_GRADIENT_COLS_PERCENT,", + index); + for (i = 0; i < tx_num; i++) { + char tmp_buf[10] = {0}; + + scnprintf(tmp_buf, 10, "%d.%d,", + cmcp_info->gd_sensor_col[i].gradient_val / 10, + cmcp_info->gd_sensor_col[i].gradient_val % 10); + index = prepare_print_string(out_buf, + &tmp_buf[0], index); + } + index = prepare_print_string(out_buf, + "\n", index); + + /*print CM_MAX_GRADIENT_ROWS_PERCENT*/ + index = print_silicon_id(out_buf, + &device_id[0], index); + index = prepare_print_string(out_buf, + ",Sensor Cm Validation,CM_MAX_GRADIENT_ROWS_PERCENT,", + index); + for (i = 0; i < rx_num; i++) { + char tmp_buf[10] = {0}; + + scnprintf(tmp_buf, 10, "%d.%d,", + cmcp_info->gd_sensor_row[i].gradient_val / 10, + cmcp_info->gd_sensor_row[i].gradient_val % 10); + index = prepare_print_string(out_buf, + &tmp_buf[0], index); + } + index = prepare_print_string(out_buf, + "\n", index); + + if (!dad->cmcp_range_check) { + /*print CM_DELTA_COLUMN*/ + for (i = 0; i < rx_num; i++) { + index = print_silicon_id( + out_buf, + &device_id[0], index); + index = prepare_print_string( + out_buf, + ",Sensor Cm Validation,DELTA_COLUMNS_ROW", + index); + index = prepare_print_data( + out_buf, + &i, index, 1); + index = prepare_print_data( + out_buf, + &tmp, index, 1); + for (j = 1; j < tx_num; j++) + index = prepare_print_data( + out_buf, + &cmcp_info->cm_sensor_column_delta[(j-1)*rx_num+i], + index, 1); + index = prepare_print_string( + out_buf, + "\n", index); + } + + /*print CM_DELTA_ROW*/ + index = print_silicon_id(out_buf, + &device_id[0], + index); + index = prepare_print_string(out_buf, + ",Sensor Cm Validation,DELTA_ROWS_ROW", + index); + index = prepare_print_data(out_buf, + &tmp, index, 1); + for (j = 0; j < tx_num; j++) + index = prepare_print_data( + out_buf, + &tmp, index, 1); + index = prepare_print_string(out_buf, + "\n", index); + + for (i = 1; i < rx_num; i++) { + index = print_silicon_id( + out_buf, + &device_id[0], + index); + index = prepare_print_string( + out_buf, + ",Sensor Cm Validation,DELTA_ROWS_ROW", + index); + index = prepare_print_data( + out_buf, &i, + index, 1); + for (j = 0; j < tx_num; j++) + index = prepare_print_data( + out_buf, + &cmcp_info->cm_sensor_row_delta[j*rx_num+i-1], + index, 1); + index = prepare_print_string( + out_buf, + "\n", index); + } + + /*print pass/fail Sensor Cm Validation*/ + index = print_silicon_id(out_buf, &device_id[0], + index); + if (result->cm_test_pass) + index = prepare_print_string(out_buf, + ",Sensor Cm Validation,PASS,\n", + index); + else + index = prepare_print_string(out_buf, + ",Sensor Cm Validation,FAIL,\n", + index); + } + } + } + + if (!no_builtin_file) { + if (((test_item & CM_BTN) == CM_BTN) && (btn_num > 0) + && (!dad->cmcp_range_check)) { + char tmp_buf[10] = {0}; + /*print Button Element by Element */ + index = print_silicon_id(out_buf, &device_id[0], + index); + if (result->cm_button_validation_pass) + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Button Element by Element,PASS\n", + index); + else + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Button Element by Element,FAIL\n", + index); + + /* + *print Sensor Cm Validation + *- Buttons Range Buttons Range + */ + index = print_silicon_id(out_buf, + &device_id[0], index); + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Buttons Range,Buttons Range,", + index); + scnprintf(tmp_buf, 10, "%d.%d,", + cmcp_info->cm_delta_data_btn / 10, + cmcp_info->cm_delta_data_btn % 10); + index = prepare_print_string(out_buf, + &tmp_buf[0], index); + index = prepare_print_string(out_buf, + "\n", index); + + /*print Sensor Cm Validation + *-Buttons Range Cm_button_avg + */ + index = print_silicon_id(out_buf, + &device_id[0], index); + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Buttons Range,Cm_button_avg,", + index); + index = prepare_print_data(out_buf, + &cmcp_info->cm_ave_data_btn, + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + + /*print Sensor Cm Validation + * -Buttons Range Cm_button_avg + */ + index = print_silicon_id(out_buf, + &device_id[0], index); + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Buttons Range,Cm_button_cal,", + index); + index = prepare_print_data(out_buf, + &cmcp_info->cm_cal_data_btn, + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + + /*print Sensor Cm Validation + *-Buttons Range pass/fail + */ + index = print_silicon_id(out_buf, + &device_id[0], index); + if (result->cm_button_delta_pass) + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Buttons Range,PASS,LIMITS,", + index); + else + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Buttons Range,FAIL,LIMITS,", + index); + index = prepare_print_data(out_buf, + &configuration->cm_max_delta_button_percent, + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + } + + if ((test_item & CM_PANEL) == CM_PANEL && + !dad->cmcp_range_check) { + char tmp_buf[10] = {0}; + /*print Cm_sensor_cal */ + index = print_silicon_id(out_buf, + &device_id[0], index); + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Calibration,Cm_sensor_cal,", + index); + index = prepare_print_data(out_buf, + &cmcp_info->cm_cal_data_panel, + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + + /*print Cm_sensor_cal limit*/ + index = print_silicon_id(out_buf, + &device_id[0], index); + if (result->cm_sensor_calibration_pass) + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Calibration,PASS,LIMITS,", + index); + else + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Calibration,FAIL,LIMITS,", + index); + index = prepare_print_data(out_buf, + &configuration->cm_min_limit_cal, + index, 1); + index = prepare_print_data(out_buf, + &configuration->cm_max_limit_cal, + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + + /*print Columns Delta Matrix*/ + index = print_silicon_id(out_buf, + &device_id[0], index); + if (result->cm_sensor_col_delta_pass) + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Columns Delta Matrix,PASS,LIMITS,", + index); + else + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Columns Delta Matrix,FAIL,LIMITS,", + index); + index = prepare_print_data(out_buf, + &configuration->cm_range_limit_col, + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + + /*print Cm Validation - Element by Element*/ + index = print_silicon_id(out_buf, + &device_id[0], index); + if (result->cm_sensor_validation_pass) + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Element by Element,PASS,", + index); + else + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Element by Element,FAIL,", + index); + index = prepare_print_string(out_buf, + "\n", index); + + /*print Cm Validation -Gradient Cols*/ + index = print_silicon_id(out_buf, + &device_id[0], index); + if (result->cm_sensor_gd_col_pass) + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Gradient Cols,PASS,", + index); + else + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Gradient Cols,FAIL,", + index); + index = prepare_print_string(out_buf, + "\n", index); + + /*print Cm Validation -Gradient Rows*/ + index = print_silicon_id(out_buf, + &device_id[0], index); + if (result->cm_sensor_gd_row_pass) + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Gradient Rows,PASS,", + index); + else + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Gradient Rows,FAIL,", + index); + index = prepare_print_string(out_buf, + "\n", index); + + + /*print Sensor Cm Validation + *-Rows Delta Matrix + */ + index = print_silicon_id(out_buf, + &device_id[0], index); + if (result->cm_sensor_row_delta_pass) + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Rows Delta Matrix,PASS,LIMITS,", + index); + else + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Rows Delta Matrix,FAIL,LIMITS,", + index); + index = prepare_print_data(out_buf, + &configuration->cm_range_limit_row, + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + + /*print Cm_sensor_avg */ + index = print_silicon_id(out_buf, + &device_id[0], index); + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Sensor Range,Cm_sensor_avg,", + index); + index = prepare_print_data(out_buf, + &cmcp_info->cm_ave_data_panel, + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + + /*printSensor Cm Validation - + * Sensor Range, Sensor Range + */ + index = print_silicon_id(out_buf, + &device_id[0], index); + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Sensor Range,Sensor Range,", + index); + scnprintf(tmp_buf, 10, "%d.%d,", + cmcp_info->cm_sensor_delta / 10, + cmcp_info->cm_sensor_delta % 10); + index = prepare_print_string(out_buf, + &tmp_buf[0], index); + index = prepare_print_string(out_buf, + "\n", index); + + /*print Sensor Cm Validation - Sensor Range*/ + index = print_silicon_id(out_buf, + &device_id[0], index); + if (result->cm_sensor_delta_pass) + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Sensor Range,PASS,LIMITS,", + index); + else + index = prepare_print_string(out_buf, + ",Sensor Cm Validation - Sensor Range,FAIL,LIMITS,", + index); + index = prepare_print_data(out_buf, + &configuration->cm_max_delta_sensor_percent, + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + } + } + } + + if ((test_item & CP_ENABLED) == CP_ENABLED) { + if (((test_item & CP_BTN) == CP_BTN) && (btn_num > 0)) { + /*print BUTNS_CP_DATA_ROW00 */ + index = print_silicon_id(out_buf, &device_id[0], index); + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,BUTNS_CP_DATA_ROW00,", + index); + index = prepare_print_data(out_buf, + &cmcp_info->cp_btn_data[0], + index, btn_num); + index = prepare_print_string(out_buf, + "\n", index); + + if (!no_builtin_file && !dad->cmcp_range_check) { + /*print Cp Button Element by Element */ + index = print_silicon_id(out_buf, &device_id[0], + index); + if (result->cp_button_validation_pass) + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check - Button Element by Element,PASS\n", + index); + else + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check - Button Element by Element,FAIL\n", + index); + + /*print cp_button_ave */ + index = print_silicon_id(out_buf, + &device_id[0], index); + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,Cp_button_avg,", + index); + index = prepare_print_data(out_buf, + &cmcp_info->cp_button_ave, + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + + /*print Cp_button_cal */ + index = print_silicon_id(out_buf, + &device_id[0], index); + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,Cp_button_cal,", + index); + index = prepare_print_data(out_buf, + &cmcp_info->cp_btn_cal, + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + } + } + + if ((test_item & CP_PANEL) == CP_PANEL) { + /*print CP_DATA_RX */ + index = print_silicon_id(out_buf, &device_id[0], index); + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,CP_DATA_RX,", index); + index = prepare_print_data(out_buf, + &cmcp_info->cp_rx_data_panel[0], index, rx_num); + index = prepare_print_string(out_buf, "\n", index); + + /*print CP_DATA_TX */ + index = print_silicon_id(out_buf, &device_id[0], index); + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,CP_DATA_TX,", index); + index = prepare_print_data(out_buf, + &cmcp_info->cp_tx_data_panel[0], index, tx_num); + index = prepare_print_string(out_buf, "\n", index); + } + if (((test_item & CP_BTN) == CP_BTN) && (btn_num > 0) + && !dad->cmcp_range_check) { + if (!no_builtin_file) { + char tmp_buf[10] = {0}; + /*print Cp_delta_button */ + index = print_silicon_id(out_buf, &device_id[0], + index); + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,Cp_delta_button,", + index); + scnprintf(tmp_buf, 10, "%d.%d,", + cmcp_info->cp_button_delta / 10, + cmcp_info->cp_button_delta % 10); + index = prepare_print_string(out_buf, + &tmp_buf[0], index); + index = prepare_print_string(out_buf, "\n", + index); + } + } + if ((test_item & CP_PANEL) == CP_PANEL && + !dad->cmcp_range_check) { + if (!no_builtin_file) { + char tmp_buf[10] = {0}; + /*print Cp_delta_rx */ + index = print_silicon_id(out_buf, &device_id[0], + index); + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,Cp_delta_rx,", + index); + scnprintf(tmp_buf, 10, "%d.%d,", + cmcp_info->cp_sensor_rx_delta / 10, + cmcp_info->cp_sensor_rx_delta % 10); + index = prepare_print_string(out_buf, + &tmp_buf[0], index); + index = prepare_print_string(out_buf, "\n", + index); + + /*print Cp_delta_tx */ + index = print_silicon_id(out_buf, &device_id[0], + index); + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,Cp_delta_tx,", + index); + scnprintf(tmp_buf, 10, "%d.%d,", + cmcp_info->cp_sensor_tx_delta / 10, + cmcp_info->cp_sensor_tx_delta % 10); + index = prepare_print_string(out_buf, + &tmp_buf[0], index); + index = prepare_print_string(out_buf, "\n", + index); + + /*print Cp_sensor_avg_rx */ + index = print_silicon_id(out_buf, &device_id[0], + index); + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,Cp_sensor_avg_rx,", + index); + index = prepare_print_data(out_buf, + &cmcp_info->cp_rx_ave_data_panel, + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + + /*print Cp_sensor_avg_tx */ + index = print_silicon_id(out_buf, + &device_id[0], index); + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,Cp_sensor_avg_tx,", + index); + index = prepare_print_data(out_buf, + &cmcp_info->cp_tx_ave_data_panel, + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + + /*print Cp_sensor_cal_rx */ + index = print_silicon_id(out_buf, + &device_id[0], index); + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,Cp_sensor_cal_rx,", + index); + index = prepare_print_data(out_buf, + &cmcp_info->cp_rx_cal_data_panel[0], + index, rx_num); + index = prepare_print_string(out_buf, + "\n", index); + + /*print Cp_sensor_cal_tx */ + index = print_silicon_id(out_buf, + &device_id[0], index); + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,Cp_sensor_cal_tx,", + index); + index = prepare_print_data(out_buf, + &cmcp_info->cp_tx_cal_data_panel[0], + index, tx_num); + index = prepare_print_string(out_buf, + "\n", index); + } + } + + if (!no_builtin_file && !dad->cmcp_range_check) { + /*print cp test limits */ + index = print_silicon_id(out_buf, &device_id[0], index); + if (result->cp_test_pass) + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,PASS, LIMITS,", + index); + else + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,FAIL, LIMITS,", + index); + + index = prepare_print_string(out_buf, + "CP_MAX_DELTA_SENSOR_RX_PERCENT,", index); + index = prepare_print_data(out_buf, + &configuration->cp_max_delta_sensor_rx_percent, + index, 1); + index = prepare_print_string(out_buf, + "CP_MAX_DELTA_SENSOR_TX_PERCENT,", index); + index = prepare_print_data(out_buf, + &configuration->cp_max_delta_sensor_tx_percent, + index, 1); + index = prepare_print_string(out_buf, + "CP_MAX_DELTA_BUTTON_PERCENT,", index); + index = prepare_print_data(out_buf, + &configuration->cp_max_delta_button_percent, + index, 1); + index = prepare_print_string(out_buf, "\n", index); + } + } + + if (!no_builtin_file) { + if ((test_item & CM_ENABLED) == CM_ENABLED) { + if ((test_item & CM_PANEL) == CM_PANEL) { + /*print columns gradient limit*/ + index = prepare_print_string(out_buf, + ",Sensor Cm Validation,MAX_LIMITS,CM_MAX_GRADIENT_COLS_PERCENT,", + index); + index = prepare_print_data(out_buf, + &configuration->cm_max_table_gradient_cols_percent[0], + index, + configuration->cm_max_table_gradient_cols_percent_size); + index = prepare_print_string(out_buf, + "\n", index); + /*print rows gradient limit*/ + index = prepare_print_string(out_buf, + ",Sensor Cm Validation,MAX_LIMITS,CM_MAX_GRADIENT_ROWS_PERCENT,", + index); + index = prepare_print_data(out_buf, + &configuration->cm_max_table_gradient_rows_percent[0], + index, + configuration->cm_max_table_gradient_rows_percent_size); + index = prepare_print_string(out_buf, + "\n", index); + + /*print cm max limit*/ + for (i = 0; i < rx_num; i++) { + index = prepare_print_string(out_buf, + ",Sensor Cm Validation,MAX_LIMITS,CM_DATA_ROW", + index); + index = prepare_print_data(out_buf, + &i, index, 1); + for (j = 0; j < tx_num; j++) + index = prepare_print_data( + out_buf, + &configuration->cm_min_max_table_sensor[i*tx_num*2+j*2+1], + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + } + } + + if (((test_item & CM_BTN) == CM_BTN) && (btn_num > 0)) { + index = prepare_print_string(out_buf, + ",Sensor Cm Validation,MAX LIMITS,M_BUTNS,", + index); + for (j = 0; j < btn_num; j++) { + index = prepare_print_data(out_buf, + &configuration->cm_min_max_table_button[2*j+1], + index, 1); + } + index = prepare_print_string(out_buf, + "\n", index); + } + + index = prepare_print_string(out_buf, + ",Sensor Cm Validation MAX LIMITS\n", index); + + if ((test_item & CM_PANEL) == CM_PANEL) { + /*print cm min limit*/ + for (i = 0; i < rx_num; i++) { + index = prepare_print_string(out_buf, + ",Sensor Cm Validation,MIN_LIMITS,CM_DATA_ROW", + index); + index = prepare_print_data(out_buf, &i, + index, 1); + for (j = 0; j < tx_num; j++) + index = prepare_print_data( + out_buf, + &configuration->cm_min_max_table_sensor[i*tx_num*2 + j*2], + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + } + } + + if (((test_item & CM_BTN) == CM_BTN) && (btn_num > 0)) { + index = prepare_print_string(out_buf, + ",Sensor Cm Validation,MIN LIMITS,M_BUTNS,", + index); + for (j = 0; j < btn_num; j++) { + index = prepare_print_data(out_buf, + &configuration->cm_min_max_table_button[2*j], + index, 1); + } + index = prepare_print_string(out_buf, + "\n", index); + } + index = prepare_print_string(out_buf, + ",Sensor Cm Validation MIN LIMITS\n", index); + } + + if ((test_item & CP_ENABLED) == CP_ENABLED) { + if ((test_item & CP_PANEL) == CP_PANEL) { + /*print cp tx max limit*/ + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,MAX_LIMITS,TX,", + index); + for (i = 0; i < tx_num; i++) + index = prepare_print_data(out_buf, + &configuration->cp_min_max_table_tx[i*2+1], + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + + /*print cp rx max limit*/ + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,MAX_LIMITS,RX,", + index); + for (i = 0; i < rx_num; i++) + index = prepare_print_data(out_buf, + &configuration->cp_min_max_table_rx[i*2+1], + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + } + + /*print cp btn max limit*/ + if (((test_item & CP_BTN) == CP_BTN) && (btn_num > 0)) { + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,MAX_LIMITS,S_BUTNS,", + index); + for (i = 0; i < btn_num; i++) + index = prepare_print_data(out_buf, + &configuration->cp_min_max_table_button[i*2+1], + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + } + + if ((test_item & CP_PANEL) == CP_PANEL) { + /*print cp tx min limit*/ + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,MIN_LIMITS,TX,", + index); + for (i = 0; i < tx_num; i++) + index = prepare_print_data(out_buf, + &configuration->cp_min_max_table_tx[i*2], + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + + /*print cp rx min limit*/ + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,MIN_LIMITS,RX,", + index); + for (i = 0; i < rx_num; i++) + index = prepare_print_data(out_buf, + &configuration->cp_min_max_table_rx[i*2], + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + } + + /*print cp btn min limit*/ + if (((test_item & CP_BTN) == CP_BTN) && (btn_num > 0)) { + index = prepare_print_string(out_buf, + ",Self-cap Calibration Check,MIN_LIMITS,S_BUTNS,", + index); + for (i = 0; i < btn_num; i++) + index = prepare_print_data(out_buf, + &configuration->cp_min_max_table_button[i*2], + index, 1); + index = prepare_print_string(out_buf, + "\n", index); + } + } + } + return index; +} + +int result_save(struct device *dev, char *buf, + struct configuration *configuration, struct result *result, + struct cmcp_data *cmcp_info, loff_t *ppos, size_t count, int test_item, + int no_builtin_file) +{ + u8 *out_buf = NULL; + int index = 0; + int byte_left; + + out_buf = kzalloc(MAX_BUF_LEN, GFP_KERNEL); + if (configuration == NULL) + dev_err(dev, "config is NULL"); + if (result == NULL) + dev_err(dev, "result is NULL"); + if (cmcp_info == NULL) + dev_err(dev, "cmcp_info is NULL"); + + index = save_header(out_buf, index, result); + index = save_engineering_data(dev, out_buf, index, + cmcp_info, configuration, result, + test_item, no_builtin_file); + byte_left = simple_read_from_buffer(buf, count, ppos, out_buf, index); + + kfree(out_buf); + return byte_left; +} + +static int cmcp_results_debugfs_open(struct inode *inode, + struct file *filp) +{ + filp->private_data = inode->i_private; + return 0; +} + +static int cmcp_results_debugfs_close(struct inode *inode, + struct file *filp) +{ + filp->private_data = NULL; + return 0; +} + +static ssize_t cmcp_results_debugfs_read(struct file *filp, + char __user *buf, size_t count, loff_t *ppos) +{ + struct cyttsp5_device_access_data *dad = filp->private_data; + struct device *dev; + struct cmcp_data *cmcp_info = dad->cmcp_info; + struct result *result = dad->result; + struct configuration *configuration = dad->configs; + int ret = 0; + int test_item; + int no_builtin_file = 0; + int test_executed = 0; + + dev = dad->dev; + + mutex_lock(&dad->sysfs_lock); + test_executed = dad->test_executed; + test_item = cyttsp5_cmcp_get_test_item(dad->cmcp_test_items); + if (dad->builtin_cmcp_threshold_status < 0) { + dev_err(dev, "%s: No cmcp threshold file.\n", __func__); + no_builtin_file = 1; + } + mutex_unlock(&dad->sysfs_lock); + + if (test_executed) + /*save result to buf*/ + ret = result_save(dev, buf, configuration, result, cmcp_info, + ppos, count, test_item, no_builtin_file); + else { + char warning_info[] = + "No test result available!\n"; + dev_err(dev, "%s: No test result available!\n", __func__); + + return simple_read_from_buffer(buf, count, ppos, warning_info, + strlen(warning_info)); + } + + return ret; +} + +static const struct file_operations cmcp_results_debugfs_fops = { + .open = cmcp_results_debugfs_open, + .release = cmcp_results_debugfs_close, + .read = cmcp_results_debugfs_read, + .write = NULL, +}; + +static ssize_t cyttsp5_cmcp_threshold_loading_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cyttsp5_device_access_data *dad + = cyttsp5_get_device_access_data(dev); + bool cmcp_threshold_loading; + + mutex_lock(&dad->cmcp_threshold_lock); + cmcp_threshold_loading = dad->cmcp_threshold_loading; + mutex_unlock(&dad->cmcp_threshold_lock); + + return sprintf(buf, "%d\n", cmcp_threshold_loading); +} + +/* Return the buffer offset of new test case */ +u32 cmcp_return_offset_of_new_case(const char *bufPtr, u32 first_time) +{ + static u32 offset, first_search; + + if (first_time == 0) { + first_search = 0; + offset = 0; + } + + if (first_search != 0) { + /* Search one case */ + for (;;) { + /* Search ASCII_LF */ + while (*bufPtr++ != ASCII_LF) + offset++; + + offset++; + /* Single line: end loop + * Multiple lines: continue loop */ + if (*bufPtr != ASCII_COMMA) + break; + } + } else + first_search = 1; + + return offset; +} + +/* Get test case information from cmcp threshold file */ +u32 cmcp_get_case_info_from_threshold_file(struct device *dev, const char *buf, + struct test_case_search *search_array, u32 file_size) +{ + u32 case_num = 0, buffer_offset = 0, name_count = 0, first_search = 0; + + dev_vdbg(dev, "%s: Search cmcp threshold file\n", __func__); + + /* Get all the test cases */ + for (case_num = 0; case_num < MAX_CASE_NUM; case_num++) { + buffer_offset = + cmcp_return_offset_of_new_case(&buf[buffer_offset], + first_search); + first_search = 1; + + if (buf[buffer_offset] == 0) + break; + + for (name_count = 0; name_count < NAME_SIZE_MAX; name_count++) { + /* File end */ + if (buf[buffer_offset + name_count] == ASCII_COMMA) + break; + + search_array[case_num].name[name_count] = + buf[buffer_offset + name_count]; + } + + /* Exit when buffer offset is larger than file size */ + if (buffer_offset >= file_size) + break; + + search_array[case_num].name_size = name_count; + search_array[case_num].offset = buffer_offset; + /* + dev_vdbg(dev, + "Find case %d: Name is %s; Name size is %d; Case offset is %d\n", + case_num, + search_array[case_num].name, + search_array[case_num].name_size, + search_array[case_num].offset); + */ + } + + return case_num; +} + +/* Compose one value based on data of each bit */ +int cmcp_compose_data(char *buf, u32 count) +{ + u32 base_array[] = {1, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9}; + int value = 0; + u32 index = 0; + + for (index = 0; index < count; index++) + value += buf[index] * base_array[count - 1 - index]; + + return value; +} + +/* Return one value */ +int cmcp_return_one_value(struct device *dev, + const char *buf, u32 *offset, u32 *line_num) +{ + int value = -1; + char tmp_buffer[10]; + u32 count = 0; + u32 tmp_offset = *offset; + static u32 line_count = 1; + + /* Bypass extra commas */ + while (buf[tmp_offset] == ASCII_COMMA + && buf[tmp_offset + 1] == ASCII_COMMA) + tmp_offset++; + + /* Windows and Linux difference at the end of one line */ + if (buf[tmp_offset] == ASCII_COMMA + && buf[tmp_offset + 1] == ASCII_CR + && buf[tmp_offset + 2] == ASCII_LF) + tmp_offset += 2; + else if (buf[tmp_offset] == ASCII_COMMA + && buf[tmp_offset + 1] == ASCII_LF) + tmp_offset += 1; + + /* New line for multiple lines */ + if (buf[tmp_offset] == ASCII_LF && buf[tmp_offset + 1] == ASCII_COMMA) { + tmp_offset++; + line_count++; + /*dev_vdbg(dev, "\n");*/ + } + + /* Beginning */ + if (buf[tmp_offset] == ASCII_COMMA) { + tmp_offset++; + for (;;) { + if ((buf[tmp_offset] >= ASCII_ZERO) + && (buf[tmp_offset] <= ASCII_NINE)) { + tmp_buffer[count++] = + buf[tmp_offset] - ASCII_ZERO; + tmp_offset++; + } else { + if (count != 0) { + value = cmcp_compose_data(tmp_buffer, + count); + /*dev_vdbg(dev, ",%d", value);*/ + } else { + /* 0 indicates no data available */ + value = -1; + } + break; + } + } + } else { + /* Multiple line: line count */ + *line_num = line_count; + /* Reset for next case */ + line_count = 1; + } + + *offset = tmp_offset; + + return value; +} + +/* Get configuration information */ +void cmcp_get_configuration_info(struct device *dev, + const char *buf, struct test_case_search *search_array, + u32 case_count, struct test_case_field *field_array, + struct configuration *config) +{ + u32 count = 0, sub_count = 0; + u32 exist_or_not = 0; + u32 value_offset = 0; + int retval = 0; + u32 data_num = 0; + u32 line_num = 1; + + dev_vdbg(dev, + "%s: Fill configuration struct per cmcp threshold file\n", + __func__); + + /* Search cases */ + for (count = 0; count < MAX_CASE_NUM; count++) { + exist_or_not = 0; + for (sub_count = 0; sub_count < case_count; sub_count++) { + if (!strncmp(field_array[count].name, + search_array[sub_count].name, + field_array[count].name_size)) { + exist_or_not = 1; + break; + } + } + + field_array[count].exist_or_not = exist_or_not; + + /* Clear data number */ + data_num = 0; + + if (exist_or_not == 1) { + switch (field_array[count].type) { + case TEST_CASE_TYPE_NO: + field_array[count].data_num = 0; + field_array[count].line_num = 1; + break; + case TEST_CASE_TYPE_ONE: + value_offset = search_array[sub_count].offset + + search_array[sub_count].name_size; + *field_array[count].bufptr = + cmcp_return_one_value(dev, buf, + &value_offset, 0); + field_array[count].data_num = 1; + field_array[count].line_num = 1; + break; + case TEST_CASE_TYPE_MUL: + case TEST_CASE_TYPE_MUL_LINES: + line_num = 1; + value_offset = search_array[sub_count].offset + + search_array[sub_count].name_size; + for (;;) { + retval = cmcp_return_one_value(dev, + buf, &value_offset, &line_num); + if (retval >= 0) { + *field_array[count].bufptr++ = + retval; + data_num++; + } else + break; + } + + field_array[count].data_num = data_num; + field_array[count].line_num = line_num; + break; + default: + break; + } + dev_vdbg(dev, + "%s: %s: Data number is %d, line number is %d\n", + __func__, + field_array[count].name, + field_array[count].data_num, + field_array[count].line_num); + } else + dev_vdbg(dev, "%s: !!! %s doesn't exist\n", + __func__, field_array[count].name); + } +} + +/* Get basic information, like tx, rx, button number */ +void cmcp_get_basic_info(struct device *dev, + struct test_case_field *field_array, struct configuration *config) +{ +#define CMCP_DEBUG 0 + u32 tx_num = 0; +#if CMCP_DEBUG + u32 index = 0; +#endif + + config->is_valid_or_not = 1; /* Set to valid by default */ + config->cm_enabled = 0; + config->cp_enabled = 0; + + if (field_array[CM_TEST_INPUTS].exist_or_not) + config->cm_enabled = 1; + if (field_array[CP_TEST_INPUTS].exist_or_not) + config->cp_enabled = 1; + + /* Get basic information only when CM and CP are enabled */ + if (config->cm_enabled && config->cp_enabled) { + dev_vdbg(dev, "%s: Find CM and CP thresholds\n", __func__); + + config->rx_num = + field_array[PER_ELEMENT_MIN_MAX_TABLE_SENSOR].line_num; + tx_num = + (field_array[PER_ELEMENT_MIN_MAX_TABLE_SENSOR].data_num >> 1) + /field_array[PER_ELEMENT_MIN_MAX_TABLE_SENSOR].line_num; + config->tx_num = tx_num; + + config->btn_num = + field_array[PER_ELEMENT_MIN_MAX_TABLE_BUTTON].data_num >> 1; + + config->cm_min_max_table_button_size = + field_array[PER_ELEMENT_MIN_MAX_TABLE_BUTTON].data_num; + config->cm_min_max_table_sensor_size = + field_array[PER_ELEMENT_MIN_MAX_TABLE_SENSOR].data_num; + config->cp_min_max_table_rx_size = + field_array[PER_ELEMENT_MIN_MAX_RX].data_num; + config->cp_min_max_table_tx_size = + field_array[PER_ELEMENT_MIN_MAX_TX].data_num; + config->cm_max_table_gradient_cols_percent_size = + field_array[CM_GRADIENT_CHECK_COL].data_num; + config->cm_max_table_gradient_rows_percent_size = + field_array[CM_GRADIENT_CHECK_ROW].data_num; + config->cp_min_max_table_button_size = + field_array[CP_PER_ELEMENT_MIN_MAX_BUTTON].data_num; + +#if CMCP_DEBUG + dev_vdbg(dev, "%d\n", config->cm_excluding_col_edge); + dev_vdbg(dev, "%d\n", config->cm_excluding_row_edge); + for (index = 0; + index < config->cm_max_table_gradient_cols_percent_size; + index++) + dev_vdbg(dev, "%d\n", + config->cm_max_table_gradient_cols_percent[index]); + for (index = 0; + index < config->cm_max_table_gradient_rows_percent_size; + index++) + dev_vdbg(dev, "%d\n", + config->cm_max_table_gradient_rows_percent[index]); + dev_vdbg(dev, "%d\n", config->cm_range_limit_row); + dev_vdbg(dev, "%d\n", config->cm_range_limit_col); + dev_vdbg(dev, "%d\n", config->cm_min_limit_cal); + dev_vdbg(dev, "%d\n", config->cm_max_limit_cal); + dev_vdbg(dev, "%d\n", config->cm_max_delta_sensor_percent); + dev_vdbg(dev, "%d\n", config->cm_max_delta_button_percent); + for (index = 0; + index < config->cm_min_max_table_button_size; index++) + dev_vdbg(dev, "%d\n", + config->cm_min_max_table_button[index]); + for (index = 0; + index < config->cm_min_max_table_sensor_size; index++) + dev_vdbg(dev, "%d\n", + config->cm_min_max_table_sensor[index]); + dev_vdbg(dev, "%d\n", + config->cp_max_delta_sensor_rx_percent); + dev_vdbg(dev, "%d\n", + config->cp_max_delta_sensor_tx_percent); + dev_vdbg(dev, "%d\n", + config->cp_max_delta_button_percent); + dev_vdbg(dev, "%d\n", + config->min_button); + dev_vdbg(dev, "%d\n", + config->max_button); + + for (index = 0; + index < config->cp_min_max_table_button_size; index++) + dev_vdbg(dev, "%d\n", + config->cp_min_max_table_button[index]); + for (index = 0; + index < config->cp_min_max_table_rx_size; index++) + dev_vdbg(dev, "%d\n", + config->cp_min_max_table_rx[index]); + for (index = 0; + index < config->cp_min_max_table_tx_size; index++) + dev_vdbg(dev, "%d\n", + config->cp_min_max_table_tx[index]); +#endif + /* Invalid mutual data length */ + if ((field_array[PER_ELEMENT_MIN_MAX_TABLE_SENSOR].data_num >> + 1) % field_array[PER_ELEMENT_MIN_MAX_TABLE_SENSOR].line_num) { + config->is_valid_or_not = 0; + dev_vdbg(dev, "Invalid mutual data length\n"); + } + } else { + if (!config->cm_enabled) + dev_vdbg(dev, + "%s: Miss CM thresholds or CM data format is wrong!\n", + __func__); + + if (!config->cp_enabled) + dev_vdbg(dev, + "%s: Miss CP thresholds or CP data format is wrong!\n", + __func__); + + config->rx_num = 0; + config->tx_num = 0; + config->btn_num = 0; + config->is_valid_or_not = 0; + } + + dev_vdbg(dev, + "%s:\n" + "Input file is %s!\n" + "CM test: %s\n" + "CP test: %s\n" + "rx_num is %d\n" + "tx_num is %d\n" + "btn_num is %d\n", + __func__, + config->is_valid_or_not == 1 ? "VALID" : "!!! INVALID !!!", + config->cm_enabled == 1 ? "Found" : "Not found", + config->cp_enabled == 1 ? "Found" : "Not found", + config->rx_num, + config->tx_num, + config->btn_num); +} + +void cmcp_test_case_field_init(struct test_case_field *test_field_array, + struct configuration *configs) +{ + struct test_case_field test_case_field_array[MAX_CASE_NUM] = { + {"CM TEST INPUTS", 14, TEST_CASE_TYPE_NO, + NULL, 0, 0, 0}, + {"CM_EXCLUDING_COL_EDGE", 21, TEST_CASE_TYPE_ONE, + &configs->cm_excluding_col_edge, 0, 0, 0}, + {"CM_EXCLUDING_ROW_EDGE", 21, TEST_CASE_TYPE_ONE, + &configs->cm_excluding_row_edge, 0, 0, 0}, + {"CM_GRADIENT_CHECK_COL", 21, TEST_CASE_TYPE_MUL, + &configs->cm_max_table_gradient_cols_percent[0], + 0, 0, 0}, + {"CM_GRADIENT_CHECK_ROW", 21, TEST_CASE_TYPE_MUL, + &configs->cm_max_table_gradient_rows_percent[0], + 0, 0, 0}, + {"CM_RANGE_LIMIT_ROW", 18, TEST_CASE_TYPE_ONE, + &configs->cm_range_limit_row, 0, 0, 0}, + {"CM_RANGE_LIMIT_COL", 18, TEST_CASE_TYPE_ONE, + &configs->cm_range_limit_col, 0, 0, 0}, + {"CM_MIN_LIMIT_CAL", 16, TEST_CASE_TYPE_ONE, + &configs->cm_min_limit_cal, 0, 0, 0}, + {"CM_MAX_LIMIT_CAL", 16, TEST_CASE_TYPE_ONE, + &configs->cm_max_limit_cal, 0, 0, 0}, + {"CM_MAX_DELTA_SENSOR_PERCENT", 27, TEST_CASE_TYPE_ONE, + &configs->cm_max_delta_sensor_percent, 0, 0, 0}, + {"CM_MAX_DELTA_BUTTON_PERCENT", 27, TEST_CASE_TYPE_ONE, + &configs->cm_max_delta_button_percent, 0, 0, 0}, + {"PER_ELEMENT_MIN_MAX_TABLE_BUTTON", 32, TEST_CASE_TYPE_MUL, + &configs->cm_min_max_table_button[0], 0, 0, 0}, + {"PER_ELEMENT_MIN_MAX_TABLE_SENSOR", 32, + TEST_CASE_TYPE_MUL_LINES, + &configs->cm_min_max_table_sensor[0], 0, 0, 0}, + {"CP TEST INPUTS", 14, TEST_CASE_TYPE_NO, + NULL, 0, 0, 0}, + {"CP_PER_ELEMENT_MIN_MAX_BUTTON", 29, TEST_CASE_TYPE_MUL, + &configs->cp_min_max_table_button[0], 0, 0, 0}, + {"CP_MAX_DELTA_SENSOR_RX_PERCENT", 30, TEST_CASE_TYPE_ONE, + &configs->cp_max_delta_sensor_rx_percent, + 0, 0, 0}, + {"CP_MAX_DELTA_SENSOR_TX_PERCENT", 30, TEST_CASE_TYPE_ONE, + &configs->cp_max_delta_sensor_tx_percent, + 0, 0, 0}, + {"CP_MAX_DELTA_BUTTON_PERCENT", 27, TEST_CASE_TYPE_ONE, + &configs->cp_max_delta_button_percent, 0, 0, 0}, + {"MIN_BUTTON", 10, TEST_CASE_TYPE_ONE, + &configs->min_button, 0, 0, 0}, + {"MAX_BUTTON", 10, TEST_CASE_TYPE_ONE, + &configs->max_button, 0, 0, 0}, + {"PER_ELEMENT_MIN_MAX_RX", 22, TEST_CASE_TYPE_MUL, + &configs->cp_min_max_table_rx[0], 0, 0, 0}, + {"PER_ELEMENT_MIN_MAX_TX", 22, TEST_CASE_TYPE_MUL, + &configs->cp_min_max_table_tx[0], 0, 0, 0}, + }; + + memcpy(test_field_array, test_case_field_array, + sizeof(struct test_case_field) * MAX_CASE_NUM); +} + +static ssize_t cyttsp5_parse_cmcp_threshold_file_common( + struct device *dev, const char *buf, u32 file_size) +{ + struct cyttsp5_device_access_data *dad + = cyttsp5_get_device_access_data(dev); + ssize_t rc = 0; + u32 case_count = 0; + + dev_vdbg(dev, + "%s: Start parsing cmcp threshold file. File size is %d\n", + __func__, file_size); + + cmcp_test_case_field_init(dad->test_field_array, dad->configs); + + /* Get all the cases from .csv file */ + case_count = cmcp_get_case_info_from_threshold_file(dev, + buf, dad->test_search_array, file_size); + + /* Search cases */ + cmcp_get_configuration_info(dev, + buf, + dad->test_search_array, case_count, dad->test_field_array, + dad->configs); + + /* Get basic information */ + cmcp_get_basic_info(dev, dad->test_field_array, dad->configs); + + return rc; +} + +static ssize_t cyttsp5_cmcp_threshold_loading_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct cyttsp5_device_access_data *dad + = cyttsp5_get_device_access_data(dev); + long value; + int rc; + + rc = kstrtol(buf, 10, &value); + if (rc < 0 || value < -1 || value > 1) { + dev_err(dev, "%s: Invalid value\n", __func__); + return size; + } + + mutex_lock(&dad->cmcp_threshold_lock); + + if (value == 1) + dad->cmcp_threshold_loading = true; + else if (value == -1) + dad->cmcp_threshold_loading = false; + else if (value == 0 && dad->cmcp_threshold_loading) { + dad->cmcp_threshold_loading = false; + + if (dad->cmcp_threshold_size == 0) { + dev_err(dev, "%s: No cmcp threshold data\n", __func__); + goto exit_free; + } + + /* Clear test executed flag */ + dad->test_executed = 0; + + cyttsp5_parse_cmcp_threshold_file_common(dev, + &dad->cmcp_threshold_data[0], dad->cmcp_threshold_size); + + /* Mark valid */ + dad->builtin_cmcp_threshold_status = 0; + /* Restore test item to default value when new file input */ + dad->cmcp_test_items = 0; + } + +exit_free: + kfree(dad->cmcp_threshold_data); + dad->cmcp_threshold_data = NULL; + dad->cmcp_threshold_size = 0; + + mutex_unlock(&dad->cmcp_threshold_lock); + + if (rc) + return rc; + + return size; +} + +static DEVICE_ATTR(cmcp_threshold_loading, S_IRUSR | S_IWUSR, + cyttsp5_cmcp_threshold_loading_show, + cyttsp5_cmcp_threshold_loading_store); + +/* +* cmcp threshold data write +*/ +static ssize_t cyttsp5_cmcp_threshold_data_write(struct file *filp, + struct kobject *kobj, struct bin_attribute *bin_attr, + char *buf, loff_t offset, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct cyttsp5_device_access_data *dad + = cyttsp5_get_device_access_data(dev); + u8 *p; + + dev_vdbg(dev, "%s: offset:%lld count:%zu\n", __func__, offset, count); + + mutex_lock(&dad->cmcp_threshold_lock); + + if (!dad->cmcp_threshold_loading) { + mutex_unlock(&dad->cmcp_threshold_lock); + return -ENODEV; + } + + p = krealloc(dad->cmcp_threshold_data, offset + count, GFP_KERNEL); + if (!p) { + kfree(dad->cmcp_threshold_data); + dad->cmcp_threshold_data = NULL; + mutex_unlock(&dad->cmcp_threshold_lock); + return -ENOMEM; + } + dad->cmcp_threshold_data = p; + + memcpy(&dad->cmcp_threshold_data[offset], buf, count); + dad->cmcp_threshold_size += count; + + mutex_unlock(&dad->cmcp_threshold_lock); + + return count; +} + +static struct bin_attribute bin_attr_cmcp_threshold_data = { + .attr = { + .name = "cmcp_threshold_data", + .mode = S_IWUSR, + }, + .size = 0, + .write = cyttsp5_cmcp_threshold_data_write, +}; + +/* + * Suspend scan command + */ +static int cyttsp5_suspend_scan_cmd_(struct device *dev) +{ + int rc; + + rc = cmd->nonhid_cmd->suspend_scanning(dev, 0); + if (rc < 0) + dev_err(dev, "%s: Suspend scan failed r=%d\n", + __func__, rc); + return rc; +} + +/* + * Resume scan command + */ +static int cyttsp5_resume_scan_cmd_(struct device *dev) +{ + int rc; + + rc = cmd->nonhid_cmd->resume_scanning(dev, 0); + if (rc < 0) + dev_err(dev, "%s: Resume scan failed r=%d\n", + __func__, rc); + return rc; +} + +/* + * Execute scan command + */ +static int cyttsp5_exec_scan_cmd_(struct device *dev) +{ + int rc; + + rc = cmd->nonhid_cmd->exec_panel_scan(dev, 0); + if (rc < 0) + dev_err(dev, "%s: Heatmap start scan failed r=%d\n", + __func__, rc); + return rc; +} + +/* + * Retrieve panel data command + */ +static int cyttsp5_ret_scan_data_cmd_(struct device *dev, u16 read_offset, + u16 read_count, u8 data_id, u8 *response, u8 *config, + u16 *actual_read_len, u8 *return_buf) +{ + int rc; + + rc = cmd->nonhid_cmd->retrieve_panel_scan(dev, 0, read_offset, + read_count, data_id, response, config, actual_read_len, + return_buf); + if (rc < 0) + dev_err(dev, "%s: Retrieve scan data failed r=%d\n", + __func__, rc); + return rc; +} + +/* + * Get data structure command + */ +static int cyttsp5_get_data_structure_cmd_(struct device *dev, u16 read_offset, + u16 read_length, u8 data_id, u8 *status, u8 *data_format, + u16 *actual_read_len, u8 *data) +{ + int rc; + + rc = cmd->nonhid_cmd->get_data_structure(dev, 0, read_offset, + read_length, data_id, status, data_format, + actual_read_len, data); + if (rc < 0) + dev_err(dev, "%s: Get data structure failed r=%d\n", + __func__, rc); + return rc; +} + +/* + * Run self test command + */ +static int cyttsp5_run_selftest_cmd_(struct device *dev, u8 test_id, + u8 write_idacs_to_flash, u8 *status, u8 *summary_result, + u8 *results_available) +{ + int rc; + + rc = cmd->nonhid_cmd->run_selftest(dev, 0, test_id, + write_idacs_to_flash, status, summary_result, + results_available); + if (rc < 0) + dev_err(dev, "%s: Run self test failed r=%d\n", + __func__, rc); + return rc; +} + +/* + * Get self test result command + */ +static int cyttsp5_get_selftest_result_cmd_(struct device *dev, + u16 read_offset, u16 read_length, u8 test_id, u8 *status, + u16 *actual_read_len, u8 *data) +{ + int rc; + + rc = cmd->nonhid_cmd->get_selftest_result(dev, 0, read_offset, + read_length, test_id, status, actual_read_len, data); + if (rc < 0) + dev_err(dev, "%s: Get self test result failed r=%d\n", + __func__, rc); + return rc; +} + +/* + * Calibrate IDACs command + */ +static int _cyttsp5_calibrate_idacs_cmd(struct device *dev, + u8 sensing_mode, u8 *status) +{ + int rc; + + rc = cmd->nonhid_cmd->calibrate_idacs(dev, 0, sensing_mode, status); + return rc; +} + +/* + * Initialize Baselines command + */ +static int _cyttsp5_initialize_baselines_cmd(struct device *dev, + u8 sensing_mode, u8 *status) +{ + int rc; + + rc = cmd->nonhid_cmd->initialize_baselines(dev, 0, sensing_mode, + status); + return rc; +} + +static int prepare_print_buffer(int status, u8 *in_buf, int length, + u8 *out_buf, size_t out_buf_size) +{ + int index = 0; + int i; + + index += scnprintf(out_buf, out_buf_size, "status %d\n", status); + + for (i = 0; i < length; i++) { + index += scnprintf(&out_buf[index], out_buf_size - index, + "%02X\n", in_buf[i]); + } + + return index; +} +static ssize_t cyttsp5_run_and_get_selftest_result_noprint(struct device *dev, + char *buf, size_t buf_len, u8 test_id, u16 read_length, + bool get_result_on_pass) +{ + struct cyttsp5_device_access_data *dad + = cyttsp5_get_device_access_data(dev); + int status = STATUS_FAIL; + u8 cmd_status = 0; + u8 summary_result = 0; + u16 act_length = 0; + int length = 0; + int rc; + + mutex_lock(&dad->sysfs_lock); + + pm_runtime_get_sync(dev); + + rc = cmd->request_exclusive(dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(dev, "%s: Error on request exclusive r=%d\n", + __func__, rc); + goto put_pm_runtime; + } + + rc = cyttsp5_suspend_scan_cmd_(dev); + if (rc < 0) { + dev_err(dev, "%s: Error on suspend scan r=%d\n", + __func__, rc); + goto release_exclusive; + } + + rc = cyttsp5_run_selftest_cmd_(dev, test_id, 0, + &cmd_status, &summary_result, NULL); + if (rc < 0) { + dev_err(dev, "%s: Error on run self test for test_id:%d r=%d\n", + __func__, test_id, rc); + goto resume_scan; + } + + /* Form response buffer */ + dad->ic_buf[0] = cmd_status; + dad->ic_buf[1] = summary_result; + + length = 2; + + /* Get data if command status is success */ + if (cmd_status != CY_CMD_STATUS_SUCCESS) + goto status_success; + + /* Get data unless test result is pass */ + if (summary_result == CY_ST_RESULT_PASS && !get_result_on_pass) + goto status_success; + + rc = cyttsp5_get_selftest_result_cmd_(dev, 0, read_length, + test_id, &cmd_status, &act_length, &dad->ic_buf[6]); + if (rc < 0) { + dev_err(dev, "%s: Error on get self test result r=%d\n", + __func__, rc); + goto resume_scan; + } + + dad->ic_buf[2] = cmd_status; + dad->ic_buf[3] = test_id; + dad->ic_buf[4] = LOW_BYTE(act_length); + dad->ic_buf[5] = HI_BYTE(act_length); + + length = 6 + act_length; + +status_success: + status = STATUS_SUCCESS; + +resume_scan: + cyttsp5_resume_scan_cmd_(dev); + +release_exclusive: + cmd->release_exclusive(dev); + +put_pm_runtime: + pm_runtime_put(dev); + mutex_unlock(&dad->sysfs_lock); + + return status; +} + +static ssize_t cyttsp5_run_and_get_selftest_result(struct device *dev, + char *buf, size_t buf_len, u8 test_id, u16 read_length, + bool get_result_on_pass) +{ + struct cyttsp5_device_access_data *dad + = cyttsp5_get_device_access_data(dev); + int status = STATUS_FAIL; + u8 cmd_status = 0; + u8 summary_result = 0; + u16 act_length = 0; + int length = 0; + int size; + int rc; + + mutex_lock(&dad->sysfs_lock); + + pm_runtime_get_sync(dev); + + rc = cmd->request_exclusive(dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(dev, "%s: Error on request exclusive r=%d\n", + __func__, rc); + goto put_pm_runtime; + } + + rc = cyttsp5_suspend_scan_cmd_(dev); + if (rc < 0) { + dev_err(dev, "%s: Error on suspend scan r=%d\n", + __func__, rc); + goto release_exclusive; + } + + rc = cyttsp5_run_selftest_cmd_(dev, test_id, 0, + &cmd_status, &summary_result, NULL); + if (rc < 0) { + dev_err(dev, "%s: Error on run self test for test_id:%d r=%d\n", + __func__, test_id, rc); + goto resume_scan; + } + + /* Form response buffer */ + dad->ic_buf[0] = cmd_status; + dad->ic_buf[1] = summary_result; + + length = 2; + + /* Get data if command status is success */ + if (cmd_status != CY_CMD_STATUS_SUCCESS) + goto status_success; + + /* Get data unless test result is pass */ + if (summary_result == CY_ST_RESULT_PASS && !get_result_on_pass) + goto status_success; + + rc = cyttsp5_get_selftest_result_cmd_(dev, 0, read_length, + test_id, &cmd_status, &act_length, &dad->ic_buf[6]); + if (rc < 0) { + dev_err(dev, "%s: Error on get self test result r=%d\n", + __func__, rc); + goto resume_scan; + } + + dad->ic_buf[2] = cmd_status; + dad->ic_buf[3] = test_id; + dad->ic_buf[4] = LOW_BYTE(act_length); + dad->ic_buf[5] = HI_BYTE(act_length); + + length = 6 + act_length; + +status_success: + status = STATUS_SUCCESS; + +resume_scan: + cyttsp5_resume_scan_cmd_(dev); + +release_exclusive: + cmd->release_exclusive(dev); + +put_pm_runtime: + pm_runtime_put(dev); + + if (status == STATUS_FAIL) + length = 0; + + size = prepare_print_buffer(status, dad->ic_buf, length, buf, buf_len); + + mutex_unlock(&dad->sysfs_lock); + + return size; +} + +struct cyttsp5_device_access_debugfs_data { + struct cyttsp5_device_access_data *dad; + ssize_t pr_buf_len; + u8 pr_buf[3 * CY_MAX_PRBUF_SIZE]; +}; + +static int cyttsp5_device_access_debugfs_open(struct inode *inode, + struct file *filp) +{ + struct cyttsp5_device_access_data *dad = inode->i_private; + struct cyttsp5_device_access_debugfs_data *data; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->dad = dad; + + filp->private_data = data; + + return nonseekable_open(inode, filp); +} + +static int cyttsp5_device_access_debugfs_release(struct inode *inode, + struct file *filp) +{ + kfree(filp->private_data); + + return 0; +} + +#define CY_DEBUGFS_FOPS(_name, _read, _write) \ +static const struct file_operations _name##_debugfs_fops = { \ + .open = cyttsp5_device_access_debugfs_open, \ + .release = cyttsp5_device_access_debugfs_release, \ + .read = _read, \ + .write = _write, \ +} + +static ssize_t panel_scan_debugfs_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct cyttsp5_device_access_debugfs_data *data = filp->private_data; + struct cyttsp5_device_access_data *dad = data->dad; + struct device *dev = dad->dev; + int status = STATUS_FAIL; + u8 config; + u16 actual_read_len; + int length = 0; + u8 element_size = 0; + u8 *buf_offset; + int elem_offset = 0; + int rc; + + if (*ppos) + goto exit; + + + mutex_lock(&dad->sysfs_lock); + + pm_runtime_get_sync(dev); + + rc = cmd->request_exclusive(dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(dev, "%s: Error on request exclusive r=%d\n", + __func__, rc); + goto put_pm_runtime; + } + + rc = cyttsp5_suspend_scan_cmd_(dev); + if (rc < 0) { + dev_err(dev, "%s: Error on suspend scan r=%d\n", + __func__, rc); + goto release_exclusive; + } + + rc = cyttsp5_exec_scan_cmd_(dev); + if (rc < 0) { + dev_err(dev, "%s: Error on execute panel scan r=%d\n", + __func__, rc); + goto resume_scan; + } + + /* Set length to max to read all */ + rc = cyttsp5_ret_scan_data_cmd_(dev, 0, 0xFFFF, + dad->panel_scan_data_id, dad->ic_buf, &config, + &actual_read_len, NULL); + if (rc < 0) { + dev_err(dev, "%s: Error on retrieve panel scan r=%d\n", + __func__, rc); + goto resume_scan; + } + + length = get_unaligned_le16(&dad->ic_buf[0]); + buf_offset = dad->ic_buf + length; + element_size = config & 0x07; + elem_offset = actual_read_len; + while (actual_read_len > 0) { + rc = cyttsp5_ret_scan_data_cmd_(dev, elem_offset, 0xFFFF, + dad->panel_scan_data_id, NULL, &config, + &actual_read_len, buf_offset); + if (rc < 0) + goto resume_scan; + + length += actual_read_len * element_size; + buf_offset = dad->ic_buf + length; + elem_offset += actual_read_len; + } + /* Reconstruct cmd header */ + put_unaligned_le16(length, &dad->ic_buf[0]); + put_unaligned_le16(elem_offset, &dad->ic_buf[7]); + + /* Do not print command header */ + length -= 5; + + status = STATUS_SUCCESS; + +resume_scan: + cyttsp5_resume_scan_cmd_(dev); + +release_exclusive: + cmd->release_exclusive(dev); + +put_pm_runtime: + pm_runtime_put(dev); + + if (status == STATUS_FAIL) + length = 0; + + data->pr_buf_len = prepare_print_buffer(status, &dad->ic_buf[5], + length, data->pr_buf, sizeof(data->pr_buf)); + + mutex_unlock(&dad->sysfs_lock); + +exit: + return simple_read_from_buffer(buf, count, ppos, data->pr_buf, + data->pr_buf_len); +} + +static ssize_t panel_scan_debugfs_write(struct file *filp, + const char __user *buf, size_t count, loff_t *ppos) +{ + struct cyttsp5_device_access_debugfs_data *data = filp->private_data; + struct cyttsp5_device_access_data *dad = data->dad; + ssize_t length; + int rc = 0; + + rc = simple_write_to_buffer(data->pr_buf, sizeof(data->pr_buf), ppos, + buf, count); + if (rc < 0) + return rc; + + count = rc; + + mutex_lock(&dad->sysfs_lock); + + length = cyttsp5_ic_parse_input(dad->dev, data->pr_buf, count, + dad->ic_buf, CY_MAX_PRBUF_SIZE); + + if (length != 1) { + dev_err(dad->dev, "%s: Malformed input\n", __func__); + rc = -EINVAL; + goto exit_unlock; + } + + dad->panel_scan_data_id = dad->ic_buf[0]; + +exit_unlock: + mutex_unlock(&dad->sysfs_lock); + + if (rc) + return rc; + + return count; +} + +CY_DEBUGFS_FOPS(panel_scan, panel_scan_debugfs_read, panel_scan_debugfs_write); + +static ssize_t get_idac_debugfs_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct cyttsp5_device_access_debugfs_data *data = filp->private_data; + struct cyttsp5_device_access_data *dad = data->dad; + struct device *dev = dad->dev; + int status = STATUS_FAIL; + u8 cmd_status = 0; + u8 data_format = 0; + u16 act_length = 0; + int length = 0; + int rc; + + if (*ppos) + goto exit; + + mutex_lock(&dad->sysfs_lock); + + pm_runtime_get_sync(dev); + + rc = cmd->request_exclusive(dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(dev, "%s: Error on request exclusive r=%d\n", + __func__, rc); + goto put_pm_runtime; + } + + rc = cyttsp5_suspend_scan_cmd_(dev); + if (rc < 0) { + dev_err(dev, "%s: Error on suspend scan r=%d\n", + __func__, rc); + goto release_exclusive; + } + + rc = cyttsp5_get_data_structure_cmd_(dev, 0, PIP_CMD_MAX_LENGTH, + dad->get_idac_data_id, &cmd_status, &data_format, + &act_length, &dad->ic_buf[5]); + if (rc < 0) { + dev_err(dev, "%s: Error on get data structure r=%d\n", + __func__, rc); + goto resume_scan; + } + + dad->ic_buf[0] = cmd_status; + dad->ic_buf[1] = dad->get_idac_data_id; + dad->ic_buf[2] = LOW_BYTE(act_length); + dad->ic_buf[3] = HI_BYTE(act_length); + dad->ic_buf[4] = data_format; + + length = 5 + act_length; + + status = STATUS_SUCCESS; + +resume_scan: + cyttsp5_resume_scan_cmd_(dev); + +release_exclusive: + cmd->release_exclusive(dev); + +put_pm_runtime: + pm_runtime_put(dev); + + if (status == STATUS_FAIL) + length = 0; + + data->pr_buf_len = prepare_print_buffer(status, dad->ic_buf, length, + data->pr_buf, sizeof(data->pr_buf)); + + mutex_unlock(&dad->sysfs_lock); + +exit: + return simple_read_from_buffer(buf, count, ppos, data->pr_buf, + data->pr_buf_len); +} + +static ssize_t get_idac_debugfs_write(struct file *filp, + const char __user *buf, size_t count, loff_t *ppos) +{ + struct cyttsp5_device_access_debugfs_data *data = filp->private_data; + struct cyttsp5_device_access_data *dad = data->dad; + ssize_t length; + int rc = 0; + + rc = simple_write_to_buffer(data->pr_buf, sizeof(data->pr_buf), ppos, + buf, count); + if (rc < 0) + return rc; + + count = rc; + + mutex_lock(&dad->sysfs_lock); + + length = cyttsp5_ic_parse_input(dad->dev, data->pr_buf, count, + dad->ic_buf, CY_MAX_PRBUF_SIZE); + if (length != 1) { + dev_err(dad->dev, "%s: Malformed input\n", __func__); + rc = -EINVAL; + goto exit_unlock; + } + + dad->get_idac_data_id = dad->ic_buf[0]; + +exit_unlock: + mutex_unlock(&dad->sysfs_lock); + + if (rc) + return rc; + + return count; +} + +CY_DEBUGFS_FOPS(get_idac, get_idac_debugfs_read, get_idac_debugfs_write); + +static ssize_t calibrate_debugfs_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct cyttsp5_device_access_debugfs_data *data = filp->private_data; + struct cyttsp5_device_access_data *dad = data->dad; + struct device *dev = dad->dev; + int status = STATUS_FAIL; + int length = 0; + int rc; + + if (*ppos) + goto exit; + + mutex_lock(&dad->sysfs_lock); + + pm_runtime_get_sync(dev); + + rc = cmd->request_exclusive(dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(dev, "%s: Error on request exclusive r=%d\n", + __func__, rc); + goto put_pm_runtime; + } + + rc = cyttsp5_suspend_scan_cmd_(dev); + if (rc < 0) { + dev_err(dev, "%s: Error on suspend scan r=%d\n", + __func__, rc); + goto release_exclusive; + } + + rc = _cyttsp5_calibrate_idacs_cmd(dev, dad->calibrate_sensing_mode, + &dad->ic_buf[0]); + if (rc < 0) { + dev_err(dev, "%s: Error on calibrate idacs r=%d\n", + __func__, rc); + goto resume_scan; + } + + length = 1; + + /* Check if baseline initialization is requested */ + if (dad->calibrate_initialize_baselines) { + /* Perform baseline initialization for all modes */ + rc = _cyttsp5_initialize_baselines_cmd(dev, CY_IB_SM_MUTCAP | + CY_IB_SM_SELFCAP | CY_IB_SM_BUTTON, + &dad->ic_buf[length]); + if (rc < 0) { + dev_err(dev, "%s: Error on initialize baselines r=%d\n", + __func__, rc); + goto resume_scan; + } + + length++; + } + + status = STATUS_SUCCESS; + +resume_scan: + cyttsp5_resume_scan_cmd_(dev); + +release_exclusive: + cmd->release_exclusive(dev); + +put_pm_runtime: + pm_runtime_put(dev); + + if (status == STATUS_FAIL) + length = 0; + + data->pr_buf_len = prepare_print_buffer(status, dad->ic_buf, length, + data->pr_buf, sizeof(data->pr_buf)); + + mutex_unlock(&dad->sysfs_lock); + +exit: + return simple_read_from_buffer(buf, count, ppos, data->pr_buf, + data->pr_buf_len); +} + +static ssize_t calibrate_debugfs_write(struct file *filp, + const char __user *buf, size_t count, loff_t *ppos) +{ + struct cyttsp5_device_access_debugfs_data *data = filp->private_data; + struct cyttsp5_device_access_data *dad = data->dad; + ssize_t length; + int rc = 0; + + rc = simple_write_to_buffer(data->pr_buf, sizeof(data->pr_buf), ppos, + buf, count); + if (rc < 0) + return rc; + + count = rc; + + mutex_lock(&dad->sysfs_lock); + + length = cyttsp5_ic_parse_input(dad->dev, data->pr_buf, count, + dad->ic_buf, CY_MAX_PRBUF_SIZE); + if (length != 2) { + dev_err(dad->dev, "%s: Malformed input\n", __func__); + rc = -EINVAL; + goto exit_unlock; + } + + dad->calibrate_sensing_mode = dad->ic_buf[0]; + dad->calibrate_initialize_baselines = dad->ic_buf[1]; + +exit_unlock: + mutex_unlock(&dad->sysfs_lock); + + if (rc) + return rc; + + return count; +} + +CY_DEBUGFS_FOPS(calibrate, calibrate_debugfs_read, calibrate_debugfs_write); + +static ssize_t baseline_debugfs_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct cyttsp5_device_access_debugfs_data *data = filp->private_data; + struct cyttsp5_device_access_data *dad = data->dad; + struct device *dev = dad->dev; + int status = STATUS_FAIL; + int length = 0; + int rc; + + if (*ppos) + goto exit; + + mutex_lock(&dad->sysfs_lock); + + pm_runtime_get_sync(dev); + + rc = cmd->request_exclusive(dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(dev, "%s: Error on request exclusive r=%d\n", + __func__, rc); + goto put_pm_runtime; + } + + rc = cyttsp5_suspend_scan_cmd_(dev); + if (rc < 0) { + dev_err(dev, "%s: Error on suspend scan r=%d\n", + __func__, rc); + goto release_exclusive; + } + + rc = _cyttsp5_initialize_baselines_cmd(dev, dad->baseline_sensing_mode, + &dad->ic_buf[0]); + if (rc < 0) { + dev_err(dev, "%s: Error on initialize baselines r=%d\n", + __func__, rc); + goto resume_scan; + } + + length = 1; + + status = STATUS_SUCCESS; + +resume_scan: + cyttsp5_resume_scan_cmd_(dev); + +release_exclusive: + cmd->release_exclusive(dev); + +put_pm_runtime: + pm_runtime_put(dev); + + if (status == STATUS_FAIL) + length = 0; + + data->pr_buf_len = prepare_print_buffer(status, dad->ic_buf, length, + data->pr_buf, sizeof(data->pr_buf)); + + mutex_unlock(&dad->sysfs_lock); + +exit: + return simple_read_from_buffer(buf, count, ppos, data->pr_buf, + data->pr_buf_len); +} + +static ssize_t baseline_debugfs_write(struct file *filp, + const char __user *buf, size_t count, loff_t *ppos) +{ + struct cyttsp5_device_access_debugfs_data *data = filp->private_data; + struct cyttsp5_device_access_data *dad = data->dad; + ssize_t length; + int rc = 0; + + rc = simple_write_to_buffer(data->pr_buf, sizeof(data->pr_buf), ppos, + buf, count); + if (rc < 0) + return rc; + + count = rc; + + mutex_lock(&dad->sysfs_lock); + + length = cyttsp5_ic_parse_input(dad->dev, buf, count, dad->ic_buf, + CY_MAX_PRBUF_SIZE); + if (length != 1) { + dev_err(dad->dev, "%s: Malformed input\n", __func__); + rc = -EINVAL; + goto exit_unlock; + } + + dad->baseline_sensing_mode = dad->ic_buf[0]; + +exit_unlock: + mutex_unlock(&dad->sysfs_lock); + + if (rc) + return rc; + + return count; +} + +CY_DEBUGFS_FOPS(baseline, baseline_debugfs_read, baseline_debugfs_write); + +static ssize_t auto_shorts_debugfs_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct cyttsp5_device_access_debugfs_data *data = filp->private_data; + + if (!*ppos) + /* Set length to PIP_CMD_MAX_LENGTH to read all */ + data->pr_buf_len = cyttsp5_run_and_get_selftest_result( + data->dad->dev, data->pr_buf, sizeof(data->pr_buf), + CY_ST_ID_AUTOSHORTS, PIP_CMD_MAX_LENGTH, false); + + return simple_read_from_buffer(buf, count, ppos, data->pr_buf, + data->pr_buf_len); +} + +CY_DEBUGFS_FOPS(auto_shorts, auto_shorts_debugfs_read, NULL); + +static ssize_t opens_debugfs_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct cyttsp5_device_access_debugfs_data *data = filp->private_data; + + if (!*ppos) + /* Set length to PIP_CMD_MAX_LENGTH to read all */ + data->pr_buf_len = cyttsp5_run_and_get_selftest_result( + data->dad->dev, data->pr_buf, sizeof(data->pr_buf), + CY_ST_ID_OPENS, PIP_CMD_MAX_LENGTH, false); + + return simple_read_from_buffer(buf, count, ppos, data->pr_buf, + data->pr_buf_len); +} + +CY_DEBUGFS_FOPS(opens, opens_debugfs_read, NULL); + +static ssize_t cm_panel_debugfs_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct cyttsp5_device_access_debugfs_data *data = filp->private_data; + + if (!*ppos) + /* Set length to PIP_CMD_MAX_LENGTH to read all */ + data->pr_buf_len = cyttsp5_run_and_get_selftest_result( + data->dad->dev, data->pr_buf, sizeof(data->pr_buf), + CY_ST_ID_CM_PANEL, PIP_CMD_MAX_LENGTH, true); + + return simple_read_from_buffer(buf, count, ppos, data->pr_buf, + data->pr_buf_len); +} + +CY_DEBUGFS_FOPS(cm_panel, cm_panel_debugfs_read, NULL); + +static ssize_t cp_panel_debugfs_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct cyttsp5_device_access_debugfs_data *data = filp->private_data; + + if (!*ppos) + /* Set length to PIP_CMD_MAX_LENGTH to read all */ + data->pr_buf_len = cyttsp5_run_and_get_selftest_result( + data->dad->dev, data->pr_buf, sizeof(data->pr_buf), + CY_ST_ID_CP_PANEL, PIP_CMD_MAX_LENGTH, true); + + return simple_read_from_buffer(buf, count, ppos, data->pr_buf, + data->pr_buf_len); +} + +CY_DEBUGFS_FOPS(cp_panel, cp_panel_debugfs_read, NULL); + +static ssize_t cm_button_debugfs_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct cyttsp5_device_access_debugfs_data *data = filp->private_data; + + if (!*ppos) + /* Set length to PIP_CMD_MAX_LENGTH to read all */ + data->pr_buf_len = cyttsp5_run_and_get_selftest_result( + data->dad->dev, data->pr_buf, sizeof(data->pr_buf), + CY_ST_ID_CM_BUTTON, PIP_CMD_MAX_LENGTH, true); + + return simple_read_from_buffer(buf, count, ppos, data->pr_buf, + data->pr_buf_len); +} + +CY_DEBUGFS_FOPS(cm_button, cm_button_debugfs_read, NULL); + +static ssize_t cp_button_debugfs_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct cyttsp5_device_access_debugfs_data *data = filp->private_data; + + if (!*ppos) + /* Set length to PIP_CMD_MAX_LENGTH to read all */ + data->pr_buf_len = cyttsp5_run_and_get_selftest_result( + data->dad->dev, data->pr_buf, sizeof(data->pr_buf), + CY_ST_ID_CP_BUTTON, PIP_CMD_MAX_LENGTH, true); + + return simple_read_from_buffer(buf, count, ppos, data->pr_buf, + data->pr_buf_len); +} + +CY_DEBUGFS_FOPS(cp_button, cp_button_debugfs_read, NULL); + +#ifdef TTHE_TUNER_SUPPORT +static ssize_t tthe_get_panel_data_debugfs_read(struct file *filp, + char __user *buf, size_t count, loff_t *ppos) +{ + struct cyttsp5_device_access_data *dad = filp->private_data; + struct device *dev; + u8 config; + u16 actual_read_len; + u16 length = 0; + u8 element_size = 0; + u8 *buf_offset; + u8 *buf_out; + int elem; + int elem_offset = 0; + int print_idx = 0; + int rc; + int rc1; + int i; + + mutex_lock(&dad->debugfs_lock); + dev = dad->dev; + buf_out = dad->tthe_get_panel_data_buf; + if (!buf_out) + goto release_mutex; + + pm_runtime_get_sync(dev); + + rc = cmd->request_exclusive(dev, CY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) + goto put_runtime; + + if (dad->heatmap.scan_start) { + /* To fix CDT206291: avoid multiple scans when + return data is larger than 4096 bytes in one cycle */ + dad->heatmap.scan_start = 0; + + /* Start scan */ + rc = cyttsp5_exec_scan_cmd_(dev); + if (rc < 0) + goto release_exclusive; + } + + elem = dad->heatmap.num_element; + +#if defined(CY_ENABLE_MAX_ELEN) + if (elem > CY_MAX_ELEN) { + rc = cyttsp5_ret_scan_data_cmd_(dev, elem_offset, + CY_MAX_ELEN, dad->heatmap.data_type, dad->ic_buf, + &config, &actual_read_len, NULL); + } else{ + rc = cyttsp5_ret_scan_data_cmd_(dev, elem_offset, elem, + dad->heatmap.data_type, dad->ic_buf, &config, + &actual_read_len, NULL); + } +#else + rc = cyttsp5_ret_scan_data_cmd_(dev, elem_offset, elem, + dad->heatmap.data_type, dad->ic_buf, &config, + &actual_read_len, NULL); +#endif + if (rc < 0) + goto release_exclusive; + + length = get_unaligned_le16(&dad->ic_buf[0]); + buf_offset = dad->ic_buf + length; + + element_size = config & CY_CMD_RET_PANEL_ELMNT_SZ_MASK; + + elem -= actual_read_len; + elem_offset = actual_read_len; + while (elem > 0) { +#ifdef CY_ENABLE_MAX_ELEN + if (elem > CY_MAX_ELEN) { + rc = cyttsp5_ret_scan_data_cmd_(dev, elem_offset, + CY_MAX_ELEN, dad->heatmap.data_type, NULL, &config, + &actual_read_len, buf_offset); + } else{ + rc = cyttsp5_ret_scan_data_cmd_(dev, elem_offset, elem, + dad->heatmap.data_type, NULL, &config, + &actual_read_len, buf_offset); + } +#else + + rc = cyttsp5_ret_scan_data_cmd_(dev, elem_offset, elem, + dad->heatmap.data_type, NULL, &config, + &actual_read_len, buf_offset); +#endif + if (rc < 0) + goto release_exclusive; + + if (!actual_read_len) + break; + + length += actual_read_len * element_size; + buf_offset = dad->ic_buf + length; + elem -= actual_read_len; + elem_offset += actual_read_len; + } + + /* Reconstruct cmd header */ + put_unaligned_le16(length, &dad->ic_buf[0]); + put_unaligned_le16(elem_offset, &dad->ic_buf[7]); + +release_exclusive: + rc1 = cmd->release_exclusive(dev); +put_runtime: + pm_runtime_put(dev); + + if (rc < 0) + goto release_mutex; + + print_idx += scnprintf(buf_out, TTHE_TUNER_MAX_BUF, "CY_DATA:"); + for (i = 0; i < length; i++) + print_idx += scnprintf(buf_out + print_idx, + TTHE_TUNER_MAX_BUF - print_idx, + "%02X ", dad->ic_buf[i]); + print_idx += scnprintf(buf_out + print_idx, + TTHE_TUNER_MAX_BUF - print_idx, + ":(%d bytes)\n", length); + rc = simple_read_from_buffer(buf, count, ppos, buf_out, print_idx); + print_idx = rc; + +release_mutex: + mutex_unlock(&dad->debugfs_lock); + return print_idx; +} + +static ssize_t tthe_get_panel_data_debugfs_write(struct file *filp, + const char __user *buf, size_t count, loff_t *ppos) +{ + struct cyttsp5_device_access_data *dad = filp->private_data; + struct device *dev = dad->dev; + ssize_t length; + int max_read; + u8 *buf_in = dad->tthe_get_panel_data_buf; + int ret; + + mutex_lock(&dad->debugfs_lock); + ret = copy_from_user(buf_in + (*ppos), buf, count); + if (ret) + goto exit; + buf_in[count] = 0; + + length = cyttsp5_ic_parse_input(dev, buf_in, count, dad->ic_buf, + CY_MAX_PRBUF_SIZE); + if (length <= 0) { + dev_err(dev, "%s: %s Group Data store\n", __func__, + "Malformed input for"); + goto exit; + } + + /* update parameter value */ + dad->heatmap.num_element = get_unaligned_le16(&dad->ic_buf[3]); + dad->heatmap.data_type = dad->ic_buf[5]; + + if (dad->ic_buf[6] > 0) + dad->heatmap.scan_start = true; + else + dad->heatmap.scan_start = false; + + /* elem can not be bigger then buffer size */ + max_read = CY_CMD_RET_PANEL_HDR; + max_read += dad->heatmap.num_element * CY_CMD_RET_PANEL_ELMNT_SZ_MAX; + + if (max_read >= CY_MAX_PRBUF_SIZE) { + dad->heatmap.num_element = + (CY_MAX_PRBUF_SIZE - CY_CMD_RET_PANEL_HDR) + / CY_CMD_RET_PANEL_ELMNT_SZ_MAX; + dev_err(dev, "%s: Will get %d element\n", __func__, + dad->heatmap.num_element); + } + +exit: + mutex_unlock(&dad->debugfs_lock); + dev_vdbg(dev, "%s: return count=%zu\n", __func__, count); + return count; +} + +static int tthe_get_panel_data_debugfs_open(struct inode *inode, + struct file *filp) +{ + struct cyttsp5_device_access_data *dad = inode->i_private; + + mutex_lock(&dad->debugfs_lock); + + if (dad->tthe_get_panel_data_is_open) { + mutex_unlock(&dad->debugfs_lock); + return -EBUSY; + } + + filp->private_data = inode->i_private; + + dad->tthe_get_panel_data_is_open = 1; + mutex_unlock(&dad->debugfs_lock); + return 0; +} + +static int tthe_get_panel_data_debugfs_close(struct inode *inode, + struct file *filp) +{ + struct cyttsp5_device_access_data *dad = filp->private_data; + + mutex_lock(&dad->debugfs_lock); + filp->private_data = NULL; + dad->tthe_get_panel_data_is_open = 0; + mutex_unlock(&dad->debugfs_lock); + + return 0; +} + +static const struct file_operations tthe_get_panel_data_fops = { + .open = tthe_get_panel_data_debugfs_open, + .release = tthe_get_panel_data_debugfs_close, + .read = tthe_get_panel_data_debugfs_read, + .write = tthe_get_panel_data_debugfs_write, +}; +#endif + +static int cyttsp5_setup_sysfs(struct device *dev) +{ + struct cyttsp5_device_access_data *dad + = cyttsp5_get_device_access_data(dev); + int rc; + + rc = device_create_file(dev, &dev_attr_command); + if (rc) { + dev_err(dev, "%s: Error, could not create command\n", + __func__); + goto exit; + } + + rc = device_create_file(dev, &dev_attr_status); + if (rc) { + dev_err(dev, "%s: Error, could not create status\n", + __func__); + goto unregister_command; + } + + rc = device_create_file(dev, &dev_attr_response); + if (rc) { + dev_err(dev, "%s: Error, could not create response\n", + __func__); + goto unregister_status; + } + + dad->base_dentry = debugfs_create_dir(dev_name(dev), NULL); + if (IS_ERR_OR_NULL(dad->base_dentry)) { + dev_err(dev, "%s: Error, could not create base directory\n", + __func__); + goto unregister_response; + } + + dad->mfg_test_dentry = debugfs_create_dir("mfg_test", + dad->base_dentry); + if (IS_ERR_OR_NULL(dad->mfg_test_dentry)) { + dev_err(dev, "%s: Error, could not create mfg_test directory\n", + __func__); + goto unregister_base_dir; + } + + if (IS_ERR_OR_NULL(debugfs_create_file("panel_scan", 0600, + dad->mfg_test_dentry, dad, + &panel_scan_debugfs_fops))) { + dev_err(dev, "%s: Error, could not create panel_scan\n", + __func__); + goto unregister_base_dir; + } + + if (IS_ERR_OR_NULL(debugfs_create_file("get_idac", 0600, + dad->mfg_test_dentry, dad, &get_idac_debugfs_fops))) { + dev_err(dev, "%s: Error, could not create get_idac\n", + __func__); + goto unregister_base_dir; + } + + if (IS_ERR_OR_NULL(debugfs_create_file("auto_shorts", 0400, + dad->mfg_test_dentry, dad, + &auto_shorts_debugfs_fops))) { + dev_err(dev, "%s: Error, could not create auto_shorts\n", + __func__); + goto unregister_base_dir; + } + + if (IS_ERR_OR_NULL(debugfs_create_file("opens", 0400, + dad->mfg_test_dentry, dad, &opens_debugfs_fops))) { + dev_err(dev, "%s: Error, could not create opens\n", + __func__); + goto unregister_base_dir; + } + + if (IS_ERR_OR_NULL(debugfs_create_file("calibrate", 0600, + dad->mfg_test_dentry, dad, &calibrate_debugfs_fops))) { + dev_err(dev, "%s: Error, could not create calibrate\n", + __func__); + goto unregister_base_dir; + } + + if (IS_ERR_OR_NULL(debugfs_create_file("baseline", 0600, + dad->mfg_test_dentry, dad, &baseline_debugfs_fops))) { + dev_err(dev, "%s: Error, could not create baseline\n", + __func__); + goto unregister_base_dir; + } + + if (IS_ERR_OR_NULL(debugfs_create_file("cm_panel", 0400, + dad->mfg_test_dentry, dad, &cm_panel_debugfs_fops))) { + dev_err(dev, "%s: Error, could not create cm_panel\n", + __func__); + goto unregister_base_dir; + } + + if (IS_ERR_OR_NULL(debugfs_create_file("cp_panel", 0400, + dad->mfg_test_dentry, dad, &cp_panel_debugfs_fops))) { + dev_err(dev, "%s: Error, could not create cp_panel\n", + __func__); + goto unregister_base_dir; + } + + if (IS_ERR_OR_NULL(debugfs_create_file("cm_button", 0400, + dad->mfg_test_dentry, dad, &cm_button_debugfs_fops))) { + dev_err(dev, "%s: Error, could not create cm_button\n", + __func__); + goto unregister_base_dir; + } + + if (IS_ERR_OR_NULL(debugfs_create_file("cp_button", 0400, + dad->mfg_test_dentry, dad, &cp_button_debugfs_fops))) { + dev_err(dev, "%s: Error, could not create cp_button\n", + __func__); + goto unregister_base_dir; + } + + dad->cmcp_results_debugfs = debugfs_create_file("cmcp_results", 0644, + dad->mfg_test_dentry, dad, &cmcp_results_debugfs_fops); + if (IS_ERR_OR_NULL(dad->cmcp_results_debugfs)) { + dev_err(dev, "%s: Error, could not create cmcp_results\n", + __func__); + dad->cmcp_results_debugfs = NULL; + goto unregister_base_dir; + } + +#ifdef TTHE_TUNER_SUPPORT + dad->tthe_get_panel_data_debugfs = debugfs_create_file( + CYTTSP5_TTHE_TUNER_GET_PANEL_DATA_FILE_NAME, + 0644, NULL, dad, &tthe_get_panel_data_fops); + if (IS_ERR_OR_NULL(dad->tthe_get_panel_data_debugfs)) { + dev_err(dev, "%s: Error, could not create get_panel_data\n", + __func__); + dad->tthe_get_panel_data_debugfs = NULL; + goto unregister_base_dir; + } +#endif + + rc = device_create_file(dev, &dev_attr_cmcp_test); + if (rc) { + dev_err(dev, "%s: Error, could not create cmcp_test\n", + __func__); + goto unregister_base_dir; + } + + rc = device_create_file(dev, &dev_attr_cmcp_threshold_loading); + if (rc) { + dev_err(dev, "%s: Error, could not create cmcp_thresold_loading\n", + __func__); + goto unregister_cmcp_test; + } + + rc = device_create_bin_file(dev, &bin_attr_cmcp_threshold_data); + if (rc) { + dev_err(dev, "%s: Error, could not create cmcp_thresold_data\n", + __func__); + goto unregister_cmcp_thresold_loading; + } + + dad->sysfs_nodes_created = true; + return rc; + +unregister_cmcp_thresold_loading: + device_remove_file(dev, &dev_attr_cmcp_threshold_loading); +unregister_cmcp_test: + device_remove_file(dev, &dev_attr_cmcp_test); +unregister_base_dir: + debugfs_remove_recursive(dad->base_dentry); +unregister_response: + device_remove_file(dev, &dev_attr_response); +unregister_status: + device_remove_file(dev, &dev_attr_status); +unregister_command: + device_remove_file(dev, &dev_attr_command); +exit: + return rc; +} + +static int cyttsp5_setup_sysfs_attention(struct device *dev) +{ + struct cyttsp5_device_access_data *dad + = cyttsp5_get_device_access_data(dev); + int rc = 0; + + dad->si = cmd->request_sysinfo(dev); + if (!dad->si) + return -EINVAL; + + rc = cyttsp5_setup_sysfs(dev); + + cmd->unsubscribe_attention(dev, CY_ATTEN_STARTUP, + CYTTSP5_DEVICE_ACCESS_NAME, cyttsp5_setup_sysfs_attention, + 0); + + return rc; +} + +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_DEVICE_ACCESS_API +int cyttsp5_device_access_user_command(const char *core_name, u16 read_len, + u8 *read_buf, u16 write_len, u8 *write_buf, + u16 *actual_read_len) +{ + struct cyttsp5_core_data *cd; + int rc; + + might_sleep(); + + /* Check parameters */ + if (!read_buf || !write_buf || !actual_read_len) + return -EINVAL; + + if (!core_name) + core_name = CY_DEFAULT_CORE_ID; + + /* Find device */ + cd = cyttsp5_get_core_data((char *)core_name); + if (!cd) { + pr_err("%s: No device.\n", __func__); + return -ENODEV; + } + + pm_runtime_get_sync(cd->dev); + rc = cmd->nonhid_cmd->user_cmd(cd->dev, 1, read_len, read_buf, + write_len, write_buf, actual_read_len); + pm_runtime_put(cd->dev); + + return rc; +} +EXPORT_SYMBOL_GPL(cyttsp5_device_access_user_command); + +struct command_work { + struct work_struct work; + const char *core_name; + u16 read_len; + u8 *read_buf; + u16 write_len; + u8 *write_buf; + + void (*cont)(const char *core_name, u16 read_len, u8 *read_buf, + u16 write_len, u8 *write_buf, u16 actual_read_length, + int rc); +}; + +static void cyttsp5_device_access_user_command_work_func( + struct work_struct *work) +{ + struct command_work *cmd_work = + container_of(work, struct command_work, work); + u16 actual_read_length; + int rc; + + rc = cyttsp5_device_access_user_command(cmd_work->core_name, + cmd_work->read_len, cmd_work->read_buf, + cmd_work->write_len, cmd_work->write_buf, + &actual_read_length); + + if (cmd_work->cont) + cmd_work->cont(cmd_work->core_name, + cmd_work->read_len, cmd_work->read_buf, + cmd_work->write_len, cmd_work->write_buf, + actual_read_length, rc); + + kfree(cmd_work); +} + +int cyttsp5_device_access_user_command_async(const char *core_name, + u16 read_len, u8 *read_buf, u16 write_len, u8 *write_buf, + void (*cont)(const char *core_name, u16 read_len, u8 *read_buf, + u16 write_len, u8 *write_buf, u16 actual_read_length, + int rc)) +{ + struct command_work *cmd_work; + + cmd_work = kzalloc(sizeof(*cmd_work), GFP_ATOMIC); + if (!cmd_work) + return -ENOMEM; + + cmd_work->core_name = core_name; + cmd_work->read_len = read_len; + cmd_work->read_buf = read_buf; + cmd_work->write_len = write_len; + cmd_work->write_buf = write_buf; + cmd_work->cont = cont; + + INIT_WORK(&cmd_work->work, + cyttsp5_device_access_user_command_work_func); + schedule_work(&cmd_work->work); + + return 0; +} +EXPORT_SYMBOL_GPL(cyttsp5_device_access_user_command_async); +#endif + +static void cyttsp5_cmcp_parse_threshold_file(const struct firmware *fw, + void *context) +{ + struct device *dev = context; + struct cyttsp5_device_access_data *dad = + cyttsp5_get_device_access_data(dev); + + if (!fw) { + dev_info(dev, "%s: No builtin cmcp threshold file\n", __func__); + goto exit; + } + + if (!fw->data || !fw->size) { + dev_err(dev, "%s: Invalid builtin cmcp threshold file\n", + __func__); + goto exit; + } + + dev_dbg(dev, "%s: Found cmcp threshold file.\n", __func__); + + cyttsp5_parse_cmcp_threshold_file_common(dev, &fw->data[0], fw->size); + + dad->builtin_cmcp_threshold_status = 0; + complete(&dad->builtin_cmcp_threshold_complete); + return; + +exit: + release_firmware(fw); + + dad->builtin_cmcp_threshold_status = -EINVAL; + complete(&dad->builtin_cmcp_threshold_complete); +} + +static void cyttsp5_parse_cmcp_threshold_builtin( + struct work_struct *cmcp_threshold_update) +{ + struct cyttsp5_device_access_data *dad = + container_of(cmcp_threshold_update, + struct cyttsp5_device_access_data, + cmcp_threshold_update); + struct device *dev = dad->dev; + int retval; + + dad->si = cmd->request_sysinfo(dev); + if (!dad->si) { + dev_err(dev, "%s: Fail get sysinfo pointer from core\n", + __func__); + return; + } + + dev_vdbg(dev, "%s: Enabling cmcp threshold class loader built-in\n", + __func__); + + retval = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG, + CMCP_THRESHOLD_FILE_NAME, dev, GFP_KERNEL, dev, + cyttsp5_cmcp_parse_threshold_file); + if (retval < 0) { + dev_err(dev, "%s: Fail request cmcp threshold class file load\n", + __func__); + goto exit; + } + + /* wait until cmcp threshold upgrade finishes */ + wait_for_completion(&dad->builtin_cmcp_threshold_complete); + + retval = dad->builtin_cmcp_threshold_status; + +exit: + return; +} + +static int cyttsp5_device_access_probe(struct device *dev, void **data) +{ + struct cyttsp5_device_access_data *dad; + struct configuration *configurations; + struct cmcp_data *cmcp_info; + struct result *result; + + int tx_num = MAX_TX_SENSORS; + int rx_num = MAX_RX_SENSORS; + int btn_num = MAX_BUTTONS; + + struct test_case_field *test_case_field_array; + struct test_case_search *test_case_search_array; + int rc = 0; + + dad = kzalloc(sizeof(*dad), GFP_KERNEL); + if (!dad) { + rc = -ENOMEM; + goto cyttsp5_device_access_probe_data_failed; + } + + configurations = + kzalloc(sizeof(*configurations), GFP_KERNEL); + if (!configurations) { + rc = -ENOMEM; + goto cyttsp5_device_access_probe_configs_failed; + } + dad->configs = configurations; + + cmcp_info = kzalloc(sizeof(*cmcp_info), GFP_KERNEL); + if (!cmcp_info) { + rc = -ENOMEM; + goto cyttsp5_device_access_probe_cmcp_info_failed; + } + dad->cmcp_info = cmcp_info; + + cmcp_info->tx_num = tx_num; + cmcp_info->rx_num = rx_num; + cmcp_info->btn_num = btn_num; + + result = kzalloc(sizeof(*result), GFP_KERNEL); + if (!result) { + rc = -ENOMEM; + goto cyttsp5_device_access_probe_result_failed; + } + dad->result = result; + + test_case_field_array = + kzalloc(sizeof(*test_case_field_array) * MAX_CASE_NUM, + GFP_KERNEL); + if (!test_case_field_array) { + rc = -ENOMEM; + goto cyttsp5_device_access_probe_field_array_failed; + } + + test_case_search_array = + kzalloc(sizeof(*test_case_search_array) * MAX_CASE_NUM, + GFP_KERNEL); + if (!test_case_search_array) { + rc = -ENOMEM; + goto cyttsp5_device_access_probe_search_array_failed; + } + + cmcp_info->gd_sensor_col = (struct gd_sensor *) + kzalloc(tx_num * sizeof(struct gd_sensor), GFP_KERNEL); + if (cmcp_info->gd_sensor_col == NULL) + goto cyttsp5_device_access_probe_gd_sensor_col_failed; + + cmcp_info->gd_sensor_row = (struct gd_sensor *) + kzalloc(rx_num * sizeof(struct gd_sensor), GFP_KERNEL); + if (cmcp_info->gd_sensor_row == NULL) + goto cyttsp5_device_access_probe_gd_sensor_row_failed; + + cmcp_info->cm_data_panel = + kzalloc((tx_num * rx_num + 1) * sizeof(int32_t), GFP_KERNEL); + if (cmcp_info->cm_data_panel == NULL) + goto cyttsp5_device_access_probe_cm_data_panel_failed; + + cmcp_info->cp_tx_data_panel = + kzalloc(tx_num * sizeof(int32_t), GFP_KERNEL); + if (cmcp_info->cp_tx_data_panel == NULL) + goto cyttsp5_device_access_probe_cp_tx_data_panel_failed; + + cmcp_info->cp_tx_cal_data_panel = + kzalloc(tx_num * sizeof(int32_t), GFP_KERNEL); + if (cmcp_info->cp_tx_cal_data_panel == NULL) + goto cyttsp5_device_access_probe_cp_tx_cal_data_panel_failed; + + cmcp_info->cp_rx_data_panel = + kzalloc(rx_num * sizeof(int32_t), GFP_KERNEL); + if (cmcp_info->cp_rx_data_panel == NULL) + goto cyttsp5_device_access_probe_cp_rx_data_panel_failed; + + cmcp_info->cp_rx_cal_data_panel = + kzalloc(rx_num * sizeof(int32_t), GFP_KERNEL); + if (cmcp_info->cp_rx_cal_data_panel == NULL) + goto cyttsp5_device_access_probe_cp_rx_cal_data_panel_failed; + + cmcp_info->cm_btn_data = kcalloc(btn_num, sizeof(int32_t), GFP_KERNEL); + if (cmcp_info->cm_btn_data == NULL) + goto cyttsp5_device_access_probe_cm_btn_data_failed; + + cmcp_info->cp_btn_data = kcalloc(btn_num, sizeof(int32_t), GFP_KERNEL); + if (cmcp_info->cp_btn_data == NULL) + goto cyttsp5_device_access_probe_cp_btn_data_failed; + + cmcp_info->cm_sensor_column_delta = + kzalloc(rx_num * tx_num * sizeof(int32_t), GFP_KERNEL); + if (cmcp_info->cm_sensor_column_delta == NULL) + goto cyttsp5_device_access_probe_cm_sensor_column_delta_failed; + + cmcp_info->cm_sensor_row_delta = + kzalloc(tx_num * rx_num * sizeof(int32_t), GFP_KERNEL); + if (cmcp_info->cm_sensor_row_delta == NULL) + goto cyttsp5_device_access_probe_cm_sensor_row_delta_failed; + + mutex_init(&dad->sysfs_lock); + mutex_init(&dad->cmcp_threshold_lock); + dad->dev = dev; +#ifdef TTHE_TUNER_SUPPORT + mutex_init(&dad->debugfs_lock); + dad->heatmap.num_element = 200; +#endif + *data = dad; + + dad->test_field_array = test_case_field_array; + dad->test_search_array = test_case_search_array; + dad->test_executed = 0; + + init_completion(&dad->builtin_cmcp_threshold_complete); + + /* get sysinfo */ + dad->si = cmd->request_sysinfo(dev); + if (dad->si) { + rc = cyttsp5_setup_sysfs(dev); + if (rc) + goto cyttsp5_device_access_setup_sysfs_failed; + } else { + dev_err(dev, "%s: Fail get sysinfo pointer from core p=%p\n", + __func__, dad->si); + cmd->subscribe_attention(dev, CY_ATTEN_STARTUP, + CYTTSP5_DEVICE_ACCESS_NAME, + cyttsp5_setup_sysfs_attention, 0); + } + + INIT_WORK(&dad->cmcp_threshold_update, + cyttsp5_parse_cmcp_threshold_builtin); + schedule_work(&dad->cmcp_threshold_update); + + return 0; + +cyttsp5_device_access_setup_sysfs_failed: + kfree(cmcp_info->cm_sensor_row_delta); +cyttsp5_device_access_probe_cm_sensor_row_delta_failed: + kfree(cmcp_info->cm_sensor_column_delta); +cyttsp5_device_access_probe_cm_sensor_column_delta_failed: + kfree(cmcp_info->cp_btn_data); +cyttsp5_device_access_probe_cp_btn_data_failed: + kfree(cmcp_info->cm_btn_data); +cyttsp5_device_access_probe_cm_btn_data_failed: + kfree(cmcp_info->cp_rx_cal_data_panel); +cyttsp5_device_access_probe_cp_rx_cal_data_panel_failed: + kfree(cmcp_info->cp_rx_data_panel); +cyttsp5_device_access_probe_cp_rx_data_panel_failed: + kfree(cmcp_info->cp_tx_cal_data_panel); +cyttsp5_device_access_probe_cp_tx_cal_data_panel_failed: + kfree(cmcp_info->cp_tx_data_panel); +cyttsp5_device_access_probe_cp_tx_data_panel_failed: + kfree(cmcp_info->cm_data_panel); +cyttsp5_device_access_probe_cm_data_panel_failed: + kfree(cmcp_info->gd_sensor_row); +cyttsp5_device_access_probe_gd_sensor_row_failed: + kfree(cmcp_info->gd_sensor_col); +cyttsp5_device_access_probe_gd_sensor_col_failed: + kfree(test_case_search_array); +cyttsp5_device_access_probe_search_array_failed: + kfree(test_case_field_array); +cyttsp5_device_access_probe_field_array_failed: + kfree(result); +cyttsp5_device_access_probe_result_failed: + kfree(cmcp_info); +cyttsp5_device_access_probe_cmcp_info_failed: + kfree(configurations); +cyttsp5_device_access_probe_configs_failed: + kfree(dad); +cyttsp5_device_access_probe_data_failed: + dev_err(dev, "%s failed.\n", __func__); + return rc; +} + +static void cyttsp5_device_access_release(struct device *dev, void *data) +{ + struct cyttsp5_device_access_data *dad = data; + + if (dad->sysfs_nodes_created) { + device_remove_file(dev, &dev_attr_command); + device_remove_file(dev, &dev_attr_status); + device_remove_file(dev, &dev_attr_response); + debugfs_remove(dad->cmcp_results_debugfs); + debugfs_remove_recursive(dad->base_dentry); +#ifdef TTHE_TUNER_SUPPORT + debugfs_remove(dad->tthe_get_panel_data_debugfs); +#endif + device_remove_file(dev, &dev_attr_cmcp_test); + device_remove_file(dev, &dev_attr_cmcp_threshold_loading); + device_remove_bin_file(dev, &bin_attr_cmcp_threshold_data); + kfree(dad->cmcp_threshold_data); + } else { + cmd->unsubscribe_attention(dev, CY_ATTEN_STARTUP, + CYTTSP5_DEVICE_ACCESS_NAME, + cyttsp5_setup_sysfs_attention, 0); + } + + kfree(dad->test_search_array); + kfree(dad->test_field_array); + kfree(dad->configs); + cyttsp5_free_cmcp_buf(dad->cmcp_info); + kfree(dad->cmcp_info); + kfree(dad->result); + kfree(dad); +} + +static struct cyttsp5_module device_access_module = { + .name = CYTTSP5_DEVICE_ACCESS_NAME, + .probe = cyttsp5_device_access_probe, + .release = cyttsp5_device_access_release, +}; + +static int __init cyttsp5_device_access_init(void) +{ + int rc; + + cmd = cyttsp5_get_commands(); + if (!cmd) + return -EINVAL; + + rc = cyttsp5_register_module(&device_access_module); + if (rc < 0) { + pr_err("%s: Error, failed registering module\n", + __func__); + return rc; + } + + pr_info("%s: Parade TTSP Device Access Driver (Built %s) rc=%d\n", + __func__, CY_DRIVER_VERSION, rc); + return 0; +} +module_init(cyttsp5_device_access_init); + +static void __exit cyttsp5_device_access_exit(void) +{ + cyttsp5_unregister_module(&device_access_module); +} +module_exit(cyttsp5_device_access_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Parade TrueTouch(R) Standard Product Device Access Driver"); +MODULE_AUTHOR("Parade Technologies "); + diff --git a/drivers/input/touchscreen/cyttsp5_devtree.c b/drivers/input/touchscreen/cyttsp5_devtree.c new file mode 100644 index 000000000000..b638a2f6b39a --- /dev/null +++ b/drivers/input/touchscreen/cyttsp5_devtree.c @@ -0,0 +1,750 @@ +/* + * cyttsp5_devtree.c + * Parade TrueTouch(TM) Standard Product V5 Device Tree Support Module. + * For use with Parade touchscreen controllers. + * Supported parts include: + * CYTMA5XX + * CYTMA448 + * CYTMA445A + * CYTT21XXX + * CYTT31XXX + * + * Copyright (C) 2015 Parade Technologies + * Copyright (C) 2013-2015 Cypress Semiconductor + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Contact Parade Technologies at www.paradetech.com + * + */ + +#include +#include +#include +#include +#include + +/* cyttsp */ +#include "cyttsp5_regs.h" +#include + +#define ENABLE_VIRTUAL_KEYS + +#define MAX_NAME_LENGTH 64 + +enum cyttsp5_device_type { + DEVICE_MT, + DEVICE_BTN, + DEVICE_PROXIMITY, + DEVICE_TYPE_MAX, +}; + +struct cyttsp5_device_pdata_func { + void * (*create_and_get_pdata)(struct device_node *); + void (*free_pdata)(void *); +}; + +struct cyttsp5_pdata_ptr { + void **pdata; +}; + +#ifdef ENABLE_VIRTUAL_KEYS +static struct kobject *board_properties_kobj; + +struct cyttsp5_virtual_keys { + struct kobj_attribute kobj_attr; + u16 *data; + int size; +}; +#endif + +struct cyttsp5_extended_mt_platform_data { + struct cyttsp5_mt_platform_data pdata; +#ifdef ENABLE_VIRTUAL_KEYS + struct cyttsp5_virtual_keys vkeys; +#endif +}; + +static inline int get_inp_dev_name(struct device_node *dev_node, + const char **inp_dev_name) +{ + return of_property_read_string(dev_node, "cy,inp_dev_name", + inp_dev_name); +} + +static s16 *create_and_get_u16_array(struct device_node *dev_node, + const char *name, int *size) +{ + const __be32 *values; + s16 *val_array; + int len; + int sz; + int rc; + int i; + + values = of_get_property(dev_node, name, &len); + if (values == NULL) + return NULL; + + sz = len / sizeof(u32); + pr_debug("%s: %s size:%d\n", __func__, name, sz); + + val_array = kcalloc(sz, sizeof(s16), GFP_KERNEL); + if (!val_array) { + rc = -ENOMEM; + goto fail; + } + + for (i = 0; i < sz; i++) + val_array[i] = (s16)be32_to_cpup(values++); + + *size = sz; + + return val_array; + +fail: + return ERR_PTR(rc); +} + +static struct touch_framework *create_and_get_touch_framework( + struct device_node *dev_node) +{ + struct touch_framework *frmwrk; + s16 *abs; + int size; + int rc; + + abs = create_and_get_u16_array(dev_node, "cy,abs", &size); + if (IS_ERR_OR_NULL(abs)) + return (void *)abs; + + /* Check for valid abs size */ + if (size % CY_NUM_ABS_SET) { + rc = -EINVAL; + goto fail_free_abs; + } + + frmwrk = kzalloc(sizeof(*frmwrk), GFP_KERNEL); + if (!frmwrk) { + rc = -ENOMEM; + goto fail_free_abs; + } + + frmwrk->abs = abs; + frmwrk->size = size; + + return frmwrk; + +fail_free_abs: + kfree(abs); + + return ERR_PTR(rc); +} + +static void free_touch_framework(struct touch_framework *frmwrk) +{ + kfree(frmwrk->abs); + kfree(frmwrk); +} + +#ifdef ENABLE_VIRTUAL_KEYS +#define VIRTUAL_KEY_ELEMENT_SIZE 5 +static ssize_t virtual_keys_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct cyttsp5_virtual_keys *vkeys = container_of(attr, + struct cyttsp5_virtual_keys, kobj_attr); + u16 *data = vkeys->data; + int size = vkeys->size; + int index; + int i; + + index = 0; + for (i = 0; i < size; i += VIRTUAL_KEY_ELEMENT_SIZE) + index += scnprintf(buf + index, CY_MAX_PRBUF_SIZE - index, + "0x01:%d:%d:%d:%d:%d\n", + data[i], data[i+1], data[i+2], data[i+3], data[i+4]); + + return index; +} + +static int setup_virtual_keys(struct device_node *dev_node, + const char *inp_dev_name, struct cyttsp5_virtual_keys *vkeys) +{ + char *name; + u16 *data; + int size; + int rc; + + data = create_and_get_u16_array(dev_node, "cy,virtual_keys", &size); + if (data == NULL) + return 0; + else if (IS_ERR(data)) { + rc = PTR_ERR(data); + goto fail; + } + + /* Check for valid virtual keys size */ + if (size % VIRTUAL_KEY_ELEMENT_SIZE) { + rc = -EINVAL; + goto fail_free_data; + } + + name = kzalloc(MAX_NAME_LENGTH, GFP_KERNEL); + if (!name) { + rc = -ENOMEM; + goto fail_free_data; + } + + snprintf(name, MAX_NAME_LENGTH, "virtualkeys.%s", inp_dev_name); + + vkeys->data = data; + vkeys->size = size; + + /* TODO: Instantiate in board file and export it */ + if (board_properties_kobj == NULL) + board_properties_kobj = + kobject_create_and_add("board_properties", NULL); + if (board_properties_kobj == NULL) { + pr_err("%s: Cannot get board_properties kobject!\n", __func__); + rc = -EINVAL; + goto fail_free_name; + } + + /* Initialize dynamic SysFs attribute */ + sysfs_attr_init(&vkeys->kobj_attr.attr); + vkeys->kobj_attr.attr.name = name; + vkeys->kobj_attr.attr.mode = S_IRUGO; + vkeys->kobj_attr.show = virtual_keys_show; + + rc = sysfs_create_file(board_properties_kobj, &vkeys->kobj_attr.attr); + if (rc) + goto fail_del_kobj; + + return 0; + +fail_del_kobj: + kobject_del(board_properties_kobj); +fail_free_name: + kfree(name); + vkeys->kobj_attr.attr.name = NULL; +fail_free_data: + kfree(data); + vkeys->data = NULL; +fail: + return rc; +} + +static void free_virtual_keys(struct cyttsp5_virtual_keys *vkeys) +{ + if (board_properties_kobj) + sysfs_remove_file(board_properties_kobj, + &vkeys->kobj_attr.attr); + + + kobject_del(board_properties_kobj); + board_properties_kobj = NULL; + + kfree(vkeys->data); + kfree(vkeys->kobj_attr.attr.name); +} +#endif + +static void *create_and_get_mt_pdata(struct device_node *dev_node) +{ + struct cyttsp5_extended_mt_platform_data *ext_pdata; + struct cyttsp5_mt_platform_data *pdata; + u32 value; + int rc; + + ext_pdata = kzalloc(sizeof(*ext_pdata), GFP_KERNEL); + if (!ext_pdata) { + rc = -ENOMEM; + goto fail; + } + + pdata = &ext_pdata->pdata; + + rc = get_inp_dev_name(dev_node, &pdata->inp_dev_name); + if (rc) + goto fail_free_pdata; + + /* Optional fields */ + rc = of_property_read_u32(dev_node, "cy,flags", &value); + if (!rc) + pdata->flags = value; + + rc = of_property_read_u32(dev_node, "cy,vkeys_x", &value); + if (!rc) + pdata->vkeys_x = value; + + rc = of_property_read_u32(dev_node, "cy,vkeys_y", &value); + if (!rc) + pdata->vkeys_y = value; + + /* Required fields */ + pdata->frmwrk = create_and_get_touch_framework(dev_node); + if (pdata->frmwrk == NULL) { + rc = -EINVAL; + goto fail_free_pdata; + } else if (IS_ERR(pdata->frmwrk)) { + rc = PTR_ERR(pdata->frmwrk); + goto fail_free_pdata; + } +#ifdef ENABLE_VIRTUAL_KEYS + rc = setup_virtual_keys(dev_node, pdata->inp_dev_name, + &ext_pdata->vkeys); + if (rc) { + pr_err("%s: Cannot setup virtual keys!\n", __func__); + goto fail_free_pdata; + } +#endif + return pdata; + +fail_free_pdata: + kfree(ext_pdata); +fail: + return ERR_PTR(rc); +} + +static void free_mt_pdata(void *pdata) +{ + struct cyttsp5_mt_platform_data *mt_pdata = + (struct cyttsp5_mt_platform_data *)pdata; + struct cyttsp5_extended_mt_platform_data *ext_mt_pdata = + container_of(mt_pdata, + struct cyttsp5_extended_mt_platform_data, pdata); + + free_touch_framework(mt_pdata->frmwrk); +#ifdef ENABLE_VIRTUAL_KEYS + free_virtual_keys(&ext_mt_pdata->vkeys); +#endif + kfree(ext_mt_pdata); +} + +static void *create_and_get_btn_pdata(struct device_node *dev_node) +{ + struct cyttsp5_btn_platform_data *pdata; + int rc; + + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + rc = -ENOMEM; + goto fail; + } + + rc = get_inp_dev_name(dev_node, &pdata->inp_dev_name); + if (rc) + goto fail_free_pdata; + + return pdata; + +fail_free_pdata: + kfree(pdata); +fail: + return ERR_PTR(rc); +} + +static void free_btn_pdata(void *pdata) +{ + struct cyttsp5_btn_platform_data *btn_pdata = + (struct cyttsp5_btn_platform_data *)pdata; + + kfree(btn_pdata); +} + +static void *create_and_get_proximity_pdata(struct device_node *dev_node) +{ + struct cyttsp5_proximity_platform_data *pdata; + int rc; + + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + rc = -ENOMEM; + goto fail; + } + + rc = get_inp_dev_name(dev_node, &pdata->inp_dev_name); + if (rc) + goto fail_free_pdata; + + pdata->frmwrk = create_and_get_touch_framework(dev_node); + if (pdata->frmwrk == NULL) { + rc = -EINVAL; + goto fail_free_pdata; + } else if (IS_ERR(pdata->frmwrk)) { + rc = PTR_ERR(pdata->frmwrk); + goto fail_free_pdata; + } + + return pdata; + +fail_free_pdata: + kfree(pdata); +fail: + return ERR_PTR(rc); +} + +static void free_proximity_pdata(void *pdata) +{ + struct cyttsp5_proximity_platform_data *proximity_pdata = + (struct cyttsp5_proximity_platform_data *)pdata; + + free_touch_framework(proximity_pdata->frmwrk); + + kfree(proximity_pdata); +} + +static struct cyttsp5_device_pdata_func device_pdata_funcs[DEVICE_TYPE_MAX] = { + [DEVICE_MT] = { + .create_and_get_pdata = create_and_get_mt_pdata, + .free_pdata = free_mt_pdata, + }, + [DEVICE_BTN] = { + .create_and_get_pdata = create_and_get_btn_pdata, + .free_pdata = free_btn_pdata, + }, + [DEVICE_PROXIMITY] = { + .create_and_get_pdata = create_and_get_proximity_pdata, + .free_pdata = free_proximity_pdata, + }, +}; + +static struct cyttsp5_pdata_ptr pdata_ptr[DEVICE_TYPE_MAX]; + +static const char *device_names[DEVICE_TYPE_MAX] = { + [DEVICE_MT] = "cy,mt", + [DEVICE_BTN] = "cy,btn", + [DEVICE_PROXIMITY] = "cy,proximity", +}; + +static void set_pdata_ptr(struct cyttsp5_platform_data *pdata) +{ + pdata_ptr[DEVICE_MT].pdata = (void **)&pdata->mt_pdata; + pdata_ptr[DEVICE_BTN].pdata = (void **)&pdata->btn_pdata; + pdata_ptr[DEVICE_PROXIMITY].pdata = (void **)&pdata->prox_pdata; +} + +static int get_device_type(struct device_node *dev_node, + enum cyttsp5_device_type *type) +{ + const char *name; + enum cyttsp5_device_type t; + int rc; + + rc = of_property_read_string(dev_node, "name", &name); + if (rc) + return rc; + + for (t = 0; t < DEVICE_TYPE_MAX; t++) + if (!strncmp(name, device_names[t], MAX_NAME_LENGTH)) { + *type = t; + return 0; + } + + return -EINVAL; +} + +static inline void *create_and_get_device_pdata(struct device_node *dev_node, + enum cyttsp5_device_type type) +{ + return device_pdata_funcs[type].create_and_get_pdata(dev_node); +} + +static inline void free_device_pdata(enum cyttsp5_device_type type) +{ + device_pdata_funcs[type].free_pdata(*pdata_ptr[type].pdata); +} + +static struct touch_settings *create_and_get_touch_setting( + struct device_node *core_node, const char *name) +{ + struct touch_settings *setting; + char *tag_name; + u32 tag_value; + u16 *data; + int size; + int rc; + + data = create_and_get_u16_array(core_node, name, &size); + if (IS_ERR_OR_NULL(data)) + return (void *)data; + + pr_debug("%s: Touch setting:'%s' size:%d\n", __func__, name, size); + + setting = kzalloc(sizeof(*setting), GFP_KERNEL); + if (!setting) { + rc = -ENOMEM; + goto fail_free_data; + } + + setting->data = (u8 *)data; + setting->size = size; + + tag_name = kzalloc(MAX_NAME_LENGTH, GFP_KERNEL); + if (!tag_name) { + rc = -ENOMEM; + goto fail_free_setting; + } + + snprintf(tag_name, MAX_NAME_LENGTH, "%s-tag", name); + + rc = of_property_read_u32(core_node, tag_name, &tag_value); + if (!rc) + setting->tag = tag_value; + + kfree(tag_name); + + return setting; + +fail_free_setting: + kfree(setting); +fail_free_data: + kfree(data); + + return ERR_PTR(rc); +} + +static void free_touch_setting(struct touch_settings *setting) +{ + if (setting) { + kfree(setting->data); + kfree(setting); + } +} + +static char *touch_setting_names[CY_IC_GRPNUM_NUM] = { + NULL, /* CY_IC_GRPNUM_RESERVED */ + "cy,cmd_regs", /* CY_IC_GRPNUM_CMD_REGS */ + "cy,tch_rep", /* CY_IC_GRPNUM_TCH_REP */ + "cy,data_rec", /* CY_IC_GRPNUM_DATA_REC */ + "cy,test_rec", /* CY_IC_GRPNUM_TEST_REC */ + "cy,pcfg_rec", /* CY_IC_GRPNUM_PCFG_REC */ + "cy,tch_parm_val", /* CY_IC_GRPNUM_TCH_PARM_VAL */ + "cy,tch_parm_size", /* CY_IC_GRPNUM_TCH_PARM_SIZE */ + NULL, /* CY_IC_GRPNUM_RESERVED1 */ + NULL, /* CY_IC_GRPNUM_RESERVED2 */ + "cy,opcfg_rec", /* CY_IC_GRPNUM_OPCFG_REC */ + "cy,ddata_rec", /* CY_IC_GRPNUM_DDATA_REC */ + "cy,mdata_rec", /* CY_IC_GRPNUM_MDATA_REC */ + "cy,test_regs", /* CY_IC_GRPNUM_TEST_REGS */ + "cy,btn_keys", /* CY_IC_GRPNUM_BTN_KEYS */ + NULL, /* CY_IC_GRPNUM_TTHE_REGS */ +}; + +static struct cyttsp5_core_platform_data *create_and_get_core_pdata( + struct device_node *core_node) +{ + struct cyttsp5_core_platform_data *pdata; + u32 value; + int rc; + int i; + + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + rc = -ENOMEM; + goto fail; + } + + /* Required fields */ + pdata->irq_gpio = of_get_named_gpio(core_node, "cy,irq_gpio", 0); + if (!gpio_is_valid(pdata->irq_gpio)) { + rc = -ENODEV; + pr_err("Invalid irq_gpio"); + goto fail_free; + } + + rc = of_property_read_u32(core_node, "cy,hid_desc_register", &value); + if (rc) + goto fail_free; + pdata->hid_desc_register = value; + + /* Optional fields */ + /* rst_gpio is optional since a platform may use + * power cycling instead of using the XRES pin + */ + pdata->rst_gpio = of_get_named_gpio(core_node, "cy,rst_gpio", 0); + + rc = of_property_read_u32(core_node, "cy,level_irq_udelay", &value); + if (!rc) + pdata->level_irq_udelay = value; + + rc = of_property_read_u32(core_node, "cy,vendor_id", &value); + if (!rc) + pdata->vendor_id = value; + + rc = of_property_read_u32(core_node, "cy,product_id", &value); + if (!rc) + pdata->product_id = value; + + rc = of_property_read_u32(core_node, "cy,flags", &value); + if (!rc) + pdata->flags = value; + + rc = of_property_read_u32(core_node, "cy,easy_wakeup_gesture", &value); + if (!rc) + pdata->easy_wakeup_gesture = (u8)value; + + pdata->fb_blanking_disabled = of_property_read_bool(core_node, "cy,fb_blanking_disabled"); + + for (i = 0; (unsigned int)i < ARRAY_SIZE(touch_setting_names); i++) { + if (touch_setting_names[i] == NULL) + continue; + + pdata->sett[i] = create_and_get_touch_setting(core_node, + touch_setting_names[i]); + if (IS_ERR(pdata->sett[i])) { + rc = PTR_ERR(pdata->sett[i]); + goto fail_free_sett; + } else if (pdata->sett[i] == NULL) + pr_debug("%s: No data for setting '%s'\n", __func__, + touch_setting_names[i]); + } + + pr_debug("%s: irq_gpio:%d rst_gpio:%d\n" + "hid_desc_register:%d level_irq_udelay:%d vendor_id:%d product_id:%d\n" + "flags:%d easy_wakeup_gesture:%d\n", __func__, + pdata->irq_gpio, pdata->rst_gpio, + pdata->hid_desc_register, + pdata->level_irq_udelay, pdata->vendor_id, pdata->product_id, + pdata->flags, pdata->easy_wakeup_gesture); + + pdata->xres = cyttsp5_xres; + pdata->init = cyttsp5_init; + pdata->power = cyttsp5_power; + pdata->detect = cyttsp5_detect; + pdata->irq_stat = cyttsp5_irq_stat; + + return pdata; + +fail_free_sett: + for (i--; i >= 0; i--) + free_touch_setting(pdata->sett[i]); +fail_free: + kfree(pdata); +fail: + return ERR_PTR(rc); +} + +static void free_core_pdata(void *pdata) +{ + struct cyttsp5_core_platform_data *core_pdata = pdata; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(touch_setting_names); i++) + free_touch_setting(core_pdata->sett[i]); + kfree(core_pdata); +} + +int cyttsp5_devtree_create_and_get_pdata(struct device *adap_dev) +{ + struct cyttsp5_platform_data *pdata; + struct device_node *core_node, *dev_node, *dev_node_fail; + enum cyttsp5_device_type type; + int count = 0; + int rc = 0; + + if (!adap_dev->of_node) + return 0; + + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + adap_dev->platform_data = pdata; + set_pdata_ptr(pdata); + + /* There should be only one core node */ + for_each_child_of_node(adap_dev->of_node, core_node) { + const char *name; + + rc = of_property_read_string(core_node, "name", &name); + if (!rc) + pr_debug("%s: name:%s\n", __func__, name); + + pdata->core_pdata = create_and_get_core_pdata(core_node); + if (IS_ERR(pdata->core_pdata)) { + rc = PTR_ERR(pdata->core_pdata); + break; + } + + /* Increment reference count */ + of_node_get(core_node); + + for_each_child_of_node(core_node, dev_node) { + count++; + rc = get_device_type(dev_node, &type); + if (rc) + break; + *pdata_ptr[type].pdata + = create_and_get_device_pdata(dev_node, type); + if (IS_ERR(*pdata_ptr[type].pdata)) + rc = PTR_ERR(*pdata_ptr[type].pdata); + if (rc) + break; + /* Increment reference count */ + of_node_get(dev_node); + } + + if (rc) { + free_core_pdata(pdata->core_pdata); + of_node_put(core_node); + for_each_child_of_node(core_node, dev_node_fail) { + if (dev_node == dev_node_fail) + break; + rc = get_device_type(dev_node, &type); + if (rc) + break; + free_device_pdata(type); + of_node_put(dev_node); + } + break; + } + pdata->loader_pdata = &_cyttsp5_loader_platform_data; + } + + pr_debug("%s: %d child node(s) found\n", __func__, count); + + return rc; +} +EXPORT_SYMBOL_GPL(cyttsp5_devtree_create_and_get_pdata); + +int cyttsp5_devtree_clean_pdata(struct device *adap_dev) +{ + struct cyttsp5_platform_data *pdata; + struct device_node *core_node, *dev_node; + enum cyttsp5_device_type type; + int rc = 0; + + if (!adap_dev->of_node) + return 0; + + pdata = dev_get_platdata(adap_dev); + set_pdata_ptr(pdata); + for_each_child_of_node(adap_dev->of_node, core_node) { + free_core_pdata(pdata->core_pdata); + of_node_put(core_node); + for_each_child_of_node(core_node, dev_node) { + rc = get_device_type(dev_node, &type); + if (rc) + break; + free_device_pdata(type); + of_node_put(dev_node); + } + } + + return rc; +} +EXPORT_SYMBOL_GPL(cyttsp5_devtree_clean_pdata); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Parade TrueTouch(R) Standard Product DeviceTree Driver"); +MODULE_AUTHOR("Parade Technologies "); diff --git a/drivers/input/touchscreen/cyttsp5_i2c.c b/drivers/input/touchscreen/cyttsp5_i2c.c new file mode 100644 index 000000000000..6376c2d81589 --- /dev/null +++ b/drivers/input/touchscreen/cyttsp5_i2c.c @@ -0,0 +1,221 @@ +/* + * cyttsp5_i2c.c + * Parade TrueTouch(TM) Standard Product V5 I2C Module. + * For use with Parade touchscreen controllers. + * Supported parts include: + * CYTMA5XX + * CYTMA448 + * CYTMA445A + * CYTT21XXX + * CYTT31XXX + * + * Copyright (C) 2015 Parade Technologies + * Copyright (C) 2012-2015 Cypress Semiconductor + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Contact Parade Technologies at www.paradetech.com + * + */ + +#include "cyttsp5_regs.h" + +#include +#include + +#define CY_I2C_DATA_SIZE (2 * 256) + +static int cyttsp5_i2c_read_default(struct device *dev, void *buf, int size) +{ + struct i2c_client *client = to_i2c_client(dev); + int rc; + + if (!buf || !size || size > CY_I2C_DATA_SIZE) + return -EINVAL; + + rc = i2c_master_recv(client, buf, size); + + return (rc < 0) ? rc : rc != size ? -EIO : 0; +} + +static int cyttsp5_i2c_read_default_nosize(struct device *dev, u8 *buf, u32 max) +{ + struct i2c_client *client = to_i2c_client(dev); + struct i2c_msg msgs[2]; + u8 msg_count = 1; + int rc; + u32 size; + + if (!buf) + return -EINVAL; + + msgs[0].addr = client->addr; + msgs[0].flags = (client->flags & I2C_M_TEN) | I2C_M_RD; + msgs[0].len = 2; + msgs[0].buf = buf; + rc = i2c_transfer(client->adapter, msgs, msg_count); + if (rc < 0 || rc != msg_count) + return (rc < 0) ? rc : -EIO; + + size = get_unaligned_le16(&buf[0]); + if (!size || size == 2 || size >= CY_PIP_1P7_EMPTY_BUF) + /* Before PIP 1.7, empty buffer is 0x0002; + From PIP 1.7, empty buffer is 0xFFXX */ + return 0; + + if (size > max) + return -EINVAL; + + rc = i2c_master_recv(client, buf, size); + + return (rc < 0) ? rc : rc != (int)size ? -EIO : 0; +} + +static int cyttsp5_i2c_write_read_specific(struct device *dev, u8 write_len, + u8 *write_buf, u8 *read_buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct i2c_msg msgs[2]; + u8 msg_count = 1; + int rc; + + if (!write_buf || !write_len) + return -EINVAL; + + msgs[0].addr = client->addr; + msgs[0].flags = client->flags & I2C_M_TEN; + msgs[0].len = write_len; + msgs[0].buf = write_buf; + rc = i2c_transfer(client->adapter, msgs, msg_count); + + if (rc < 0 || rc != msg_count) + return (rc < 0) ? rc : -EIO; + + rc = 0; + + if (read_buf) + rc = cyttsp5_i2c_read_default_nosize(dev, read_buf, + CY_I2C_DATA_SIZE); + + return rc; +} + +static struct cyttsp5_bus_ops cyttsp5_i2c_bus_ops = { + .bustype = BUS_I2C, + .read_default = cyttsp5_i2c_read_default, + .read_default_nosize = cyttsp5_i2c_read_default_nosize, + .write_read_specific = cyttsp5_i2c_write_read_specific, +}; + +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_DEVICETREE_SUPPORT +static const struct of_device_id cyttsp5_i2c_of_match[] = { + { .compatible = "cy,cyttsp5_i2c_adapter", }, + { } +}; +MODULE_DEVICE_TABLE(of, cyttsp5_i2c_of_match); +#endif + +static int cyttsp5_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *i2c_id) +{ + struct device *dev = &client->dev; +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_DEVICETREE_SUPPORT + const struct of_device_id *match; +#endif + int rc; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(dev, "I2C functionality not Supported\n"); + return -EIO; + } + +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_DEVICETREE_SUPPORT + match = of_match_device(of_match_ptr(cyttsp5_i2c_of_match), dev); + if (match) { + rc = cyttsp5_devtree_create_and_get_pdata(dev); + if (rc < 0) + return rc; + } +#endif + + rc = cyttsp5_probe(&cyttsp5_i2c_bus_ops, &client->dev, client->irq, + CY_I2C_DATA_SIZE); + +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_DEVICETREE_SUPPORT + if (rc && match) + cyttsp5_devtree_clean_pdata(dev); +#endif + + return rc; +} + +static int cyttsp5_i2c_remove(struct i2c_client *client) +{ +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_DEVICETREE_SUPPORT + struct device *dev = &client->dev; + const struct of_device_id *match; +#endif + struct cyttsp5_core_data *cd = i2c_get_clientdata(client); + + cyttsp5_release(cd); + +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_DEVICETREE_SUPPORT + match = of_match_device(of_match_ptr(cyttsp5_i2c_of_match), dev); + if (match) + cyttsp5_devtree_clean_pdata(dev); +#endif + + return 0; +} + +static const struct i2c_device_id cyttsp5_i2c_id[] = { + { CYTTSP5_I2C_NAME, 0, }, + { } +}; +MODULE_DEVICE_TABLE(i2c, cyttsp5_i2c_id); + +static struct i2c_driver cyttsp5_i2c_driver = { + .driver = { + .name = CYTTSP5_I2C_NAME, + .owner = THIS_MODULE, + .pm = &cyttsp5_pm_ops, +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_DEVICETREE_SUPPORT + .of_match_table = cyttsp5_i2c_of_match, +#endif + }, + .probe = cyttsp5_i2c_probe, + .remove = cyttsp5_i2c_remove, + .id_table = cyttsp5_i2c_id, +}; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 3, 0)) +module_i2c_driver(cyttsp5_i2c_driver); +#else +static int __init cyttsp5_i2c_init(void) +{ + int rc = i2c_add_driver(&cyttsp5_i2c_driver); + + pr_info("%s: Parade TTSP I2C Driver (Built %s) rc=%d\n", + __func__, CY_DRIVER_VERSION, rc); + return rc; +} +module_init(cyttsp5_i2c_init); + +static void __exit cyttsp5_i2c_exit(void) +{ + i2c_del_driver(&cyttsp5_i2c_driver); +} +module_exit(cyttsp5_i2c_exit); +#endif + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Parade TrueTouch(R) Standard Product I2C driver"); +MODULE_AUTHOR("Parade Technologies "); diff --git a/drivers/input/touchscreen/cyttsp5_loader.c b/drivers/input/touchscreen/cyttsp5_loader.c new file mode 100644 index 000000000000..0f2349b134bb --- /dev/null +++ b/drivers/input/touchscreen/cyttsp5_loader.c @@ -0,0 +1,1570 @@ +/* + * cyttsp5_loader.c + * Parade TrueTouch(TM) Standard Product V5 FW Loader Module. + * For use with Parade touchscreen controllers. + * Supported parts include: + * CYTMA5XX + * CYTMA448 + * CYTMA445A + * CYTT21XXX + * CYTT31XXX + * + * Copyright (C) 2015 Parade Technologies + * Copyright (C) 2012-2015 Cypress Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Contact Parade Technologies at www.paradetech.com + * + */ + +#include "cyttsp5_regs.h" +#include + +#define CYTTSP5_LOADER_NAME "cyttsp5_loader" +#define CY_FW_MANUAL_UPGRADE_FILE_NAME "cyttsp5_fw_manual_upgrade" + +/* Enable UPGRADE_FW_AND_CONFIG_IN_PROBE definition + * to perform FW and config upgrade during probe + * instead of scheduling a work for it + */ +/* #define UPGRADE_FW_AND_CONFIG_IN_PROBE */ + +#define CYTTSP5_AUTO_LOAD_FOR_CORRUPTED_FW 1 +#define CYTTSP5_LOADER_FW_UPGRADE_RETRY_COUNT 3 + +#define CYTTSP5_FW_UPGRADE \ + (defined(CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_PLATFORM_FW_UPGRADE) \ + || defined(CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_BINARY_FW_UPGRADE)) + +#define CYTTSP5_TTCONFIG_UPGRADE \ + (defined(CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_PLATFORM_TTCONFIG_UPGRADE) \ + || defined(CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_MANUAL_TTCONFIG_UPGRADE)) + +static const u8 cyttsp5_security_key[] = { + 0xA5, 0x01, 0x02, 0x03, 0xFF, 0xFE, 0xFD, 0x5A +}; + +/* Timeout values in ms. */ +#define CY_LDR_REQUEST_EXCLUSIVE_TIMEOUT 500 +#define CY_LDR_SWITCH_TO_APP_MODE_TIMEOUT 300 + +#define CY_MAX_STATUS_SIZE 32 + +#define CY_DATA_MAX_ROW_SIZE 256 +#define CY_DATA_ROW_SIZE 128 + +#define CY_ARRAY_ID_OFFSET 0 +#define CY_ROW_NUM_OFFSET 1 +#define CY_ROW_SIZE_OFFSET 3 +#define CY_ROW_DATA_OFFSET 5 + +#define CY_POST_TT_CFG_CRC_MASK 0x2 + +struct cyttsp5_loader_data { + struct device *dev; + struct cyttsp5_sysinfo *si; + u8 status_buf[CY_MAX_STATUS_SIZE]; + struct completion int_running; + struct completion calibration_complete; +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_BINARY_FW_UPGRADE + struct completion builtin_bin_fw_complete; + int builtin_bin_fw_status; + bool is_manual_upgrade_enabled; +#endif + struct work_struct fw_and_config_upgrade; + struct work_struct calibration_work; + struct cyttsp5_loader_platform_data *loader_pdata; +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_MANUAL_TTCONFIG_UPGRADE + struct mutex config_lock; + u8 *config_data; + int config_size; + bool config_loading; +#endif +}; + +struct cyttsp5_dev_id { + u32 silicon_id; + u8 rev_id; + u32 bl_ver; +}; + +struct cyttsp5_hex_image { + u8 array_id; + u16 row_num; + u16 row_size; + u8 row_data[CY_DATA_ROW_SIZE]; +} __packed; + +static struct cyttsp5_core_commands *cmd; + +static struct cyttsp5_module loader_module; + +static inline struct cyttsp5_loader_data *cyttsp5_get_loader_data( + struct device *dev) +{ + return cyttsp5_get_module_data(dev, &loader_module); +} + +#if CYTTSP5_FW_UPGRADE \ + || defined(CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_PLATFORM_TTCONFIG_UPGRADE) +static u8 cyttsp5_get_panel_id(struct device *dev) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + return cd->panel_id; +} +#endif + +#if CYTTSP5_FW_UPGRADE || CYTTSP5_TTCONFIG_UPGRADE +/* + * return code: + * -1: Do not upgrade firmware + * 0: Version info same, let caller decide + * 1: Do a firmware upgrade + */ +static int cyttsp5_check_firmware_version(struct device *dev, + u32 fw_ver_new, u32 fw_revctrl_new) +{ + struct cyttsp5_loader_data *ld = cyttsp5_get_loader_data(dev); + u32 fw_ver_img; + u32 fw_revctrl_img; + + fw_ver_img = ld->si->cydata.fw_ver_major << 8; + fw_ver_img += ld->si->cydata.fw_ver_minor; + + dev_dbg(dev, "%s: img vers:0x%04X new vers:0x%04X\n", __func__, + fw_ver_img, fw_ver_new); + + if (fw_ver_new > fw_ver_img) { + dev_dbg(dev, "%s: Image is newer, will upgrade\n", + __func__); + return 1; + } + + if (fw_ver_new < fw_ver_img) { + dev_dbg(dev, "%s: Image is older, will NOT upgrade\n", + __func__); + return -1; + } + + fw_revctrl_img = ld->si->cydata.revctrl; + + dev_dbg(dev, "%s: img revctrl:0x%04X new revctrl:0x%04X\n", + __func__, fw_revctrl_img, fw_revctrl_new); + + if (fw_revctrl_new > fw_revctrl_img) { + dev_dbg(dev, "%s: Image is newer, will upgrade\n", + __func__); + return 1; + } + + if (fw_revctrl_new < fw_revctrl_img) { + dev_dbg(dev, "%s: Image is older, will NOT upgrade\n", + __func__); + return -1; + } + + return 0; +} + +static void cyttsp5_calibrate_idacs(struct work_struct *calibration_work) +{ + struct cyttsp5_loader_data *ld = container_of(calibration_work, + struct cyttsp5_loader_data, calibration_work); + struct device *dev = ld->dev; + u8 mode; + u8 status; + int rc; + + rc = cmd->request_exclusive(dev, CY_LDR_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) + goto exit; + + rc = cmd->nonhid_cmd->suspend_scanning(dev, 0); + if (rc < 0) + goto release; + + for (mode = 0; mode < 3; mode++) { + rc = cmd->nonhid_cmd->calibrate_idacs(dev, 0, mode, &status); + if (rc < 0) + goto release; + } + + rc = cmd->nonhid_cmd->resume_scanning(dev, 0); + if (rc < 0) + goto release; + + dev_dbg(dev, "%s: Calibration Done\n", __func__); + +release: + cmd->release_exclusive(dev); +exit: + complete(&ld->calibration_complete); +} + +static int cyttsp5_calibration_attention(struct device *dev) +{ + struct cyttsp5_loader_data *ld = cyttsp5_get_loader_data(dev); + int rc = 0; + + schedule_work(&ld->calibration_work); + + cmd->unsubscribe_attention(dev, CY_ATTEN_STARTUP, CYTTSP5_LOADER_NAME, + cyttsp5_calibration_attention, 0); + + return rc; +} + + +#endif /* CYTTSP5_FW_UPGRADE || CYTTSP5_TTCONFIG_UPGRADE */ + +#if CYTTSP5_FW_UPGRADE +static u8 *cyttsp5_get_row_(struct device *dev, u8 *row_buf, + u8 *image_buf, int size) +{ + memcpy(row_buf, image_buf, size); + return image_buf + size; +} + +static int cyttsp5_ldr_enter_(struct device *dev, struct cyttsp5_dev_id *dev_id) +{ + int rc; + u8 return_data[8]; + u8 mode; + + dev_id->silicon_id = 0; + dev_id->rev_id = 0; + dev_id->bl_ver = 0; + + cmd->request_reset(dev); + + rc = cmd->request_get_mode(dev, 0, &mode); + if (rc < 0) + return rc; + + if (mode == CY_MODE_UNKNOWN) + return -EINVAL; + + if (mode == CY_MODE_OPERATIONAL) { + rc = cmd->nonhid_cmd->start_bl(dev, 0); + if (rc < 0) + return rc; + } + + rc = cmd->nonhid_cmd->get_bl_info(dev, 0, return_data); + if (rc < 0) + return rc; + + dev_id->silicon_id = get_unaligned_le32(&return_data[0]); + dev_id->rev_id = return_data[4]; + dev_id->bl_ver = return_data[5] + (return_data[6] << 8) + + (return_data[7] << 16); + + return 0; +} + +static int cyttsp5_ldr_init_(struct device *dev, + struct cyttsp5_hex_image *row_image) +{ + return cmd->nonhid_cmd->initiate_bl(dev, 0, 8, + (u8 *)cyttsp5_security_key, row_image->row_size, + row_image->row_data); +} + +static int cyttsp5_ldr_parse_row_(struct device *dev, u8 *row_buf, + struct cyttsp5_hex_image *row_image) +{ + int rc = 0; + + row_image->array_id = row_buf[CY_ARRAY_ID_OFFSET]; + row_image->row_num = get_unaligned_be16(&row_buf[CY_ROW_NUM_OFFSET]); + row_image->row_size = get_unaligned_be16(&row_buf[CY_ROW_SIZE_OFFSET]); + + if (row_image->row_size > ARRAY_SIZE(row_image->row_data)) { + dev_err(dev, "%s: row data buffer overflow\n", __func__); + rc = -EOVERFLOW; + goto cyttsp5_ldr_parse_row_exit; + } + + memcpy(row_image->row_data, &row_buf[CY_ROW_DATA_OFFSET], + row_image->row_size); +cyttsp5_ldr_parse_row_exit: + return rc; +} + +static int cyttsp5_ldr_prog_row_(struct device *dev, + struct cyttsp5_hex_image *row_image) +{ + u16 length = row_image->row_size + 3; + u8 data[3 + row_image->row_size]; + u8 offset = 0; + + data[offset++] = row_image->array_id; + data[offset++] = LOW_BYTE(row_image->row_num); + data[offset++] = HI_BYTE(row_image->row_num); + memcpy(data + 3, row_image->row_data, row_image->row_size); + return cmd->nonhid_cmd->prog_and_verify(dev, 0, length, data); +} + +static int cyttsp5_ldr_verify_chksum_(struct device *dev) +{ + u8 result; + int rc; + + rc = cmd->nonhid_cmd->verify_app_integrity(dev, 0, &result); + if (rc) + return rc; + + /* fail */ + if (result == 0) + return -EINVAL; + + return 0; +} + +static int cyttsp5_ldr_exit_(struct device *dev) +{ + return cmd->nonhid_cmd->launch_app(dev, 0); +} + +static int cyttsp5_load_app_(struct device *dev, const u8 *fw, int fw_size) +{ + struct cyttsp5_dev_id *dev_id; + struct cyttsp5_hex_image *row_image; + u8 *row_buf; + size_t image_rec_size; + size_t row_buf_size = CY_DATA_MAX_ROW_SIZE; + int row_count = 0; + u8 *p; + u8 *last_row; + int rc; + int rc_tmp; + + image_rec_size = sizeof(struct cyttsp5_hex_image); + if (fw_size % image_rec_size != 0) { + dev_err(dev, "%s: Firmware image is misaligned\n", __func__); + rc = -EINVAL; + goto _cyttsp5_load_app_error; + } + + dev_info(dev, "%s: start load app\n", __func__); +#ifdef TTHE_TUNER_SUPPORT + cmd->request_tthe_print(dev, NULL, 0, "start load app"); +#endif + + row_buf = kzalloc(row_buf_size, GFP_KERNEL); + row_image = kzalloc(sizeof(struct cyttsp5_hex_image), GFP_KERNEL); + dev_id = kzalloc(sizeof(struct cyttsp5_dev_id), GFP_KERNEL); + if (!row_buf || !row_image || !dev_id) { + rc = -ENOMEM; + goto _cyttsp5_load_app_exit; + } + + cmd->request_stop_wd(dev); + + dev_info(dev, "%s: Send BL Loader Enter\n", __func__); +#ifdef TTHE_TUNER_SUPPORT + cmd->request_tthe_print(dev, NULL, 0, "Send BL Loader Enter"); +#endif + rc = cyttsp5_ldr_enter_(dev, dev_id); + if (rc) { + dev_err(dev, "%s: Error cannot start Loader (ret=%d)\n", + __func__, rc); + goto _cyttsp5_load_app_exit; + } + dev_vdbg(dev, "%s: dev: silicon id=%08X rev=%02X bl=%08X\n", + __func__, dev_id->silicon_id, + dev_id->rev_id, dev_id->bl_ver); + + /* get last row */ + last_row = (u8 *)fw + fw_size - image_rec_size; + cyttsp5_get_row_(dev, row_buf, last_row, image_rec_size); + cyttsp5_ldr_parse_row_(dev, row_buf, row_image); + + /* initialise bootloader */ + rc = cyttsp5_ldr_init_(dev, row_image); + if (rc) { + dev_err(dev, "%s: Error cannot init Loader (ret=%d)\n", + __func__, rc); + goto _cyttsp5_load_app_exit; + } + + dev_info(dev, "%s: Send BL Loader Blocks\n", __func__); +#ifdef TTHE_TUNER_SUPPORT + cmd->request_tthe_print(dev, NULL, 0, "Send BL Loader Blocks"); +#endif + p = (u8 *)fw; + while (p < last_row) { + /* Get row */ + dev_dbg(dev, "%s: read row=%d\n", __func__, ++row_count); + memset(row_buf, 0, row_buf_size); + p = cyttsp5_get_row_(dev, row_buf, p, image_rec_size); + + /* Parse row */ + dev_vdbg(dev, "%s: p=%p buf=%p buf[0]=%02X\n", + __func__, p, row_buf, row_buf[0]); + rc = cyttsp5_ldr_parse_row_(dev, row_buf, row_image); + dev_vdbg(dev, "%s: array_id=%02X row_num=%04X(%d) row_size=%04X(%d)\n", + __func__, row_image->array_id, + row_image->row_num, row_image->row_num, + row_image->row_size, row_image->row_size); + if (rc) { + dev_err(dev, "%s: Parse Row Error (a=%d r=%d ret=%d\n", + __func__, row_image->array_id, + row_image->row_num, rc); + goto _cyttsp5_load_app_exit; + } else { + dev_vdbg(dev, "%s: Parse Row (a=%d r=%d ret=%d\n", + __func__, row_image->array_id, + row_image->row_num, rc); + } + + /* program row */ + rc = cyttsp5_ldr_prog_row_(dev, row_image); + if (rc) { + dev_err(dev, "%s: Program Row Error (array=%d row=%d ret=%d)\n", + __func__, row_image->array_id, + row_image->row_num, rc); + goto _cyttsp5_load_app_exit; + } + + dev_vdbg(dev, "%s: array=%d row_cnt=%d row_num=%04X\n", + __func__, row_image->array_id, row_count, + row_image->row_num); + } + + /* exit loader */ + dev_info(dev, "%s: Send BL Loader Terminate\n", __func__); +#ifdef TTHE_TUNER_SUPPORT + cmd->request_tthe_print(dev, NULL, 0, "Send BL Loader Terminate"); +#endif + rc = cyttsp5_ldr_exit_(dev); + if (rc) { + dev_err(dev, "%s: Error on exit Loader (ret=%d)\n", + __func__, rc); + + /* verify app checksum */ + rc_tmp = cyttsp5_ldr_verify_chksum_(dev); + if (rc_tmp) + dev_err(dev, "%s: ldr_verify_chksum fail r=%d\n", + __func__, rc_tmp); + else + dev_info(dev, "%s: APP Checksum Verified\n", __func__); + } + +_cyttsp5_load_app_exit: + kfree(row_buf); + kfree(row_image); + kfree(dev_id); +_cyttsp5_load_app_error: + return rc; +} + +static int cyttsp5_upgrade_firmware(struct device *dev, const u8 *fw_img, + int fw_size) +{ + struct cyttsp5_loader_data *ld = cyttsp5_get_loader_data(dev); + int retry = CYTTSP5_LOADER_FW_UPGRADE_RETRY_COUNT; + bool wait_for_calibration_complete = false; + int rc; + + pm_runtime_get_sync(dev); + + rc = cmd->request_exclusive(dev, CY_LDR_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) + goto exit; + + while (retry--) { + rc = cyttsp5_load_app_(dev, fw_img, fw_size); + if (rc < 0) + dev_err(dev, "%s: Firmware update failed rc=%d, retry:%d\n", + __func__, rc, retry); + else + break; + msleep(20); + } + if (rc < 0) { + dev_err(dev, "%s: Firmware update failed with error code %d\n", + __func__, rc); + } else if (ld->loader_pdata && + (ld->loader_pdata->flags + & CY_LOADER_FLAG_CALIBRATE_AFTER_FW_UPGRADE)) { +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0)) + reinit_completion(&ld->calibration_complete); +#else + INIT_COMPLETION(ld->calibration_complete); +#endif + /* set up call back for startup */ + dev_vdbg(dev, "%s: Adding callback for calibration\n", + __func__); + rc = cmd->subscribe_attention(dev, CY_ATTEN_STARTUP, + CYTTSP5_LOADER_NAME, cyttsp5_calibration_attention, 0); + if (rc) { + dev_err(dev, "%s: Failed adding callback for calibration\n", + __func__); + dev_err(dev, "%s: No calibration will be performed\n", + __func__); + rc = 0; + } else + wait_for_calibration_complete = true; + } + + cmd->release_exclusive(dev); + +exit: + if (!rc) + cmd->request_restart(dev, true); + + pm_runtime_put_sync(dev); + + if (wait_for_calibration_complete) + wait_for_completion(&ld->calibration_complete); + + return rc; +} + +static int cyttsp5_loader_attention(struct device *dev) +{ + struct cyttsp5_loader_data *ld = cyttsp5_get_loader_data(dev); + + complete(&ld->int_running); + return 0; +} +#endif /* CYTTSP5_FW_UPGRADE */ + +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_PLATFORM_FW_UPGRADE +static int cyttsp5_check_firmware_version_platform(struct device *dev, + struct cyttsp5_touch_firmware *fw) +{ + struct cyttsp5_loader_data *ld = cyttsp5_get_loader_data(dev); + u32 fw_ver_new; + u32 fw_revctrl_new; + int upgrade; + + if (!ld->si) { + dev_info(dev, "%s: No firmware infomation found, device FW may be corrupted\n", + __func__); + return CYTTSP5_AUTO_LOAD_FOR_CORRUPTED_FW; + } + + fw_ver_new = get_unaligned_be16(fw->ver + 2); + /* 4 middle bytes are not used */ + fw_revctrl_new = get_unaligned_be32(fw->ver + 8); + + upgrade = cyttsp5_check_firmware_version(dev, fw_ver_new, + fw_revctrl_new); + + if (upgrade > 0) + return 1; + + return 0; +} + +static struct cyttsp5_touch_firmware *cyttsp5_get_platform_firmware( + struct device *dev) +{ + struct cyttsp5_loader_data *ld = cyttsp5_get_loader_data(dev); + struct cyttsp5_touch_firmware **fws; + struct cyttsp5_touch_firmware *fw; + u8 panel_id; + + panel_id = cyttsp5_get_panel_id(dev); + if (panel_id == PANEL_ID_NOT_ENABLED) { + dev_dbg(dev, "%s: Panel ID not enabled, using legacy firmware\n", + __func__); + return ld->loader_pdata->fw; + } + + fws = ld->loader_pdata->fws; + if (!fws) { + dev_err(dev, "%s: No firmwares provided\n", __func__); + return NULL; + } + + /* Find FW according to the Panel ID */ + while ((fw = *fws++)) { + if (fw->panel_id == panel_id) { + dev_dbg(dev, "%s: Found matching fw:%p with Panel ID: 0x%02X\n", + __func__, fw, fw->panel_id); + return fw; + } + dev_vdbg(dev, "%s: Found mismatching fw:%p with Panel ID: 0x%02X\n", + __func__, fw, fw->panel_id); + } + + return NULL; +} + +static int upgrade_firmware_from_platform(struct device *dev, + bool forced) +{ + struct cyttsp5_loader_data *ld = cyttsp5_get_loader_data(dev); + struct cyttsp5_touch_firmware *fw; + int rc = -ENODEV; + int upgrade; + + if (!ld->loader_pdata) { + dev_err(dev, "%s: No loader platform data\n", __func__); + return rc; + } + + fw = cyttsp5_get_platform_firmware(dev); + if (!fw || !fw->img || !fw->size) { + dev_err(dev, "%s: No platform firmware\n", __func__); + return rc; + } + + if (!fw->ver || !fw->vsize) { + dev_err(dev, "%s: No platform firmware version\n", + __func__); + return rc; + } + + if (forced) + upgrade = forced; + else + upgrade = cyttsp5_check_firmware_version_platform(dev, fw); + + if (upgrade) + return cyttsp5_upgrade_firmware(dev, fw->img, fw->size); + + return rc; +} +#endif /* CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_PLATFORM_FW_UPGRADE */ + +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_BINARY_FW_UPGRADE +static void _cyttsp5_firmware_cont(const struct firmware *fw, void *context) +{ + struct device *dev = context; + struct cyttsp5_loader_data *ld = cyttsp5_get_loader_data(dev); + u8 header_size = 0; + + if (!fw) + goto cyttsp5_firmware_cont_exit; + + if (!fw->data || !fw->size) { + dev_err(dev, "%s: No firmware received\n", __func__); + goto cyttsp5_firmware_cont_release_exit; + } + + header_size = fw->data[0]; + if (header_size >= (fw->size + 1)) { + dev_err(dev, "%s: Firmware format is invalid\n", __func__); + goto cyttsp5_firmware_cont_release_exit; + } + + cyttsp5_upgrade_firmware(dev, &(fw->data[header_size + 1]), + fw->size - (header_size + 1)); + +cyttsp5_firmware_cont_release_exit: + release_firmware(fw); + +cyttsp5_firmware_cont_exit: + ld->is_manual_upgrade_enabled = 0; +} + +static int cyttsp5_check_firmware_version_builtin(struct device *dev, + const struct firmware *fw) +{ + struct cyttsp5_loader_data *ld = cyttsp5_get_loader_data(dev); + u32 fw_ver_new; + u32 fw_revctrl_new; + int upgrade; + + if (!ld->si) { + dev_info(dev, "%s: No firmware infomation found, device FW may be corrupted\n", + __func__); + return CYTTSP5_AUTO_LOAD_FOR_CORRUPTED_FW; + } + + fw_ver_new = get_unaligned_be16(fw->data + 3); + /* 4 middle bytes are not used */ + fw_revctrl_new = get_unaligned_be32(fw->data + 9); + + upgrade = cyttsp5_check_firmware_version(dev, fw_ver_new, + fw_revctrl_new); + + if (upgrade > 0) + return 1; + + return 0; +} + +static void _cyttsp5_firmware_cont_builtin(const struct firmware *fw, + void *context) +{ + struct device *dev = context; + struct cyttsp5_loader_data *ld = cyttsp5_get_loader_data(dev); + int upgrade; + + if (!fw) { + dev_info(dev, "%s: No builtin firmware\n", __func__); + goto _cyttsp5_firmware_cont_builtin_exit; + } + + if (!fw->data || !fw->size) { + dev_err(dev, "%s: Invalid builtin firmware\n", __func__); + goto _cyttsp5_firmware_cont_builtin_exit; + } + + dev_dbg(dev, "%s: Found firmware\n", __func__); + + upgrade = cyttsp5_check_firmware_version_builtin(dev, fw); + if (upgrade) { + _cyttsp5_firmware_cont(fw, dev); + ld->builtin_bin_fw_status = 0; + complete(&ld->builtin_bin_fw_complete); + return; + } + +_cyttsp5_firmware_cont_builtin_exit: + release_firmware(fw); + + ld->builtin_bin_fw_status = -EINVAL; + complete(&ld->builtin_bin_fw_complete); +} + +static int upgrade_firmware_from_class(struct device *dev) +{ + int retval; + + dev_vdbg(dev, "%s: Enabling firmware class loader\n", __func__); + + retval = request_firmware_nowait(THIS_MODULE, FW_ACTION_NOHOTPLUG, + CY_FW_MANUAL_UPGRADE_FILE_NAME, dev, GFP_KERNEL, dev, + _cyttsp5_firmware_cont); + if (retval < 0) { + dev_err(dev, "%s: Fail request firmware class file load\n", + __func__); + return retval; + } + + return 0; +} + +/* + * Generates binary FW filename as following: + * - Panel ID not enabled: cyttsp5_fw.bin + * - Panel ID enabled: cyttsp5_fw_pidXX.bin + */ +static char *generate_firmware_filename(struct device *dev) +{ + char *filename; + u8 panel_id; + +#define FILENAME_LEN_MAX 64 + filename = kzalloc(FILENAME_LEN_MAX, GFP_KERNEL); + if (!filename) + return NULL; + + panel_id = cyttsp5_get_panel_id(dev); + if (panel_id == PANEL_ID_NOT_ENABLED) + snprintf(filename, FILENAME_LEN_MAX, "%s", CY_FW_FILE_NAME); + else + snprintf(filename, FILENAME_LEN_MAX, "%s_pid%02X%s", + CY_FW_FILE_PREFIX, panel_id, CY_FW_FILE_SUFFIX); + + dev_dbg(dev, "%s: Filename: %s\n", __func__, filename); + + return filename; +} + +static int upgrade_firmware_from_builtin(struct device *dev) +{ + struct cyttsp5_loader_data *ld = cyttsp5_get_loader_data(dev); + char *filename; + int retval; + + dev_vdbg(dev, "%s: Enabling firmware class loader built-in\n", + __func__); + + filename = generate_firmware_filename(dev); + if (!filename) + return -ENOMEM; + + retval = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG, + filename, dev, GFP_KERNEL, dev, + _cyttsp5_firmware_cont_builtin); + if (retval < 0) { + dev_err(dev, "%s: Fail request firmware class file load\n", + __func__); + goto exit; + } + + /* wait until FW binary upgrade finishes */ + wait_for_completion(&ld->builtin_bin_fw_complete); + + retval = ld->builtin_bin_fw_status; + +exit: + kfree(filename); + + return retval; +} +#endif /* CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_BINARY_FW_UPGRADE */ + +#if CYTTSP5_TTCONFIG_UPGRADE +static int cyttsp5_write_config_row_(struct device *dev, u8 ebid, + u16 row_number, u16 row_size, u8 *data) +{ + int rc; + u16 actual_write_len; + + rc = cmd->nonhid_cmd->write_conf_block(dev, 0, row_number, + row_size, ebid, data, (u8 *)cyttsp5_security_key, + &actual_write_len); + if (rc) { + dev_err(dev, "%s: Fail Put EBID=%d row=%d cmd fail r=%d\n", + __func__, ebid, row_number, rc); + return rc; + } + + if (actual_write_len != row_size) { + dev_err(dev, "%s: Fail Put EBID=%d row=%d wrong write size=%d\n", + __func__, ebid, row_number, actual_write_len); + rc = -EINVAL; + } + + return rc; +} + +static int cyttsp5_upgrade_ttconfig(struct device *dev, + const u8 *ttconfig_data, int ttconfig_size) +{ + struct cyttsp5_loader_data *ld = cyttsp5_get_loader_data(dev); + bool wait_for_calibration_complete = false; + u8 ebid = CY_TCH_PARM_EBID; + u16 row_size = CY_DATA_ROW_SIZE; + u16 table_size; + u16 row_count; + u16 residue; + u8 *row_buf; + u8 verify_crc_status; + u16 calculated_crc; + u16 stored_crc; + int rc = 0; + int i; + + table_size = ttconfig_size; + row_count = table_size / row_size; + row_buf = (u8 *)ttconfig_data; + dev_dbg(dev, "%s: size:%d row_size=%d row_count=%d\n", + __func__, table_size, row_size, row_count); + + pm_runtime_get_sync(dev); + + rc = cmd->request_exclusive(dev, CY_LDR_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) + goto exit; + + rc = cmd->nonhid_cmd->suspend_scanning(dev, 0); + if (rc < 0) + goto release; + + for (i = 0; i < row_count; i++) { + dev_dbg(dev, "%s: row=%d size=%d\n", __func__, i, row_size); + rc = cyttsp5_write_config_row_(dev, ebid, i, row_size, + row_buf); + if (rc) { + dev_err(dev, "%s: Fail put row=%d r=%d\n", + __func__, i, rc); + break; + } + row_buf += row_size; + } + if (!rc) { + residue = table_size % row_size; + dev_dbg(dev, "%s: row=%d size=%d\n", __func__, i, residue); + rc = cyttsp5_write_config_row_(dev, ebid, i, residue, + row_buf); + row_count++; + if (rc) + dev_err(dev, "%s: Fail put row=%d r=%d\n", + __func__, i, rc); + } + + if (!rc) + dev_dbg(dev, "%s: TT_CFG updated: rows:%d bytes:%d\n", + __func__, row_count, table_size); + + rc = cmd->nonhid_cmd->verify_config_block_crc(dev, 0, ebid, + &verify_crc_status, &calculated_crc, &stored_crc); + if (rc || verify_crc_status) + dev_err(dev, "%s: CRC Failed, ebid=%d, status=%d, scrc=%X ccrc=%X\n", + __func__, ebid, verify_crc_status, + calculated_crc, stored_crc); + else + dev_dbg(dev, "%s: CRC PASS, ebid=%d, status=%d, scrc=%X ccrc=%X\n", + __func__, ebid, verify_crc_status, + calculated_crc, stored_crc); + + rc = cmd->nonhid_cmd->resume_scanning(dev, 0); + if (rc < 0) + goto release; + + if (ld->loader_pdata && + (ld->loader_pdata->flags + & CY_LOADER_FLAG_CALIBRATE_AFTER_TTCONFIG_UPGRADE)) { +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0)) + reinit_completion(&ld->calibration_complete); +#else + INIT_COMPLETION(ld->calibration_complete); +#endif + /* set up call back for startup */ + dev_vdbg(dev, "%s: Adding callback for calibration\n", + __func__); + rc = cmd->subscribe_attention(dev, CY_ATTEN_STARTUP, + CYTTSP5_LOADER_NAME, cyttsp5_calibration_attention, 0); + if (rc) { + dev_err(dev, "%s: Failed adding callback for calibration\n", + __func__); + dev_err(dev, "%s: No calibration will be performed\n", + __func__); + rc = 0; + } else + wait_for_calibration_complete = true; + } + +release: + cmd->release_exclusive(dev); + +exit: + if (!rc) + cmd->request_restart(dev, true); + + pm_runtime_put_sync(dev); + + if (wait_for_calibration_complete) + wait_for_completion(&ld->calibration_complete); + + return rc; +} +#endif /* CYTTSP5_TTCONFIG_UPGRADE */ + +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_PLATFORM_TTCONFIG_UPGRADE +static int cyttsp5_get_ttconfig_crc(struct device *dev, + const u8 *ttconfig_data, int ttconfig_size, u16 *crc) +{ + u16 crc_loc; + + crc_loc = get_unaligned_le16(&ttconfig_data[2]); + if (ttconfig_size < crc_loc + 2) + return -EINVAL; + + *crc = get_unaligned_le16(&ttconfig_data[crc_loc]); + + return 0; +} + +static int cyttsp5_get_ttconfig_version(struct device *dev, + const u8 *ttconfig_data, int ttconfig_size, u16 *version) +{ + if (ttconfig_size < CY_TTCONFIG_VERSION_OFFSET + + CY_TTCONFIG_VERSION_SIZE) + return -EINVAL; + + *version = get_unaligned_le16( + &ttconfig_data[CY_TTCONFIG_VERSION_OFFSET]); + + return 0; +} + +static int cyttsp5_check_ttconfig_version(struct device *dev, + const u8 *ttconfig_data, int ttconfig_size) +{ + struct cyttsp5_loader_data *ld = cyttsp5_get_loader_data(dev); + u16 cfg_crc_new; + int rc; + + if (!ld->si) + return 0; + + /* Check for config version */ + if (ld->loader_pdata->flags & + CY_LOADER_FLAG_CHECK_TTCONFIG_VERSION) { + u16 cfg_ver_new; + + rc = cyttsp5_get_ttconfig_version(dev, ttconfig_data, + ttconfig_size, &cfg_ver_new); + if (rc) + return 0; + + dev_dbg(dev, "%s: img_ver:0x%04X new_ver:0x%04X\n", + __func__, ld->si->cydata.fw_ver_conf, cfg_ver_new); + + /* Check if config version is newer */ + if (cfg_ver_new > ld->si->cydata.fw_ver_conf) { + dev_dbg(dev, "%s: Config version newer, will upgrade\n", + __func__); + return 1; + } + + dev_dbg(dev, "%s: Config version is identical or older, will NOT upgrade\n", + __func__); + /* Check for config CRC */ + } else { + rc = cyttsp5_get_ttconfig_crc(dev, ttconfig_data, + ttconfig_size, &cfg_crc_new); + if (rc) + return 0; + + dev_dbg(dev, "%s: img_crc:0x%04X new_crc:0x%04X\n", + __func__, ld->si->ttconfig.crc, cfg_crc_new); + + if (cfg_crc_new != ld->si->ttconfig.crc) { + dev_dbg(dev, "%s: Config CRC different, will upgrade\n", + __func__); + return 1; + } + + dev_dbg(dev, "%s: Config CRC equal, will NOT upgrade\n", + __func__); + } + + return 0; +} + +static int cyttsp5_check_ttconfig_version_platform(struct device *dev, + struct cyttsp5_touch_config *ttconfig) +{ + struct cyttsp5_loader_data *ld = cyttsp5_get_loader_data(dev); + u32 fw_ver_config; + u32 fw_revctrl_config; + + if (!ld->si) { + dev_info(dev, "%s: No firmware infomation found, device FW may be corrupted\n", + __func__); + return 0; + } + + fw_ver_config = get_unaligned_be16(ttconfig->fw_ver + 2); + /* 4 middle bytes are not used */ + fw_revctrl_config = get_unaligned_be32(ttconfig->fw_ver + 8); + + /* FW versions should match */ + if (cyttsp5_check_firmware_version(dev, fw_ver_config, + fw_revctrl_config)) { + dev_err(dev, "%s: FW versions mismatch\n", __func__); + return 0; + } + + /* Check PowerOn Self Test, TT_CFG CRC bit */ + if ((ld->si->cydata.post_code & CY_POST_TT_CFG_CRC_MASK) == 0) { + dev_dbg(dev, "%s: POST, TT_CFG failed (%X), will upgrade\n", + __func__, ld->si->cydata.post_code); + return 1; + } + + return cyttsp5_check_ttconfig_version(dev, ttconfig->param_regs->data, + ttconfig->param_regs->size); +} + +static struct cyttsp5_touch_config *cyttsp5_get_platform_ttconfig( + struct device *dev) +{ + struct cyttsp5_loader_data *ld = cyttsp5_get_loader_data(dev); + struct cyttsp5_touch_config **ttconfigs; + struct cyttsp5_touch_config *ttconfig; + u8 panel_id; + + panel_id = cyttsp5_get_panel_id(dev); + if (panel_id == PANEL_ID_NOT_ENABLED) { + /* TODO: Make debug message */ + dev_info(dev, "%s: Panel ID not enabled, using legacy ttconfig\n", + __func__); + return ld->loader_pdata->ttconfig; + } + + ttconfigs = ld->loader_pdata->ttconfigs; + if (!ttconfigs) + return NULL; + + /* Find TT config according to the Panel ID */ + while ((ttconfig = *ttconfigs++)) { + if (ttconfig->panel_id == panel_id) { + /* TODO: Make debug message */ + dev_info(dev, "%s: Found matching ttconfig:%p with Panel ID: 0x%02X\n", + __func__, ttconfig, ttconfig->panel_id); + return ttconfig; + } + dev_vdbg(dev, "%s: Found mismatching ttconfig:%p with Panel ID: 0x%02X\n", + __func__, ttconfig, ttconfig->panel_id); + } + + return NULL; +} + +static int upgrade_ttconfig_from_platform(struct device *dev) +{ + struct cyttsp5_loader_data *ld = cyttsp5_get_loader_data(dev); + struct cyttsp5_touch_config *ttconfig; + struct touch_settings *param_regs; + struct cyttsp5_touch_fw; + int rc = -ENODEV; + int upgrade; + + if (!ld->loader_pdata) { + dev_info(dev, "%s: No loader platform data\n", __func__); + return rc; + } + + ttconfig = cyttsp5_get_platform_ttconfig(dev); + if (!ttconfig) { + dev_info(dev, "%s: No ttconfig data\n", __func__); + return rc; + } + + param_regs = ttconfig->param_regs; + if (!param_regs) { + dev_info(dev, "%s: No touch parameters\n", __func__); + return rc; + } + + if (!param_regs->data || !param_regs->size) { + dev_info(dev, "%s: Invalid touch parameters\n", __func__); + return rc; + } + + if (!ttconfig->fw_ver || !ttconfig->fw_vsize) { + dev_info(dev, "%s: Invalid FW version for touch parameters\n", + __func__); + return rc; + } + + upgrade = cyttsp5_check_ttconfig_version_platform(dev, ttconfig); + if (upgrade) + return cyttsp5_upgrade_ttconfig(dev, param_regs->data, + param_regs->size); + + return rc; +} +#endif /* CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_PLATFORM_TTCONFIG_UPGRADE */ + +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_MANUAL_TTCONFIG_UPGRADE +static ssize_t cyttsp5_config_data_write(struct file *filp, + struct kobject *kobj, struct bin_attribute *bin_attr, + char *buf, loff_t offset, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct cyttsp5_loader_data *data = cyttsp5_get_loader_data(dev); + u8 *p; + + dev_vdbg(dev, "%s: offset:%lld count:%zu\n", __func__, offset, count); + + mutex_lock(&data->config_lock); + + if (!data->config_loading) { + mutex_unlock(&data->config_lock); + return -ENODEV; + } + + p = krealloc(data->config_data, offset + count, GFP_KERNEL); + if (!p) { + kfree(data->config_data); + data->config_data = NULL; + mutex_unlock(&data->config_lock); + return -ENOMEM; + } + data->config_data = p; + + memcpy(&data->config_data[offset], buf, count); + data->config_size += count; + + mutex_unlock(&data->config_lock); + + return count; +} + +static struct bin_attribute bin_attr_config_data = { + .attr = { + .name = "config_data", + .mode = S_IWUSR, + }, + .size = 0, + .write = cyttsp5_config_data_write, +}; + +static ssize_t cyttsp5_config_loading_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cyttsp5_loader_data *ld = cyttsp5_get_loader_data(dev); + bool config_loading; + + mutex_lock(&ld->config_lock); + config_loading = ld->config_loading; + mutex_unlock(&ld->config_lock); + + return sprintf(buf, "%d\n", config_loading); +} + +static int cyttsp5_verify_ttconfig_binary(struct device *dev, + u8 *bin_config_data, int bin_config_size, u8 **start, int *len) +{ + struct cyttsp5_loader_data *ld = cyttsp5_get_loader_data(dev); + int header_size; + u16 config_size; + u32 fw_ver_config; + u32 fw_revctrl_config; + + if (!ld->si) { + dev_err(dev, "%s: No firmware infomation found, device FW may be corrupted\n", + __func__); + return -ENODEV; + } + + /* + * We need 11 bytes for FW version control info and at + * least 6 bytes in config (Length + Max Length + CRC) + */ + header_size = bin_config_data[0] + 1; + if (header_size < 11 || header_size >= bin_config_size - 6) { + dev_err(dev, "%s: Invalid header size %d\n", __func__, + header_size); + return -EINVAL; + } + + fw_ver_config = get_unaligned_be16(&bin_config_data[1]); + /* 4 middle bytes are not used */ + fw_revctrl_config = get_unaligned_be32(&bin_config_data[7]); + + /* FW versions should match */ + if (cyttsp5_check_firmware_version(dev, fw_ver_config, + fw_revctrl_config)) { + dev_err(dev, "%s: FW versions mismatch\n", __func__); + return -EINVAL; + } + + config_size = get_unaligned_le16(&bin_config_data[header_size]); + /* Perform a simple size check (2 bytes for CRC) */ + if (config_size != bin_config_size - header_size - 2) { + dev_err(dev, "%s: Config size invalid\n", __func__); + return -EINVAL; + } + + *start = &bin_config_data[header_size]; + *len = bin_config_size - header_size; + + return 0; +} + +/* + * 1: Start loading TT Config + * 0: End loading TT Config and perform upgrade + *-1: Exit loading + */ +static ssize_t cyttsp5_config_loading_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct cyttsp5_loader_data *ld = cyttsp5_get_loader_data(dev); + long value; + u8 *start; + int length; + int rc; + + rc = kstrtol(buf, 10, &value); + if (rc < 0 || value < -1 || value > 1) { + dev_err(dev, "%s: Invalid value\n", __func__); + return size; + } + + mutex_lock(&ld->config_lock); + + if (value == 1) + ld->config_loading = true; + else if (value == -1) + ld->config_loading = false; + else if (value == 0 && ld->config_loading) { + ld->config_loading = false; + if (ld->config_size == 0) { + dev_err(dev, "%s: No config data\n", __func__); + goto exit_free; + } + + rc = cyttsp5_verify_ttconfig_binary(dev, + ld->config_data, ld->config_size, + &start, &length); + if (rc) + goto exit_free; + + rc = cyttsp5_upgrade_ttconfig(dev, start, length); + } + +exit_free: + kfree(ld->config_data); + ld->config_data = NULL; + ld->config_size = 0; + + mutex_unlock(&ld->config_lock); + + if (rc) + return rc; + + return size; +} + +static DEVICE_ATTR(config_loading, S_IRUSR | S_IWUSR, + cyttsp5_config_loading_show, cyttsp5_config_loading_store); +#endif /* CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_MANUAL_TTCONFIG_UPGRADE */ + +static void cyttsp5_fw_and_config_upgrade( + struct work_struct *fw_and_config_upgrade) +{ + struct cyttsp5_loader_data *ld = container_of(fw_and_config_upgrade, + struct cyttsp5_loader_data, fw_and_config_upgrade); + struct device *dev = ld->dev; + + ld->si = cmd->request_sysinfo(dev); + if (!ld->si) + dev_err(dev, "%s: Fail get sysinfo pointer from core\n", + __func__); +#if !CYTTSP5_FW_UPGRADE + dev_info(dev, "%s: No FW upgrade method selected!\n", __func__); +#endif + +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_PLATFORM_FW_UPGRADE + if (!upgrade_firmware_from_platform(dev, false)) + return; +#endif + +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_BINARY_FW_UPGRADE + if (!upgrade_firmware_from_builtin(dev)) + return; +#endif + +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_PLATFORM_TTCONFIG_UPGRADE + if (!upgrade_ttconfig_from_platform(dev)) + return; +#endif +} + +#if CYTTSP5_FW_UPGRADE +static int cyttsp5_fw_upgrade_cb(struct device *dev) +{ +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_PLATFORM_FW_UPGRADE + if (!upgrade_firmware_from_platform(dev, false)) + return 1; +#endif + +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_BINARY_FW_UPGRADE + if (!upgrade_firmware_from_builtin(dev)) + return 1; +#endif + return 0; +} +#endif + +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_PLATFORM_FW_UPGRADE +static ssize_t cyttsp5_forced_upgrade_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + int rc = upgrade_firmware_from_platform(dev, true); + + if (rc) + return rc; + return size; +} + +static DEVICE_ATTR(forced_upgrade, S_IWUSR, + NULL, cyttsp5_forced_upgrade_store); +#endif + +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_BINARY_FW_UPGRADE +static ssize_t cyttsp5_manual_upgrade_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct cyttsp5_loader_data *ld = cyttsp5_get_loader_data(dev); + int rc; + + if (ld->is_manual_upgrade_enabled) + return -EBUSY; + + ld->is_manual_upgrade_enabled = 1; + + rc = upgrade_firmware_from_class(ld->dev); + + if (rc < 0) + ld->is_manual_upgrade_enabled = 0; + + return size; +} + +static DEVICE_ATTR(manual_upgrade, S_IWUSR, + NULL, cyttsp5_manual_upgrade_store); +#endif + +static int cyttsp5_loader_probe(struct device *dev, void **data) +{ + struct cyttsp5_loader_data *ld; + struct cyttsp5_platform_data *pdata = dev_get_platdata(dev); + int rc; + + if (!pdata || !pdata->loader_pdata) { + dev_err(dev, "%s: Missing platform data\n", __func__); + rc = -ENODEV; + goto error_no_pdata; + } + + ld = kzalloc(sizeof(*ld), GFP_KERNEL); + if (!ld) { + rc = -ENOMEM; + goto error_alloc_data_failed; + } + +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_PLATFORM_FW_UPGRADE + rc = device_create_file(dev, &dev_attr_forced_upgrade); + if (rc) { + dev_err(dev, "%s: Error, could not create forced_upgrade\n", + __func__); + goto error_create_forced_upgrade; + } +#endif + +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_BINARY_FW_UPGRADE + rc = device_create_file(dev, &dev_attr_manual_upgrade); + if (rc) { + dev_err(dev, "%s: Error, could not create manual_upgrade\n", + __func__); + goto error_create_manual_upgrade; + } +#endif + +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_MANUAL_TTCONFIG_UPGRADE + rc = device_create_file(dev, &dev_attr_config_loading); + if (rc) { + dev_err(dev, "%s: Error, could not create config_loading\n", + __func__); + goto error_create_config_loading; + } + + rc = device_create_bin_file(dev, &bin_attr_config_data); + if (rc) { + dev_err(dev, "%s: Error, could not create config_data\n", + __func__); + goto error_create_config_data; + } +#endif + + ld->loader_pdata = pdata->loader_pdata; + ld->dev = dev; + *data = ld; + +#if CYTTSP5_FW_UPGRADE + init_completion(&ld->int_running); +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_BINARY_FW_UPGRADE + init_completion(&ld->builtin_bin_fw_complete); +#endif + cmd->subscribe_attention(dev, CY_ATTEN_IRQ, CYTTSP5_LOADER_NAME, + cyttsp5_loader_attention, CY_MODE_BOOTLOADER); + + cmd->subscribe_attention(dev, CY_ATTEN_LOADER, CYTTSP5_LOADER_NAME, + cyttsp5_fw_upgrade_cb, CY_MODE_UNKNOWN); +#endif +#if CYTTSP5_FW_UPGRADE || CYTTSP5_TTCONFIG_UPGRADE + init_completion(&ld->calibration_complete); + INIT_WORK(&ld->calibration_work, cyttsp5_calibrate_idacs); +#endif +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_MANUAL_TTCONFIG_UPGRADE + mutex_init(&ld->config_lock); +#endif + +#ifdef UPGRADE_FW_AND_CONFIG_IN_PROBE + /* Call FW and config upgrade directly in probe */ + cyttsp5_fw_and_config_upgrade(&ld->fw_and_config_upgrade); +#else + INIT_WORK(&ld->fw_and_config_upgrade, cyttsp5_fw_and_config_upgrade); + schedule_work(&ld->fw_and_config_upgrade); +#endif + + dev_info(dev, "%s: Successful probe %s\n", __func__, dev_name(dev)); + return 0; + +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_MANUAL_TTCONFIG_UPGRADE +error_create_config_data: + device_remove_file(dev, &dev_attr_config_loading); +error_create_config_loading: +#endif +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_BINARY_FW_UPGRADE + device_remove_file(dev, &dev_attr_manual_upgrade); +error_create_manual_upgrade: +#endif +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_PLATFORM_FW_UPGRADE + device_remove_file(dev, &dev_attr_forced_upgrade); +error_create_forced_upgrade: +#endif + kfree(ld); +error_alloc_data_failed: +error_no_pdata: + dev_err(dev, "%s failed.\n", __func__); + return rc; +} + +static void cyttsp5_loader_release(struct device *dev, void *data) +{ + struct cyttsp5_loader_data *ld = (struct cyttsp5_loader_data *)data; + +#if CYTTSP5_FW_UPGRADE + cmd->unsubscribe_attention(dev, CY_ATTEN_IRQ, CYTTSP5_LOADER_NAME, + cyttsp5_loader_attention, CY_MODE_BOOTLOADER); + + cmd->unsubscribe_attention(dev, CY_ATTEN_LOADER, CYTTSP5_LOADER_NAME, + cyttsp5_fw_upgrade_cb, CY_MODE_UNKNOWN); +#endif +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_MANUAL_TTCONFIG_UPGRADE + device_remove_bin_file(dev, &bin_attr_config_data); + device_remove_file(dev, &dev_attr_config_loading); + kfree(ld->config_data); +#endif +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_BINARY_FW_UPGRADE + device_remove_file(dev, &dev_attr_manual_upgrade); +#endif +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_PLATFORM_FW_UPGRADE + device_remove_file(dev, &dev_attr_forced_upgrade); +#endif + kfree(ld); +} + +static struct cyttsp5_module loader_module = { + .name = CYTTSP5_LOADER_NAME, + .probe = cyttsp5_loader_probe, + .release = cyttsp5_loader_release, +}; + +static int __init cyttsp5_loader_init(void) +{ + int rc; + + cmd = cyttsp5_get_commands(); + if (!cmd) + return -EINVAL; + + rc = cyttsp5_register_module(&loader_module); + if (rc < 0) { + pr_err("%s: Error, failed registering module\n", + __func__); + return rc; + } + + pr_info("%s: Parade TTSP FW Loader Driver (Built %s) rc=%d\n", + __func__, CY_DRIVER_VERSION, rc); + return 0; +} +module_init(cyttsp5_loader_init); + +static void __exit cyttsp5_loader_exit(void) +{ + cyttsp5_unregister_module(&loader_module); +} +module_exit(cyttsp5_loader_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Parade TrueTouch(R) Standard Product FW Loader Driver"); +MODULE_AUTHOR("Parade Technologies "); diff --git a/drivers/input/touchscreen/cyttsp5_mt_common.c b/drivers/input/touchscreen/cyttsp5_mt_common.c new file mode 100644 index 000000000000..96ab50b45d59 --- /dev/null +++ b/drivers/input/touchscreen/cyttsp5_mt_common.c @@ -0,0 +1,758 @@ +/* + * cyttsp5_mt_common.c + * Parade TrueTouch(TM) Standard Product V5 Multi-Touch Reports Module. + * For use with Parade touchscreen controllers. + * Supported parts include: + * CYTMA5XX + * CYTMA448 + * CYTMA445A + * CYTT21XXX + * CYTT31XXX + * + * Copyright (C) 2015 Parade Technologies + * Copyright (C) 2012-2015 Cypress Semiconductor + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Contact Parade Technologies at www.paradetech.com + * + */ + +#include "cyttsp5_regs.h" + +#define CYTTSP5_MT_NAME "cyttsp5_mt" + +#define MT_PARAM_SIGNAL(md, sig_ost) PARAM_SIGNAL(md->pdata->frmwrk, sig_ost) +#define MT_PARAM_MIN(md, sig_ost) PARAM_MIN(md->pdata->frmwrk, sig_ost) +#define MT_PARAM_MAX(md, sig_ost) PARAM_MAX(md->pdata->frmwrk, sig_ost) +#define MT_PARAM_FUZZ(md, sig_ost) PARAM_FUZZ(md->pdata->frmwrk, sig_ost) +#define MT_PARAM_FLAT(md, sig_ost) PARAM_FLAT(md->pdata->frmwrk, sig_ost) + +void cyttsp5_pr_buf_debug(struct device *dev, u8 *dptr, int size, + const char *data_name) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + u8 *pr_buf = cd->pr_buf; + int i, k; + const char fmt[] = "%02X "; + int max; + + if (!size) + return; + + max = (CY_MAX_PRBUF_SIZE - 1) - sizeof(CY_PR_TRUNCATED); + + pr_buf[0] = 0; + for (i = k = 0; i < size && k < max; i++, k += 3) + scnprintf(pr_buf + k, CY_MAX_PRBUF_SIZE, fmt, dptr[i]); + + if (size) + dev_err(dev, "%s: %s[0..%d]=%s%s\n", __func__, data_name, + size - 1, pr_buf, size <= max ? "" : CY_PR_TRUNCATED); + else + dev_err(dev, "%s: %s[]\n", __func__, data_name); +} + +static void cyttsp5_mt_lift_all(struct cyttsp5_mt_data *md) +{ + int max = md->si->tch_abs[CY_TCH_T].max; + + if (md->num_prv_rec != 0) { + if (md->mt_function.report_slot_liftoff) + md->mt_function.report_slot_liftoff(md, max); + input_sync(md->input); + md->num_prv_rec = 0; + } +} + +static void cyttsp5_get_touch_axis(struct cyttsp5_mt_data *md, + int *axis, int size, int max, u8 *xy_data, int bofs) +{ + int nbyte; + int next; + + for (nbyte = 0, *axis = 0, next = 0; nbyte < size; nbyte++) { + dev_vdbg(md->dev, + "%s: *axis=%02X(%d) size=%d max=%08X xy_data=%p xy_data[%d]=%02X(%d) bofs=%d\n", + __func__, *axis, *axis, size, max, xy_data, next, + xy_data[next], xy_data[next], bofs); + *axis = *axis + ((xy_data[next] >> bofs) << (nbyte * 8)); + next++; + } + + *axis &= max - 1; + + dev_vdbg(md->dev, + "%s: *axis=%02X(%d) size=%d max=%08X xy_data=%p xy_data[%d]=%02X(%d)\n", + __func__, *axis, *axis, size, max, xy_data, next, + xy_data[next], xy_data[next]); +} + +static void cyttsp5_get_touch_hdr(struct cyttsp5_mt_data *md, + struct cyttsp5_touch *touch, u8 *xy_mode) +{ + struct device *dev = md->dev; + struct cyttsp5_sysinfo *si = md->si; + enum cyttsp5_tch_hdr hdr; + + for (hdr = CY_TCH_TIME; hdr < CY_TCH_NUM_HDR; hdr++) { + if (!si->tch_hdr[hdr].report) + continue; + cyttsp5_get_touch_axis(md, &touch->hdr[hdr], + si->tch_hdr[hdr].size, + si->tch_hdr[hdr].max, + xy_mode + si->tch_hdr[hdr].ofs, + si->tch_hdr[hdr].bofs); + dev_vdbg(dev, "%s: get %s=%04X(%d)\n", __func__, + cyttsp5_tch_hdr_string[hdr], + touch->hdr[hdr], touch->hdr[hdr]); + } + + dev_dbg(dev, + "%s: time=%X tch_num=%d lo=%d noise=%d counter=%d\n", + __func__, + touch->hdr[CY_TCH_TIME], + touch->hdr[CY_TCH_NUM], + touch->hdr[CY_TCH_LO], + touch->hdr[CY_TCH_NOISE], + touch->hdr[CY_TCH_COUNTER]); +} + +static void cyttsp5_get_touch_record(struct cyttsp5_mt_data *md, + struct cyttsp5_touch *touch, u8 *xy_data) +{ + struct device *dev = md->dev; + struct cyttsp5_sysinfo *si = md->si; + enum cyttsp5_tch_abs abs; + + for (abs = CY_TCH_X; abs < CY_TCH_NUM_ABS; abs++) { + if (!si->tch_abs[abs].report) + continue; + cyttsp5_get_touch_axis(md, &touch->abs[abs], + si->tch_abs[abs].size, + si->tch_abs[abs].max, + xy_data + si->tch_abs[abs].ofs, + si->tch_abs[abs].bofs); + dev_vdbg(dev, "%s: get %s=%04X(%d)\n", __func__, + cyttsp5_tch_abs_string[abs], + touch->abs[abs], touch->abs[abs]); + } +} + +static void cyttsp5_mt_process_touch(struct cyttsp5_mt_data *md, + struct cyttsp5_touch *touch) +{ + struct device *dev = md->dev; + struct cyttsp5_sysinfo *si = md->si; + int tmp; + bool flipped; + + + /* Orientation is signed */ + touch->abs[CY_TCH_OR] = (int8_t)touch->abs[CY_TCH_OR]; + + if (md->pdata->flags & CY_MT_FLAG_FLIP) { + tmp = touch->abs[CY_TCH_X]; + touch->abs[CY_TCH_X] = touch->abs[CY_TCH_Y]; + touch->abs[CY_TCH_Y] = tmp; + if (touch->abs[CY_TCH_OR] > 0) + touch->abs[CY_TCH_OR] = + md->or_max - touch->abs[CY_TCH_OR]; + else + touch->abs[CY_TCH_OR] = + md->or_min - touch->abs[CY_TCH_OR]; + flipped = true; + } else + flipped = false; + + if (md->pdata->flags & CY_MT_FLAG_INV_X) { + if (flipped) + touch->abs[CY_TCH_X] = si->sensing_conf_data.res_y - + touch->abs[CY_TCH_X]; + else + touch->abs[CY_TCH_X] = si->sensing_conf_data.res_x - + touch->abs[CY_TCH_X]; + touch->abs[CY_TCH_OR] *= -1; + } + if (md->pdata->flags & CY_MT_FLAG_INV_Y) { + if (flipped) + touch->abs[CY_TCH_Y] = si->sensing_conf_data.res_x - + touch->abs[CY_TCH_Y]; + else + touch->abs[CY_TCH_Y] = si->sensing_conf_data.res_y - + touch->abs[CY_TCH_Y]; + touch->abs[CY_TCH_OR] *= -1; + } + + /* Convert MAJOR/MINOR from mm to resolution */ + tmp = touch->abs[CY_TCH_MAJ] * 100 * si->sensing_conf_data.res_x; + touch->abs[CY_TCH_MAJ] = tmp / si->sensing_conf_data.len_x; + tmp = touch->abs[CY_TCH_MIN] * 100 * si->sensing_conf_data.res_x; + touch->abs[CY_TCH_MIN] = tmp / si->sensing_conf_data.len_x; + + dev_vdbg(dev, "%s: flip=%s inv-x=%s inv-y=%s x=%04X(%d) y=%04X(%d)\n", + __func__, flipped ? "true" : "false", + md->pdata->flags & CY_MT_FLAG_INV_X ? "true" : "false", + md->pdata->flags & CY_MT_FLAG_INV_Y ? "true" : "false", + touch->abs[CY_TCH_X], touch->abs[CY_TCH_X], + touch->abs[CY_TCH_Y], touch->abs[CY_TCH_Y]); +} + +static void cyttsp5_report_event(struct cyttsp5_mt_data *md, int event, + int value) +{ + int sig = MT_PARAM_SIGNAL(md, event); + + if (sig != CY_IGNORE_VALUE) + input_report_abs(md->input, sig, value); +} + +static void cyttsp5_get_mt_touches(struct cyttsp5_mt_data *md, + struct cyttsp5_touch *tch, int num_cur_tch) +{ + struct device *dev = md->dev; + struct cyttsp5_sysinfo *si = md->si; + int sig; + int i, j, t = 0; + DECLARE_BITMAP(ids, si->tch_abs[CY_TCH_T].max); + int mt_sync_count = 0; + u8 *tch_addr; + + bitmap_zero(ids, si->tch_abs[CY_TCH_T].max); + memset(tch->abs, 0, sizeof(tch->abs)); + + for (i = 0; i < num_cur_tch; i++) { + tch_addr = si->xy_data + (i * si->desc.tch_record_size); + cyttsp5_get_touch_record(md, tch, tch_addr); + + /* Discard proximity event */ + if (tch->abs[CY_TCH_O] == CY_OBJ_PROXIMITY) { + dev_vdbg(dev, "%s: Discarding proximity event\n", + __func__); + continue; + } + + /* Validate track_id */ + t = tch->abs[CY_TCH_T]; + if (t < md->t_min || t > md->t_max) { + dev_err(dev, "%s: tch=%d -> bad trk_id=%d max_id=%d\n", + __func__, i, t, md->t_max); + if (md->mt_function.input_sync) + md->mt_function.input_sync(md->input); + mt_sync_count++; + cyttsp5_pr_buf_debug(dev, si->xy_mode, si->desc.tch_header_size, + "Err xy_mode"); + cyttsp5_pr_buf_debug(dev, si->xy_data, + num_cur_tch* si->desc.tch_record_size, "Err xy_data"); + continue; + } + + /* Lift-off */ + if (tch->abs[CY_TCH_E] == CY_EV_LIFTOFF) { + dev_dbg(dev, "%s: t=%d e=%d lift-off\n", + __func__, t, tch->abs[CY_TCH_E]); + goto cyttsp5_get_mt_touches_pr_tch; + } + + /* Process touch */ + cyttsp5_mt_process_touch(md, tch); + + /* use 0 based track id's */ + t -= md->t_min; + + sig = MT_PARAM_SIGNAL(md, CY_ABS_ID_OST); + if (sig != CY_IGNORE_VALUE) { + if (md->mt_function.input_report) + md->mt_function.input_report(md->input, sig, + t, tch->abs[CY_TCH_O]); + __set_bit(t, ids); + } + + /* If touch type is hover, send P as distance, reset P */ + if (tch->abs[CY_TCH_O] == CY_OBJ_HOVER) { + /* CY_ABS_D_OST signal must be in touch framework */ + cyttsp5_report_event(md, CY_ABS_D_OST, + tch->abs[CY_TCH_P]); + tch->abs[CY_TCH_P] = 0; + } else + cyttsp5_report_event(md, CY_ABS_D_OST, 0); + + + /* all devices: position and pressure fields */ + for (j = 0; j <= CY_ABS_W_OST; j++) { + if (!si->tch_abs[j].report) + continue; + cyttsp5_report_event(md, CY_ABS_X_OST + j, + tch->abs[CY_TCH_X + j]); + } + + /* Get the extended touch fields */ + for (j = 0; j < CY_NUM_EXT_TCH_FIELDS; j++) { + if (!si->tch_abs[CY_ABS_MAJ_OST + j].report) + continue; + cyttsp5_report_event(md, CY_ABS_MAJ_OST + j, + tch->abs[CY_TCH_MAJ + j]); + } + if (md->mt_function.input_sync) + md->mt_function.input_sync(md->input); + mt_sync_count++; + +cyttsp5_get_mt_touches_pr_tch: + dev_dbg(dev, + "%s: t=%d x=%d y=%d z=%d M=%d m=%d o=%d e=%d obj=%d tip=%d\n", + __func__, t, + tch->abs[CY_TCH_X], + tch->abs[CY_TCH_Y], + tch->abs[CY_TCH_P], + tch->abs[CY_TCH_MAJ], + tch->abs[CY_TCH_MIN], + tch->abs[CY_TCH_OR], + tch->abs[CY_TCH_E], + tch->abs[CY_TCH_O], + tch->abs[CY_TCH_TIP]); + } + + if (md->mt_function.final_sync) + md->mt_function.final_sync(md->input, + si->tch_abs[CY_TCH_T].max, mt_sync_count, ids); + + md->num_prv_rec = num_cur_tch; +} + +/* read xy_data for all current touches */ +static int cyttsp5_xy_worker(struct cyttsp5_mt_data *md) +{ + struct device *dev = md->dev; + struct cyttsp5_sysinfo *si = md->si; + int max_tch = si->sensing_conf_data.max_tch; + struct cyttsp5_touch tch; + u8 num_cur_tch; + int rc = 0; + + cyttsp5_get_touch_hdr(md, &tch, si->xy_mode + 3); + + num_cur_tch = tch.hdr[CY_TCH_NUM]; + if (num_cur_tch > max_tch) { + dev_err(dev, "%s: Num touch err detected (n=%d)\n", + __func__, num_cur_tch); + num_cur_tch = max_tch; + } + + if (tch.hdr[CY_TCH_LO]) { + dev_dbg(dev, "%s: Large area detected\n", __func__); + if (md->pdata->flags & CY_MT_FLAG_NO_TOUCH_ON_LO) + num_cur_tch = 0; + } + + if (num_cur_tch == 0 && md->num_prv_rec == 0) + goto cyttsp5_xy_worker_exit; + + /* extract xy_data for all currently reported touches */ + dev_vdbg(dev, "%s: extract data num_cur_tch=%d\n", __func__, + num_cur_tch); + if (num_cur_tch) + cyttsp5_get_mt_touches(md, &tch, num_cur_tch); + else + cyttsp5_mt_lift_all(md); + + rc = 0; + +cyttsp5_xy_worker_exit: + return rc; +} + +static void cyttsp5_mt_send_dummy_event(struct cyttsp5_core_data *cd, + struct cyttsp5_mt_data *md) +{ +#ifndef EASYWAKE_TSG6 + /* TSG5 EasyWake */ + unsigned long ids = 0; + + /* for easy wakeup */ + if (md->mt_function.input_report) + md->mt_function.input_report(md->input, ABS_MT_TRACKING_ID, + 0, CY_OBJ_STANDARD_FINGER); + if (md->mt_function.input_sync) + md->mt_function.input_sync(md->input); + if (md->mt_function.final_sync) + md->mt_function.final_sync(md->input, 0, 1, &ids); + if (md->mt_function.report_slot_liftoff) + md->mt_function.report_slot_liftoff(md, 1); + if (md->mt_function.final_sync) + md->mt_function.final_sync(md->input, 1, 1, &ids); +#else + /* TSG6 FW1.3 EasyWake */ + u8 key_value; + + switch (cd->gesture_id) { + case GESTURE_DOUBLE_TAP: + key_value = KEY_F1; + break; + case GESTURE_TWO_FINGERS_SLIDE: + key_value = KEY_F2; + break; + case GESTURE_TOUCH_DETECTED: + key_value = KEY_F3; + break; + case GESTURE_PUSH_BUTTON: + key_value = KEY_F4; + break; + case GESTURE_SINGLE_SLIDE_DE_TX: + key_value = KEY_F5; + break; + case GESTURE_SINGLE_SLIDE_IN_TX: + key_value = KEY_F6; + break; + case GESTURE_SINGLE_SLIDE_DE_RX: + key_value = KEY_F7; + break; + case GESTURE_SINGLE_SLIDE_IN_RX: + key_value = KEY_F8; + break; + default: + break; + } + + input_report_key(md->input, key_value, 1); + mdelay(10); + input_report_key(md->input, key_value, 0); + input_sync(md->input); +#endif +} + +static int cyttsp5_mt_attention(struct device *dev) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + struct cyttsp5_mt_data *md = &cd->md; + int rc; + + if (md->si->xy_mode[2] != md->si->desc.tch_report_id) + return 0; + + /* core handles handshake */ + mutex_lock(&md->mt_lock); + rc = cyttsp5_xy_worker(md); + mutex_unlock(&md->mt_lock); + if (rc < 0) + dev_err(dev, "%s: xy_worker error r=%d\n", __func__, rc); + + return rc; +} + +static int cyttsp5_mt_wake_attention(struct device *dev) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + struct cyttsp5_mt_data *md = &cd->md; + + mutex_lock(&md->mt_lock); + cyttsp5_mt_send_dummy_event(cd, md); + mutex_unlock(&md->mt_lock); + return 0; +} + +static int cyttsp5_startup_attention(struct device *dev) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + struct cyttsp5_mt_data *md = &cd->md; + + mutex_lock(&md->mt_lock); + cyttsp5_mt_lift_all(md); + mutex_unlock(&md->mt_lock); + + return 0; +} + +static int cyttsp5_mt_suspend_attention(struct device *dev) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + struct cyttsp5_mt_data *md = &cd->md; + + mutex_lock(&md->mt_lock); + cyttsp5_mt_lift_all(md); + md->is_suspended = true; + mutex_unlock(&md->mt_lock); + + pm_runtime_put(dev); + + return 0; +} + +static int cyttsp5_mt_resume_attention(struct device *dev) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + struct cyttsp5_mt_data *md = &cd->md; + + pm_runtime_get(dev); + + mutex_lock(&md->mt_lock); + md->is_suspended = false; + mutex_unlock(&md->mt_lock); + + return 0; +} + +static int cyttsp5_mt_open(struct input_dev *input) +{ + struct device *dev = input->dev.parent; + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + struct cyttsp5_mt_data *md = &cd->md; + + pm_runtime_get_sync(dev); + + mutex_lock(&md->mt_lock); + md->is_suspended = false; + mutex_unlock(&md->mt_lock); + + dev_vdbg(dev, "%s: setup subscriptions\n", __func__); + + /* set up touch call back */ + _cyttsp5_subscribe_attention(dev, CY_ATTEN_IRQ, CYTTSP5_MT_NAME, + cyttsp5_mt_attention, CY_MODE_OPERATIONAL); + + /* set up startup call back */ + _cyttsp5_subscribe_attention(dev, CY_ATTEN_STARTUP, CYTTSP5_MT_NAME, + cyttsp5_startup_attention, 0); + + /* set up wakeup call back */ + _cyttsp5_subscribe_attention(dev, CY_ATTEN_WAKE, CYTTSP5_MT_NAME, + cyttsp5_mt_wake_attention, 0); + + /* set up suspend call back */ + _cyttsp5_subscribe_attention(dev, CY_ATTEN_SUSPEND, CYTTSP5_MT_NAME, + cyttsp5_mt_suspend_attention, 0); + + /* set up resume call back */ + _cyttsp5_subscribe_attention(dev, CY_ATTEN_RESUME, CYTTSP5_MT_NAME, + cyttsp5_mt_resume_attention, 0); + + return 0; +} + +static void cyttsp5_mt_close(struct input_dev *input) +{ + struct device *dev = input->dev.parent; + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + struct cyttsp5_mt_data *md = &cd->md; + + _cyttsp5_unsubscribe_attention(dev, CY_ATTEN_IRQ, CYTTSP5_MT_NAME, + cyttsp5_mt_attention, CY_MODE_OPERATIONAL); + + _cyttsp5_unsubscribe_attention(dev, CY_ATTEN_STARTUP, CYTTSP5_MT_NAME, + cyttsp5_startup_attention, 0); + + _cyttsp5_unsubscribe_attention(dev, CY_ATTEN_WAKE, CYTTSP5_MT_NAME, + cyttsp5_mt_wake_attention, 0); + + _cyttsp5_unsubscribe_attention(dev, CY_ATTEN_SUSPEND, CYTTSP5_MT_NAME, + cyttsp5_mt_suspend_attention, 0); + + _cyttsp5_unsubscribe_attention(dev, CY_ATTEN_RESUME, CYTTSP5_MT_NAME, + cyttsp5_mt_resume_attention, 0); + + mutex_lock(&md->mt_lock); + if (!md->is_suspended) { + pm_runtime_put(dev); + md->is_suspended = true; + } + mutex_unlock(&md->mt_lock); +} + +static int cyttsp5_setup_input_device(struct device *dev) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + struct cyttsp5_mt_data *md = &cd->md; + int signal = CY_IGNORE_VALUE; + int max_x, max_y, max_p, min, max; + int max_x_tmp, max_y_tmp; + int i; + int rc; + + dev_vdbg(dev, "%s: Initialize event signals\n", __func__); + __set_bit(EV_ABS, md->input->evbit); + __set_bit(EV_REL, md->input->evbit); + __set_bit(EV_KEY, md->input->evbit); +#ifdef INPUT_PROP_DIRECT + __set_bit(INPUT_PROP_DIRECT, md->input->propbit); +#endif + + /* If virtualkeys enabled, don't use all screen */ + if (md->pdata->flags & CY_MT_FLAG_VKEYS) { + max_x_tmp = md->pdata->vkeys_x; + max_y_tmp = md->pdata->vkeys_y; + } else { + max_x_tmp = md->si->sensing_conf_data.res_x; + max_y_tmp = md->si->sensing_conf_data.res_y; + } + + /* get maximum values from the sysinfo data */ + if (md->pdata->flags & CY_MT_FLAG_FLIP) { + max_x = max_y_tmp - 1; + max_y = max_x_tmp - 1; + } else { + max_x = max_x_tmp - 1; + max_y = max_y_tmp - 1; + } + max_p = md->si->sensing_conf_data.max_z; + + /* set event signal capabilities */ + for (i = 0; i < NUM_SIGNALS(md->pdata->frmwrk); i++) { + signal = MT_PARAM_SIGNAL(md, i); + if (signal != CY_IGNORE_VALUE) { + __set_bit(signal, md->input->absbit); + + min = MT_PARAM_MIN(md, i); + max = MT_PARAM_MAX(md, i); + if (i == CY_ABS_ID_OST) { + /* shift track ids down to start at 0 */ + max = max - min; + min = min - min; + } else if (i == CY_ABS_X_OST) + max = max_x; + else if (i == CY_ABS_Y_OST) + max = max_y; + else if (i == CY_ABS_P_OST) + max = max_p; + + input_set_abs_params(md->input, signal, min, max, + MT_PARAM_FUZZ(md, i), MT_PARAM_FLAT(md, i)); + dev_dbg(dev, "%s: register signal=%02X min=%d max=%d\n", + __func__, signal, min, max); + } + } + + md->or_min = MT_PARAM_MIN(md, CY_ABS_OR_OST); + md->or_max = MT_PARAM_MAX(md, CY_ABS_OR_OST); + + md->t_min = MT_PARAM_MIN(md, CY_ABS_ID_OST); + md->t_max = MT_PARAM_MAX(md, CY_ABS_ID_OST); + + rc = md->mt_function.input_register_device(md->input, + md->si->tch_abs[CY_TCH_T].max); + if (rc < 0) + dev_err(dev, "%s: Error, failed register input device r=%d\n", + __func__, rc); + else + md->input_device_registered = true; + +#ifdef EASYWAKE_TSG6 + input_set_capability(md->input, EV_KEY, KEY_F1); + input_set_capability(md->input, EV_KEY, KEY_F2); + input_set_capability(md->input, EV_KEY, KEY_F3); + input_set_capability(md->input, EV_KEY, KEY_F4); + input_set_capability(md->input, EV_KEY, KEY_F5); + input_set_capability(md->input, EV_KEY, KEY_F6); + input_set_capability(md->input, EV_KEY, KEY_F7); + input_set_capability(md->input, EV_KEY, KEY_F8); +#endif + return rc; +} + +static int cyttsp5_setup_input_attention(struct device *dev) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + struct cyttsp5_mt_data *md = &cd->md; + int rc; + + md->si = _cyttsp5_request_sysinfo(dev); + if (!md->si) + return -EINVAL; + + rc = cyttsp5_setup_input_device(dev); + + _cyttsp5_unsubscribe_attention(dev, CY_ATTEN_STARTUP, CYTTSP5_MT_NAME, + cyttsp5_setup_input_attention, 0); + + return rc; +} + +int cyttsp5_mt_probe(struct device *dev) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + struct cyttsp5_mt_data *md = &cd->md; + struct cyttsp5_platform_data *pdata = dev_get_platdata(dev); + struct cyttsp5_mt_platform_data *mt_pdata; + int rc = 0; + + if (!pdata || !pdata->mt_pdata) { + dev_err(dev, "%s: Missing platform data\n", __func__); + rc = -ENODEV; + goto error_no_pdata; + } + mt_pdata = pdata->mt_pdata; + + cyttsp5_init_function_ptrs(md); + + mutex_init(&md->mt_lock); + md->dev = dev; + md->pdata = mt_pdata; + + /* Create the input device and register it. */ + dev_vdbg(dev, "%s: Create the input device and register it\n", + __func__); + md->input = input_allocate_device(); + if (!md->input) { + dev_err(dev, "%s: Error, failed to allocate input device\n", + __func__); + rc = -ENODEV; + goto error_alloc_failed; + } + + if (md->pdata->inp_dev_name) + md->input->name = md->pdata->inp_dev_name; + else + md->input->name = CYTTSP5_MT_NAME; + scnprintf(md->phys, sizeof(md->phys), "%s/input%d", dev_name(dev), + cd->phys_num++); + md->input->phys = md->phys; + md->input->dev.parent = md->dev; + md->input->open = cyttsp5_mt_open; + md->input->close = cyttsp5_mt_close; + input_set_drvdata(md->input, md); + + /* get sysinfo */ + md->si = _cyttsp5_request_sysinfo(dev); + + if (md->si) { + rc = cyttsp5_setup_input_device(dev); + if (rc) + goto error_init_input; + } else { + dev_err(dev, "%s: Fail get sysinfo pointer from core p=%p\n", + __func__, md->si); + _cyttsp5_subscribe_attention(dev, CY_ATTEN_STARTUP, + CYTTSP5_MT_NAME, cyttsp5_setup_input_attention, 0); + } + + return 0; + +error_init_input: + input_free_device(md->input); +error_alloc_failed: +error_no_pdata: + dev_err(dev, "%s failed.\n", __func__); + return rc; +} + +int cyttsp5_mt_release(struct device *dev) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + struct cyttsp5_mt_data *md = &cd->md; + + if (md->input_device_registered) { + input_unregister_device(md->input); + } else { + input_free_device(md->input); + _cyttsp5_unsubscribe_attention(dev, CY_ATTEN_STARTUP, + CYTTSP5_MT_NAME, cyttsp5_setup_input_attention, 0); + } + + return 0; +} diff --git a/drivers/input/touchscreen/cyttsp5_mta.c b/drivers/input/touchscreen/cyttsp5_mta.c new file mode 100644 index 000000000000..d457bb66d360 --- /dev/null +++ b/drivers/input/touchscreen/cyttsp5_mta.c @@ -0,0 +1,85 @@ +/* + * cyttsp5_mta.c + * Parade TrueTouch(TM) Standard Product V5 Multi-Touch Protocol A Module. + * For use with Parade touchscreen controllers. + * Supported parts include: + * CYTMA5XX + * CYTMA448 + * CYTMA445A + * CYTT21XXX + * CYTT31XXX + * + * Copyright (C) 2015 Parade Technologies + * Copyright (C) 2012-2015 Cypress Semiconductor + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Contact Parade Technologies at www.paradetech.com + * + */ + +#include "cyttsp5_regs.h" + +static void cyttsp5_final_sync(struct input_dev *input, int max_slots, + int mt_sync_count, unsigned long *ids) +{ + if (mt_sync_count) + input_sync(input); +} + +static void cyttsp5_input_sync(struct input_dev *input) +{ + input_mt_sync(input); +} + +static void cyttsp5_input_report(struct input_dev *input, int sig, + int t, int type) +{ + if (type == CY_OBJ_STANDARD_FINGER || type == CY_OBJ_GLOVE + || type == CY_OBJ_HOVER) { + input_report_key(input, BTN_TOOL_FINGER, CY_BTN_PRESSED); + input_report_key(input, BTN_TOOL_PEN, CY_BTN_RELEASED); + } else if (type == CY_OBJ_STYLUS) { + input_report_key(input, BTN_TOOL_PEN, CY_BTN_PRESSED); + input_report_key(input, BTN_TOOL_FINGER, CY_BTN_RELEASED); + } + + if (type != CY_OBJ_HOVER) + input_report_key(input, BTN_TOUCH, CY_BTN_PRESSED); + + input_report_abs(input, sig, t); +} + +static void cyttsp5_report_slot_liftoff(struct cyttsp5_mt_data *md, + int max_slots) +{ + input_report_key(md->input, BTN_TOUCH, CY_BTN_RELEASED); + input_report_key(md->input, BTN_TOOL_FINGER, CY_BTN_RELEASED); + input_report_key(md->input, BTN_TOOL_PEN, CY_BTN_RELEASED); + +} + +static int cyttsp5_input_register_device(struct input_dev *input, int max_slots) +{ + __set_bit(BTN_TOUCH, input->keybit); + __set_bit(BTN_TOOL_FINGER, input->keybit); + __set_bit(BTN_TOOL_PEN, input->keybit); + return input_register_device(input); +} + +void cyttsp5_init_function_ptrs(struct cyttsp5_mt_data *md) +{ + md->mt_function.report_slot_liftoff = cyttsp5_report_slot_liftoff; + md->mt_function.final_sync = cyttsp5_final_sync; + md->mt_function.input_sync = cyttsp5_input_sync; + md->mt_function.input_report = cyttsp5_input_report; + md->mt_function.input_register_device = cyttsp5_input_register_device; +} diff --git a/drivers/input/touchscreen/cyttsp5_mtb.c b/drivers/input/touchscreen/cyttsp5_mtb.c new file mode 100644 index 000000000000..680eb3ab3167 --- /dev/null +++ b/drivers/input/touchscreen/cyttsp5_mtb.c @@ -0,0 +1,93 @@ +/* + * cyttsp5_mtb.c + * Parade TrueTouch(TM) Standard Product V5 Multi-Touch Protocol B Module. + * For use with Parade touchscreen controllers. + * Supported parts include: + * CYTMA5XX + * CYTMA448 + * CYTMA445A + * CYTT21XXX + * CYTT31XXX + * + * Copyright (C) 2015 Parade Technologies + * Copyright (C) 2012-2015 Cypress Semiconductor + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Contact Parade Technologies at www.paradetech.com + * + */ + +#include "cyttsp5_regs.h" +#include +#include + +static void cyttsp5_final_sync(struct input_dev *input, int max_slots, + int mt_sync_count, unsigned long *ids) +{ + int t; + + for (t = 0; t < max_slots; t++) { + if (test_bit(t, ids)) + continue; + input_mt_slot(input, t); + input_mt_report_slot_state(input, MT_TOOL_FINGER, false); + } + + input_sync(input); +} + +static void cyttsp5_input_report(struct input_dev *input, int sig, + int t, int type) +{ + input_mt_slot(input, t); + + if (type == CY_OBJ_STANDARD_FINGER || type == CY_OBJ_GLOVE + || type == CY_OBJ_HOVER) + input_mt_report_slot_state(input, MT_TOOL_FINGER, true); + else if (type == CY_OBJ_STYLUS) + input_mt_report_slot_state(input, MT_TOOL_PEN, true); +} + +static void cyttsp5_report_slot_liftoff(struct cyttsp5_mt_data *md, + int max_slots) +{ + int t; + + if (md->num_prv_rec == 0) + return; + + for (t = 0; t < max_slots; t++) { + input_mt_slot(md->input, t); + input_mt_report_slot_state(md->input, + MT_TOOL_FINGER, false); + } +} + +static int cyttsp5_input_register_device(struct input_dev *input, int max_slots) +{ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0)) + input_mt_init_slots(input, max_slots, 0); +#else + input_mt_init_slots(input, max_slots); +#endif + return input_register_device(input); +} + +void cyttsp5_init_function_ptrs(struct cyttsp5_mt_data *md) +{ + md->mt_function.report_slot_liftoff = cyttsp5_report_slot_liftoff; + md->mt_function.final_sync = cyttsp5_final_sync; + md->mt_function.input_sync = NULL; + md->mt_function.input_report = cyttsp5_input_report; + md->mt_function.input_register_device = cyttsp5_input_register_device; +} + diff --git a/drivers/input/touchscreen/cyttsp5_platform.c b/drivers/input/touchscreen/cyttsp5_platform.c new file mode 100644 index 000000000000..976e9befdd91 --- /dev/null +++ b/drivers/input/touchscreen/cyttsp5_platform.c @@ -0,0 +1,288 @@ +/* + * cyttsp5_platform.c + * Parade TrueTouch(TM) Standard Product V5 Platform Module. + * For use with Parade touchscreen controllers. + * Supported parts include: + * CYTMA5XX + * CYTMA448 + * CYTMA445A + * CYTT21XXX + * CYTT31XXX + * + * Copyright (C) 2015 Parade Technologies + * Copyright (C) 2013-2015 Cypress Semiconductor + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Contact Parade Technologies at www.paradetech.com + * + */ + +#include "cyttsp5_regs.h" +#include + +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_PLATFORM_FW_UPGRADE +/* FW for Panel ID = 0x00 */ +#include "cyttsp5_fw_pid00.h" +static struct cyttsp5_touch_firmware cyttsp5_firmware_pid00 = { + .img = cyttsp4_img_pid00, + .size = ARRAY_SIZE(cyttsp4_img_pid00), + .ver = cyttsp4_ver_pid00, + .vsize = ARRAY_SIZE(cyttsp4_ver_pid00), + .panel_id = 0x00, +}; + +/* FW for Panel ID = 0x01 */ +#include "cyttsp5_fw_pid01.h" +static struct cyttsp5_touch_firmware cyttsp5_firmware_pid01 = { + .img = cyttsp4_img_pid01, + .size = ARRAY_SIZE(cyttsp4_img_pid01), + .ver = cyttsp4_ver_pid01, + .vsize = ARRAY_SIZE(cyttsp4_ver_pid01), + .panel_id = 0x01, +}; + +/* FW for Panel ID not enabled (legacy) */ +#include "cyttsp5_fw.h" +static struct cyttsp5_touch_firmware cyttsp5_firmware = { + .img = cyttsp4_img, + .size = ARRAY_SIZE(cyttsp4_img), + .ver = cyttsp4_ver, + .vsize = ARRAY_SIZE(cyttsp4_ver), +}; +#else +/* FW for Panel ID not enabled (legacy) */ +static struct cyttsp5_touch_firmware cyttsp5_firmware = { + .img = NULL, + .size = 0, + .ver = NULL, + .vsize = 0, +}; +#endif + +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_PLATFORM_TTCONFIG_UPGRADE +/* TT Config for Panel ID = 0x00 */ +#include "cyttsp5_params_pid00.h" +static struct touch_settings cyttsp5_sett_param_regs_pid00 = { + .data = (uint8_t *)&cyttsp4_param_regs_pid00[0], + .size = ARRAY_SIZE(cyttsp4_param_regs_pid00), + .tag = 0, +}; + +static struct touch_settings cyttsp5_sett_param_size_pid00 = { + .data = (uint8_t *)&cyttsp4_param_size_pid00[0], + .size = ARRAY_SIZE(cyttsp4_param_size_pid00), + .tag = 0, +}; + +static struct cyttsp5_touch_config cyttsp5_ttconfig_pid00 = { + .param_regs = &cyttsp5_sett_param_regs_pid00, + .param_size = &cyttsp5_sett_param_size_pid00, + .fw_ver = ttconfig_fw_ver_pid00, + .fw_vsize = ARRAY_SIZE(ttconfig_fw_ver_pid00), + .panel_id = 0x00, +}; + +/* TT Config for Panel ID = 0x01 */ +#include "cyttsp5_params_pid01.h" +static struct touch_settings cyttsp5_sett_param_regs_pid01 = { + .data = (uint8_t *)&cyttsp4_param_regs_pid01[0], + .size = ARRAY_SIZE(cyttsp4_param_regs_pid01), + .tag = 0, +}; + +static struct touch_settings cyttsp5_sett_param_size_pid01 = { + .data = (uint8_t *)&cyttsp4_param_size_pid01[0], + .size = ARRAY_SIZE(cyttsp4_param_size_pid01), + .tag = 0, +}; + +static struct cyttsp5_touch_config cyttsp5_ttconfig_pid01 = { + .param_regs = &cyttsp5_sett_param_regs_pid01, + .param_size = &cyttsp5_sett_param_size_pid01, + .fw_ver = ttconfig_fw_ver_pid01, + .fw_vsize = ARRAY_SIZE(ttconfig_fw_ver_pid01), + .panel_id = 0x01, +}; + +/* TT Config for Panel ID not enabled (legacy)*/ +#include "cyttsp5_params.h" +static struct touch_settings cyttsp5_sett_param_regs = { + .data = (uint8_t *)&cyttsp4_param_regs[0], + .size = ARRAY_SIZE(cyttsp4_param_regs), + .tag = 0, +}; + +static struct touch_settings cyttsp5_sett_param_size = { + .data = (uint8_t *)&cyttsp4_param_size[0], + .size = ARRAY_SIZE(cyttsp4_param_size), + .tag = 0, +}; + +static struct cyttsp5_touch_config cyttsp5_ttconfig = { + .param_regs = &cyttsp5_sett_param_regs, + .param_size = &cyttsp5_sett_param_size, + .fw_ver = ttconfig_fw_ver, + .fw_vsize = ARRAY_SIZE(ttconfig_fw_ver), +}; +#else +/* TT Config for Panel ID not enabled (legacy)*/ +static struct cyttsp5_touch_config cyttsp5_ttconfig = { + .param_regs = NULL, + .param_size = NULL, + .fw_ver = NULL, + .fw_vsize = 0, +}; +#endif + +static struct cyttsp5_touch_firmware *cyttsp5_firmwares[] = { +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_PLATFORM_FW_UPGRADE + &cyttsp5_firmware_pid00, + &cyttsp5_firmware_pid01, +#endif + NULL, /* Last item should always be NULL */ +}; + +static struct cyttsp5_touch_config *cyttsp5_ttconfigs[] = { +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_PLATFORM_TTCONFIG_UPGRADE + &cyttsp5_ttconfig_pid00, + &cyttsp5_ttconfig_pid01, +#endif + NULL, /* Last item should always be NULL */ +}; + +struct cyttsp5_loader_platform_data _cyttsp5_loader_platform_data = { + .fw = &cyttsp5_firmware, + .ttconfig = &cyttsp5_ttconfig, + .fws = cyttsp5_firmwares, + .ttconfigs = cyttsp5_ttconfigs, + .flags = CY_LOADER_FLAG_NONE, +}; + +int cyttsp5_xres(struct cyttsp5_core_platform_data *pdata, + struct device *dev) +{ + int rst_gpio = pdata->rst_gpio; + int rc = 0; + + gpio_set_value(rst_gpio, 1); + msleep(20); + gpio_set_value(rst_gpio, 0); + msleep(40); + gpio_set_value(rst_gpio, 1); + msleep(20); + dev_info(dev, + "%s: RESET CYTTSP gpio=%d r=%d\n", __func__, + pdata->rst_gpio, rc); + return rc; +} + +int cyttsp5_init(struct cyttsp5_core_platform_data *pdata, + int on, struct device *dev) +{ + int rst_gpio = pdata->rst_gpio; + int irq_gpio = pdata->irq_gpio; + int rc = 0; + + if (on) { + rc = gpio_request(rst_gpio, NULL); + if (rc < 0) { + gpio_free(rst_gpio); + rc = gpio_request(rst_gpio, NULL); + } + if (rc < 0) { + dev_err(dev, + "%s: Fail request gpio=%d\n", __func__, + rst_gpio); + } else { + rc = gpio_direction_output(rst_gpio, 1); + if (rc < 0) { + pr_err("%s: Fail set output gpio=%d\n", + __func__, rst_gpio); + gpio_free(rst_gpio); + } else { + rc = gpio_request(irq_gpio, NULL); + if (rc < 0) { + gpio_free(irq_gpio); + rc = gpio_request(irq_gpio, + NULL); + } + if (rc < 0) { + dev_err(dev, + "%s: Fail request gpio=%d\n", + __func__, irq_gpio); + gpio_free(rst_gpio); + } else { + gpio_direction_input(irq_gpio); + } + } + } + } else { + gpio_free(rst_gpio); + gpio_free(irq_gpio); + } + + dev_info(dev, "%s: INIT CYTTSP RST gpio=%d and IRQ gpio=%d r=%d\n", + __func__, rst_gpio, irq_gpio, rc); + return rc; +} + +static int cyttsp5_wakeup(struct cyttsp5_core_platform_data *pdata, + struct device *dev, atomic_t *ignore_irq) +{ + return 0; +} + +static int cyttsp5_sleep(struct cyttsp5_core_platform_data *pdata, + struct device *dev, atomic_t *ignore_irq) +{ + return 0; +} + +int cyttsp5_power(struct cyttsp5_core_platform_data *pdata, + int on, struct device *dev, atomic_t *ignore_irq) +{ + if (on) + return cyttsp5_wakeup(pdata, dev, ignore_irq); + + return cyttsp5_sleep(pdata, dev, ignore_irq); +} + +int cyttsp5_irq_stat(struct cyttsp5_core_platform_data *pdata, + struct device *dev) +{ + return gpio_get_value(pdata->irq_gpio); +} + +#ifdef CYTTSP5_DETECT_HW +int cyttsp5_detect(struct cyttsp5_core_platform_data *pdata, + struct device *dev, cyttsp5_platform_read read) +{ + int retry = 3; + int rc; + char buf[1]; + + while (retry--) { + /* Perform reset, wait for 100 ms and perform read */ + dev_vdbg(dev, "%s: Performing a reset\n", __func__); + pdata->xres(pdata, dev); + msleep(100); + rc = read(dev, buf, 1); + if (!rc) + return 0; + + dev_vdbg(dev, "%s: Read unsuccessful, try=%d\n", + __func__, 3 - retry); + } + + return rc; +} +#endif diff --git a/drivers/input/touchscreen/cyttsp5_proximity.c b/drivers/input/touchscreen/cyttsp5_proximity.c new file mode 100644 index 000000000000..66dcfc338bd1 --- /dev/null +++ b/drivers/input/touchscreen/cyttsp5_proximity.c @@ -0,0 +1,553 @@ +/* + * cyttsp5_proximity.c + * Parade TrueTouch(TM) Standard Product V5 Proximity Module. + * For use with Parade touchscreen controllers. + * Supported parts include: + * CYTMA5XX + * CYTMA448 + * CYTMA445A + * CYTT21XXX + * CYTT31XXX + * + * Copyright (C) 2015 Parade Technologies + * Copyright (C) 2013-2015 Cypress Semiconductor + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Contact Parade Technologies at www.paradetech.com + * + */ + +#include "cyttsp5_regs.h" + +#define CYTTSP5_PROXIMITY_NAME "cyttsp5_proximity" + +/* Timeout value in ms. */ +#define CYTTSP5_PROXIMITY_REQUEST_EXCLUSIVE_TIMEOUT 1000 + +#define CYTTSP5_PROXIMITY_ON 0 +#define CYTTSP5_PROXIMITY_OFF 1 + +static inline struct cyttsp5_proximity_data *get_prox_data(struct device *dev) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + return &cd->pd; +} + +static void cyttsp5_report_proximity(struct cyttsp5_proximity_data *pd, + bool on) +{ + int val = on ? CYTTSP5_PROXIMITY_ON : CYTTSP5_PROXIMITY_OFF; + + input_report_abs(pd->input, ABS_DISTANCE, val); + input_sync(pd->input); +} + +static void cyttsp5_get_touch_axis(struct cyttsp5_proximity_data *pd, + int *axis, int size, int max, u8 *xy_data, int bofs) +{ + int nbyte; + int next; + + for (nbyte = 0, *axis = 0, next = 0; nbyte < size; nbyte++) { + dev_vdbg(pd->dev, + "%s: *axis=%02X(%d) size=%d max=%08X xy_data=%p xy_data[%d]=%02X(%d) bofs=%d\n", + __func__, *axis, *axis, size, max, xy_data, next, + xy_data[next], xy_data[next], bofs); + *axis = *axis + ((xy_data[next] >> bofs) << (nbyte * 8)); + next++; + } + + *axis &= max - 1; + + dev_vdbg(pd->dev, + "%s: *axis=%02X(%d) size=%d max=%08X xy_data=%p xy_data[%d]=%02X(%d)\n", + __func__, *axis, *axis, size, max, xy_data, next, + xy_data[next], xy_data[next]); +} + +static void cyttsp5_get_touch_hdr(struct cyttsp5_proximity_data *pd, + struct cyttsp5_touch *touch, u8 *xy_mode) +{ + struct device *dev = pd->dev; + struct cyttsp5_sysinfo *si = pd->si; + enum cyttsp5_tch_hdr hdr; + + for (hdr = CY_TCH_TIME; hdr < CY_TCH_NUM_HDR; hdr++) { + if (!si->tch_hdr[hdr].report) + continue; + cyttsp5_get_touch_axis(pd, &touch->hdr[hdr], + si->tch_hdr[hdr].size, + si->tch_hdr[hdr].max, + xy_mode + si->tch_hdr[hdr].ofs, + si->tch_hdr[hdr].bofs); + dev_vdbg(dev, "%s: get %s=%04X(%d)\n", __func__, + cyttsp5_tch_hdr_string[hdr], + touch->hdr[hdr], touch->hdr[hdr]); + } +} + +static void cyttsp5_get_touch(struct cyttsp5_proximity_data *pd, + struct cyttsp5_touch *touch, u8 *xy_data) +{ + struct device *dev = pd->dev; + struct cyttsp5_sysinfo *si = pd->si; + enum cyttsp5_tch_abs abs; + + for (abs = CY_TCH_X; abs < CY_TCH_NUM_ABS; abs++) { + if (!si->tch_abs[abs].report) + continue; + cyttsp5_get_touch_axis(pd, &touch->abs[abs], + si->tch_abs[abs].size, + si->tch_abs[abs].max, + xy_data + si->tch_abs[abs].ofs, + si->tch_abs[abs].bofs); + dev_vdbg(dev, "%s: get %s=%04X(%d)\n", __func__, + cyttsp5_tch_abs_string[abs], + touch->abs[abs], touch->abs[abs]); + } + + dev_vdbg(dev, "%s: x=%04X(%d) y=%04X(%d)\n", __func__, + touch->abs[CY_TCH_X], touch->abs[CY_TCH_X], + touch->abs[CY_TCH_Y], touch->abs[CY_TCH_Y]); +} + +static void cyttsp5_get_proximity_touch(struct cyttsp5_proximity_data *pd, + struct cyttsp5_touch *tch, int num_cur_tch) +{ + struct cyttsp5_sysinfo *si = pd->si; + int i; + + for (i = 0; i < num_cur_tch; i++) { + cyttsp5_get_touch(pd, tch, si->xy_data + + (i * si->desc.tch_record_size)); + + /* Check for proximity event */ + if (tch->abs[CY_TCH_O] == CY_OBJ_PROXIMITY) { + if (tch->abs[CY_TCH_E] == CY_EV_TOUCHDOWN) + cyttsp5_report_proximity(pd, true); + else if (tch->abs[CY_TCH_E] == CY_EV_LIFTOFF) + cyttsp5_report_proximity(pd, false); + break; + } + } +} + +/* read xy_data for all current touches */ +static int cyttsp5_xy_worker(struct cyttsp5_proximity_data *pd) +{ + struct device *dev = pd->dev; + struct cyttsp5_sysinfo *si = pd->si; + struct cyttsp5_touch tch; + u8 num_cur_tch; + + cyttsp5_get_touch_hdr(pd, &tch, si->xy_mode + 3); + + num_cur_tch = tch.hdr[CY_TCH_NUM]; + if (num_cur_tch > si->sensing_conf_data.max_tch) { + dev_err(dev, "%s: Num touch err detected (n=%d)\n", + __func__, num_cur_tch); + num_cur_tch = si->sensing_conf_data.max_tch; + } + + if (tch.hdr[CY_TCH_LO]) + dev_dbg(dev, "%s: Large area detected\n", __func__); + + /* extract xy_data for all currently reported touches */ + dev_vdbg(dev, "%s: extract data num_cur_rec=%d\n", __func__, + num_cur_tch); + if (num_cur_tch) + cyttsp5_get_proximity_touch(pd, &tch, num_cur_tch); + else + cyttsp5_report_proximity(pd, false); + + return 0; +} + +static int cyttsp5_proximity_attention(struct device *dev) +{ + struct cyttsp5_proximity_data *pd = get_prox_data(dev); + int rc = 0; + + if (pd->si->xy_mode[2] != pd->si->desc.tch_report_id) + return 0; + + mutex_lock(&pd->prox_lock); + rc = cyttsp5_xy_worker(pd); + mutex_unlock(&pd->prox_lock); + if (rc < 0) + dev_err(dev, "%s: xy_worker error r=%d\n", __func__, rc); + + return rc; +} + +static int cyttsp5_startup_attention(struct device *dev) +{ + struct cyttsp5_proximity_data *pd = get_prox_data(dev); + + mutex_lock(&pd->prox_lock); + cyttsp5_report_proximity(pd, false); + mutex_unlock(&pd->prox_lock); + + return 0; +} + +static int _cyttsp5_set_proximity_via_touchmode_enabled( + struct cyttsp5_proximity_data *pd, bool enable) +{ + struct device *dev = pd->dev; + u32 touchmode_enabled; + int rc; + + rc = cyttsp5_request_nonhid_get_param(dev, 0, + CY_RAM_ID_TOUCHMODE_ENABLED, &touchmode_enabled); + if (rc) + return rc; + + if (enable) + touchmode_enabled |= 0x80; + else + touchmode_enabled &= 0x7F; + + rc = cyttsp5_request_nonhid_set_param(dev, 0, + CY_RAM_ID_TOUCHMODE_ENABLED, touchmode_enabled, + CY_RAM_ID_TOUCHMODE_ENABLED_SIZE); + + return rc; +} + +static int _cyttsp5_set_proximity_via_proximity_enable( + struct cyttsp5_proximity_data *pd, bool enable) +{ + struct device *dev = pd->dev; + u32 proximity_enable; + int rc; + + rc = cyttsp5_request_nonhid_get_param(dev, 0, + CY_RAM_ID_PROXIMITY_ENABLE, &proximity_enable); + if (rc) + return rc; + + if (enable) + proximity_enable |= 0x01; + else + proximity_enable &= 0xFE; + + rc = cyttsp5_request_nonhid_set_param(dev, 0, + CY_RAM_ID_PROXIMITY_ENABLE, proximity_enable, + CY_RAM_ID_PROXIMITY_ENABLE_SIZE); + + return rc; +} + +static int _cyttsp5_set_proximity(struct cyttsp5_proximity_data *pd, + bool enable) +{ + if (!IS_PIP_VER_GE(pd->si, 1, 4)) + return _cyttsp5_set_proximity_via_touchmode_enabled(pd, + enable); + + return _cyttsp5_set_proximity_via_proximity_enable(pd, enable); +} + +static int _cyttsp5_proximity_enable(struct cyttsp5_proximity_data *pd) +{ + struct device *dev = pd->dev; + int rc = 0; + + pm_runtime_get_sync(dev); + + rc = cyttsp5_request_exclusive(dev, + CYTTSP5_PROXIMITY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(dev, "%s: Error on request exclusive r=%d\n", + __func__, rc); + goto exit; + } + + rc = _cyttsp5_set_proximity(pd, true); + if (rc < 0) { + dev_err(dev, "%s: Error on request enable proximity scantype r=%d\n", + __func__, rc); + goto exit_release; + } + + dev_vdbg(dev, "%s: setup subscriptions\n", __func__); + + /* set up touch call back */ + _cyttsp5_subscribe_attention(dev, CY_ATTEN_IRQ, CYTTSP5_PROXIMITY_NAME, + cyttsp5_proximity_attention, CY_MODE_OPERATIONAL); + + /* set up startup call back */ + _cyttsp5_subscribe_attention(dev, CY_ATTEN_STARTUP, + CYTTSP5_PROXIMITY_NAME, cyttsp5_startup_attention, 0); + +exit_release: + cyttsp5_release_exclusive(dev); +exit: + return rc; +} + +static int _cyttsp5_proximity_disable(struct cyttsp5_proximity_data *pd, + bool force) +{ + struct device *dev = pd->dev; + int rc = 0; + + rc = cyttsp5_request_exclusive(dev, + CYTTSP5_PROXIMITY_REQUEST_EXCLUSIVE_TIMEOUT); + if (rc < 0) { + dev_err(dev, "%s: Error on request exclusive r=%d\n", + __func__, rc); + goto exit; + } + + rc = _cyttsp5_set_proximity(pd, false); + if (rc < 0) { + dev_err(dev, "%s: Error on request disable proximity scan r=%d\n", + __func__, rc); + goto exit_release; + } + +exit_release: + cyttsp5_release_exclusive(dev); + +exit: + if (!rc || force) { + _cyttsp5_unsubscribe_attention(dev, CY_ATTEN_IRQ, + CYTTSP5_PROXIMITY_NAME, cyttsp5_proximity_attention, + CY_MODE_OPERATIONAL); + + _cyttsp5_unsubscribe_attention(dev, CY_ATTEN_STARTUP, + CYTTSP5_PROXIMITY_NAME, cyttsp5_startup_attention, 0); + } + + pm_runtime_put(dev); + + return rc; +} + +static ssize_t cyttsp5_proximity_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cyttsp5_proximity_data *pd = get_prox_data(dev); + int val = 0; + + mutex_lock(&pd->sysfs_lock); + val = pd->enable_count; + mutex_unlock(&pd->sysfs_lock); + + return scnprintf(buf, CY_MAX_PRBUF_SIZE, "%d\n", val); +} + +static ssize_t cyttsp5_proximity_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct cyttsp5_proximity_data *pd = get_prox_data(dev); + unsigned long value; + int rc; + + rc = kstrtoul(buf, 10, &value); + if (rc < 0 || (value != 0 && value != 1)) { + dev_err(dev, "%s: Invalid value\n", __func__); + return -EINVAL; + } + + mutex_lock(&pd->sysfs_lock); + if (value) { + if (pd->enable_count++) { + dev_vdbg(dev, "%s: '%s' already enabled\n", __func__, + pd->input->name); + } else { + rc = _cyttsp5_proximity_enable(pd); + if (rc) + pd->enable_count--; + } + } else { + if (--pd->enable_count) { + if (pd->enable_count < 0) { + dev_err(dev, "%s: '%s' unbalanced disable\n", + __func__, pd->input->name); + pd->enable_count = 0; + } + } else { + rc = _cyttsp5_proximity_disable(pd, false); + if (rc) + pd->enable_count++; + } + } + mutex_unlock(&pd->sysfs_lock); + + if (rc) + return rc; + + return size; +} + +static DEVICE_ATTR(prox_enable, S_IRUSR | S_IWUSR, + cyttsp5_proximity_enable_show, + cyttsp5_proximity_enable_store); + +static int cyttsp5_setup_input_device_and_sysfs(struct device *dev) +{ + struct cyttsp5_proximity_data *pd = get_prox_data(dev); + int signal = CY_IGNORE_VALUE; + int i; + int rc; + + rc = device_create_file(dev, &dev_attr_prox_enable); + if (rc) { + dev_err(dev, "%s: Error, could not create enable\n", + __func__); + goto exit; + } + + dev_vdbg(dev, "%s: Initialize event signals\n", __func__); + + __set_bit(EV_ABS, pd->input->evbit); + + /* set event signal capabilities */ + for (i = 0; i < NUM_SIGNALS(pd->pdata->frmwrk); i++) { + signal = PARAM_SIGNAL(pd->pdata->frmwrk, i); + if (signal != CY_IGNORE_VALUE) { + input_set_abs_params(pd->input, signal, + PARAM_MIN(pd->pdata->frmwrk, i), + PARAM_MAX(pd->pdata->frmwrk, i), + PARAM_FUZZ(pd->pdata->frmwrk, i), + PARAM_FLAT(pd->pdata->frmwrk, i)); + } + } + + rc = input_register_device(pd->input); + if (rc) { + dev_err(dev, "%s: Error, failed register input device r=%d\n", + __func__, rc); + goto unregister_enable; + } + + pd->input_device_registered = true; + return rc; + +unregister_enable: + device_remove_file(dev, &dev_attr_prox_enable); +exit: + return rc; +} + +static int cyttsp5_setup_input_attention(struct device *dev) +{ + struct cyttsp5_proximity_data *pd = get_prox_data(dev); + int rc; + + pd->si = _cyttsp5_request_sysinfo(dev); + if (!pd->si) + return -EINVAL; + + rc = cyttsp5_setup_input_device_and_sysfs(dev); + if (!rc) + rc = _cyttsp5_set_proximity(pd, false); + + _cyttsp5_unsubscribe_attention(dev, CY_ATTEN_STARTUP, + CYTTSP5_PROXIMITY_NAME, cyttsp5_setup_input_attention, 0); + + return rc; +} + +int cyttsp5_proximity_probe(struct device *dev) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + struct cyttsp5_proximity_data *pd = &cd->pd; + struct cyttsp5_platform_data *pdata = dev_get_platdata(dev); + struct cyttsp5_proximity_platform_data *prox_pdata; + int rc = 0; + + if (!pdata || !pdata->prox_pdata) { + dev_err(dev, "%s: Missing platform data\n", __func__); + rc = -ENODEV; + goto error_no_pdata; + } + prox_pdata = pdata->prox_pdata; + + mutex_init(&pd->prox_lock); + mutex_init(&pd->sysfs_lock); + pd->dev = dev; + pd->pdata = prox_pdata; + + /* Create the input device and register it. */ + dev_vdbg(dev, "%s: Create the input device and register it\n", + __func__); + pd->input = input_allocate_device(); + if (!pd->input) { + dev_err(dev, "%s: Error, failed to allocate input device\n", + __func__); + rc = -ENODEV; + goto error_alloc_failed; + } + + if (pd->pdata->inp_dev_name) + pd->input->name = pd->pdata->inp_dev_name; + else + pd->input->name = CYTTSP5_PROXIMITY_NAME; + scnprintf(pd->phys, sizeof(pd->phys), "%s/input%d", dev_name(dev), + cd->phys_num++); + pd->input->phys = pd->phys; + pd->input->dev.parent = pd->dev; + input_set_drvdata(pd->input, pd); + + /* get sysinfo */ + pd->si = _cyttsp5_request_sysinfo(dev); + + if (pd->si) { + rc = cyttsp5_setup_input_device_and_sysfs(dev); + if (rc) + goto error_init_input; + + rc = _cyttsp5_set_proximity(pd, false); + } else { + dev_err(dev, "%s: Fail get sysinfo pointer from core p=%p\n", + __func__, pd->si); + _cyttsp5_subscribe_attention(dev, CY_ATTEN_STARTUP, + CYTTSP5_PROXIMITY_NAME, cyttsp5_setup_input_attention, + 0); + } + + return 0; + +error_init_input: + input_free_device(pd->input); +error_alloc_failed: +error_no_pdata: + dev_err(dev, "%s failed.\n", __func__); + return rc; +} + +int cyttsp5_proximity_release(struct device *dev) +{ + struct cyttsp5_proximity_data *pd = get_prox_data(dev); + + if (pd->input_device_registered) { + /* Disable proximity sensing */ + mutex_lock(&pd->sysfs_lock); + if (pd->enable_count) + _cyttsp5_proximity_disable(pd, true); + mutex_unlock(&pd->sysfs_lock); + device_remove_file(dev, &dev_attr_prox_enable); + input_unregister_device(pd->input); + } else { + input_free_device(pd->input); + _cyttsp5_unsubscribe_attention(dev, CY_ATTEN_STARTUP, + CYTTSP5_PROXIMITY_NAME, cyttsp5_setup_input_attention, + 0); + } + + return 0; +} diff --git a/drivers/input/touchscreen/cyttsp5_regs.h b/drivers/input/touchscreen/cyttsp5_regs.h new file mode 100644 index 000000000000..5392f1fc68a1 --- /dev/null +++ b/drivers/input/touchscreen/cyttsp5_regs.h @@ -0,0 +1,1144 @@ +/* + * cyttsp5_regs.h + * Parade TrueTouch(TM) Standard Product V5 Registers. + * For use with Parade touchscreen controllers. + * Supported parts include: + * CYTMA5XX + * CYTMA448 + * CYTMA445A + * CYTT21XXX + * CYTT31XXX + * + * Copyright (C) 2015 Parade Technologies + * Copyright (C) 2012-2015 Cypress Semiconductor + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Contact Parade Technologies at www.paradetech.com + * + */ + +#ifndef _CYTTSP5_REGS_H +#define _CYTTSP5_REGS_H + +#include +#ifdef CONFIG_HAS_EARLYSUSPEND +#include +#elif defined(CONFIG_FB) +#include +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* #define EASYWAKE_TSG6 */ + +#define CY_FW_FILE_PREFIX "cyttsp5_fw" +#define CY_FW_FILE_SUFFIX ".bin" +#define CY_FW_FILE_NAME "cyttsp5_fw.bin" + +#ifdef TTHE_TUNER_SUPPORT +#define CYTTSP5_TTHE_TUNER_FILE_NAME "tthe_tuner" +#endif + +#define CY_MAX_PRBUF_SIZE PIPE_BUF +#define CY_PR_TRUNCATED " truncated..." + +#define CY_DEFAULT_CORE_ID "cyttsp5_core0" +#define CY_MAX_NUM_CORE_DEVS 5 +#define CY_IRQ_ASSERTED_VALUE 0 + + +#ifdef CY_ENABLE_MAX_ELEN +#define CY_MAX_ELEN 100 +#endif + +/* HID */ +#define HID_CYVENDOR 0xff010000 +#define CY_HID_VENDOR_ID 0x04B4 +#define CY_HID_BL_PRODUCT_ID 0xC100 +#define CY_HID_APP_PRODUCT_ID 0xC101 +#define CY_HID_VERSION 0x0100 +#define CY_HID_APP_REPORT_ID 0xF7 +#define CY_HID_BL_REPORT_ID 0xFF + +#define HID_INVALID_REPORT_ID 0x0 +#define HID_TOUCH_REPORT_ID 0x1 +#define HID_BTN_REPORT_ID 0x3 +#define HID_WAKEUP_REPORT_ID 0x4 +#define HID_NOISE_METRIC_REPORT_ID 0x5 +#define HID_TRACKING_HEATMAP_REPOR_ID 0xE +#define HID_SENSOR_DATA_REPORT_ID 0xF +#define HID_APP_RESPONSE_REPORT_ID 0x1F +#define HID_APP_OUTPUT_REPORT_ID 0x2F +#define HID_BL_RESPONSE_REPORT_ID 0x30 +#define HID_BL_OUTPUT_REPORT_ID 0x40 +#define HID_RESPONSE_REPORT_ID 0xF0 + +#define HID_OUTPUT_RESPONSE_REPORT_OFFSET 2 +#define HID_OUTPUT_RESPONSE_CMD_OFFSET 4 +#define HID_OUTPUT_RESPONSE_CMD_MASK 0x7F +#define HID_OUTPUT_CMD_OFFSET 6 +#define HID_OUTPUT_CMD_MASK 0x7F + +#define HID_SYSINFO_CYDATA_OFFSET 5 +#define HID_SYSINFO_SENSING_OFFSET 33 +#define HID_SYSINFO_BTN_OFFSET 48 +#define HID_SYSINFO_BTN_MASK 0xFF +#define HID_SYSINFO_MAX_BTN 8 + +#define HID_POWER_ON 0x0 +#define HID_POWER_SLEEP 0x1 +#define HID_LENGTH_BYTES 2 +#define HID_LENGTH_AND_REPORT_ID_BYTES 3 + +/* Timeout in ms */ +#define CY_REQUEST_EXCLUSIVE_TIMEOUT 8000 +#define CY_WATCHDOG_TIMEOUT 1000 +#define CY_HID_RESET_TIMEOUT 5000 +#define CY_HID_AUTO_CALI_CPLT_TIMEOUT 2500 +/* HID_DESCRIPTOR_TIMEOUT value based on FW spec (CAL_OS) */ +#define CY_HID_GET_HID_DESCRIPTOR_TIMEOUT 4000 +#define CY_HID_GET_REPORT_DESCRIPTOR_TIMEOUT 500 +#define CY_HID_SET_POWER_TIMEOUT 500 +#ifdef VERBOSE_DEBUG +#define CY_HID_OUTPUT_TIMEOUT 2000 +#else +#define CY_HID_OUTPUT_TIMEOUT 1000 +#endif +#define CY_HID_OUTPUT_START_BOOTLOADER_TIMEOUT 2000 +#define CY_HID_OUTPUT_USER_TIMEOUT 8000 +#define CY_HID_OUTPUT_GET_SYSINFO_TIMEOUT 3000 +#define CY_HID_OUTPUT_CALIBRATE_IDAC_TIMEOUT 5000 +#define CY_HID_OUTPUT_WRITE_CONF_BLOCK_TIMEOUT 400 +#define CY_HID_OUTPUT_RUN_SELF_TEST_TIMEOUT 10000 +#define CY_HID_OUTPUT_BL_INITIATE_BL_TIMEOUT 20000 +#define CY_HID_OUTPUT_BL_PROGRAM_AND_VERIFY_TIMEOUT 400 + +#define CY_WATCHDOG_RETRY_COUNT 60 + +/* maximum number of concurrent tracks */ +#define TOUCH_REPORT_SIZE 10 +#define TOUCH_INPUT_HEADER_SIZE 7 +#define TOUCH_COUNT_BYTE_OFFSET 5 +#define BTN_REPORT_SIZE 9 +#define BTN_INPUT_HEADER_SIZE 5 +#define SENSOR_REPORT_SIZE 150 +#define SENSOR_HEADER_SIZE 4 + +/* helpers */ +#define GET_NUM_TOUCHES(x) ((x) & 0x1F) +#define IS_LARGE_AREA(x) ((x) & 0x20) +#define IS_BAD_PKT(x) ((x) & 0x20) +#define IS_TMO(t) ((t) == 0) +#define HI_BYTE(x) (u8)(((x) >> 8) & 0xFF) +#define LOW_BYTE(x) (u8)((x) & 0xFF) +#define SET_CMD_LOW(byte, bits) \ + ((byte) = (((byte) & 0xF0) | ((bits) & 0x0F))) +#define SET_CMD_HIGH(byte, bits)\ + ((byte) = (((byte) & 0x0F) | ((bits) & 0xF0))) + +#define GET_MASK(length) \ + ((1 << length) - 1) +#define GET_FIELD(name, length, shift) \ + ((name >> shift) & GET_MASK(length)) + +#define HID_ITEM_SIZE_MASK 0x03 +#define HID_ITEM_TYPE_MASK 0x0C +#define HID_ITEM_TAG_MASK 0xF0 + +#define HID_ITEM_SIZE_SHIFT 0 +#define HID_ITEM_TYPE_SHIFT 2 +#define HID_ITEM_TAG_SHIFT 4 + +#define HID_GET_ITEM_SIZE(x) \ + ((x & HID_ITEM_SIZE_MASK) >> HID_ITEM_SIZE_SHIFT) +#define HID_GET_ITEM_TYPE(x) \ + ((x & HID_ITEM_TYPE_MASK) >> HID_ITEM_TYPE_SHIFT) +#define HID_GET_ITEM_TAG(x) \ + ((x & HID_ITEM_TAG_MASK) >> HID_ITEM_TAG_SHIFT) + +#define IS_DEEP_SLEEP_CONFIGURED(x) \ + ((x) == 0 || (x) == 0xFF) + +#define IS_PIP_VER_GE(p, maj, min) \ + ((p)->cydata.pip_ver_major < (maj) ? \ + 0 : \ + ((p)->cydata.pip_ver_minor < (min) ? \ + 0 : \ + 1)) + +/* drv_debug commands */ +#define CY_DBG_SUSPEND 4 +#define CY_DBG_RESUME 5 +#define CY_DBG_SOFT_RESET 97 +#define CY_DBG_RESET 98 +#define CY_DBG_HID_RESET 50 +#define CY_DBG_HID_GET_REPORT 51 +#define CY_DBG_HID_SET_REPORT 52 +#define CY_DBG_HID_SET_POWER_ON 53 +#define CY_DBG_HID_SET_POWER_SLEEP 54 +#define CY_DBG_HID_NULL 100 +#define CY_DBG_HID_ENTER_BL 101 +#define CY_DBG_HID_SYSINFO 102 +#define CY_DBG_HID_SUSPEND_SCAN 103 +#define CY_DBG_HID_RESUME_SCAN 104 +#define CY_DBG_HID_STOP_WD 105 +#define CY_DBG_HID_START_WD 106 + +#define CY_TTHE_TUNER_EXIT 107 +#define CY_TTHE_BUF_CLEAN 108 + + +/* Recognized usages */ +/* undef them first for possible redefinition in Linux */ +#undef HID_DI_PRESSURE +#undef HID_DI_TIP +#undef HID_DI_CONTACTID +#undef HID_DI_CONTACTCOUNT +#undef HID_DI_SCANTIME +#define HID_DI_PRESSURE 0x000d0030 +#define HID_DI_TIP 0x000d0042 +#define HID_DI_CONTACTID 0x000d0051 +#define HID_DI_CONTACTCOUNT 0x000d0054 +#define HID_DI_SCANTIME 0x000d0056 + +/* Parade vendor specific usages */ +#define HID_CY_UNDEFINED 0xff010000 +#define HID_CY_BOOTLOADER 0xff010001 +#define HID_CY_TOUCHAPPLICATION 0xff010002 +#define HID_CY_BUTTONS 0xff010020 +#define HID_CY_GENERICITEM 0xff010030 +#define HID_CY_LARGEOBJECT 0xff010040 +#define HID_CY_NOISEEFFECTS 0xff010041 +#define HID_CY_REPORTCOUNTER 0xff010042 +#define HID_CY_TOUCHTYPE 0xff010060 +#define HID_CY_EVENTID 0xff010061 +#define HID_CY_MAJORAXISLENGTH 0xff010062 +#define HID_CY_MINORAXISLENGTH 0xff010063 +#define HID_CY_ORIENTATION 0xff010064 +#define HID_CY_BUTTONSIGNAL 0xff010065 +#define HID_CY_MAJOR_CONTACT_AXIS_LENGTH 0xff010066 +#define HID_CY_MINOR_CONTACT_AXIS_LENGTH 0xff010067 +#define HID_CY_TCH_COL_USAGE_PG 0x000D0022 +#define HID_CY_BTN_COL_USAGE_PG 0xFF010020 + +#define PANEL_ID_NOT_ENABLED 0xFF + +#ifdef EASYWAKE_TSG6 +#define GESTURE_DOUBLE_TAP (1) +#define GESTURE_TWO_FINGERS_SLIDE (2) +#define GESTURE_TOUCH_DETECTED (3) +#define GESTURE_PUSH_BUTTON (4) +#define GESTURE_SINGLE_SLIDE_DE_TX (5) +#define GESTURE_SINGLE_SLIDE_IN_TX (6) +#define GESTURE_SINGLE_SLIDE_DE_RX (7) +#define GESTURE_SINGLE_SLIDE_IN_RX (8) +#endif + +/* FW RAM parameters */ +#define CY_RAM_ID_TOUCHMODE_ENABLED 0x02 +#define CY_RAM_ID_PROXIMITY_ENABLE 0x20 +#define CY_RAM_ID_TOUCHMODE_ENABLED_SIZE 1 +#define CY_RAM_ID_PROXIMITY_ENABLE_SIZE 1 + +/* abs signal capabilities offsets in the frameworks array */ +enum cyttsp5_sig_caps { + CY_SIGNAL_OST, + CY_MIN_OST, + CY_MAX_OST, + CY_FUZZ_OST, + CY_FLAT_OST, + CY_NUM_ABS_SET /* number of signal capability fields */ +}; + +/* helpers */ +#define NUM_SIGNALS(frmwrk) ((frmwrk)->size / CY_NUM_ABS_SET) +#define PARAM(frmwrk, sig_ost, cap_ost) \ + ((frmwrk)->abs[((sig_ost) * CY_NUM_ABS_SET) + (cap_ost)]) + +#define PARAM_SIGNAL(frmwrk, sig_ost) PARAM(frmwrk, sig_ost, CY_SIGNAL_OST) +#define PARAM_MIN(frmwrk, sig_ost) PARAM(frmwrk, sig_ost, CY_MIN_OST) +#define PARAM_MAX(frmwrk, sig_ost) PARAM(frmwrk, sig_ost, CY_MAX_OST) +#define PARAM_FUZZ(frmwrk, sig_ost) PARAM(frmwrk, sig_ost, CY_FUZZ_OST) +#define PARAM_FLAT(frmwrk, sig_ost) PARAM(frmwrk, sig_ost, CY_FLAT_OST) + +/* abs axis signal offsets in the framworks array */ +enum cyttsp5_sig_ost { + CY_ABS_X_OST, + CY_ABS_Y_OST, + CY_ABS_P_OST, + CY_ABS_W_OST, + CY_ABS_ID_OST, + CY_ABS_MAJ_OST, + CY_ABS_MIN_OST, + CY_ABS_OR_OST, + CY_ABS_TOOL_OST, + CY_ABS_D_OST, + CY_NUM_ABS_OST /* number of abs signals */ +}; + +enum hid_command { + HID_CMD_RESERVED, + HID_CMD_RESET, + HID_CMD_GET_REPORT, + HID_CMD_SET_REPORT, + HID_CMD_GET_IDLE, + HID_CMD_SET_IDLE, + HID_CMD_GET_PROTOCOL, + HID_CMD_SET_PROTOCOL, + HID_CMD_SET_POWER, + HID_CMD_VENDOR = 0xE, +}; + +enum hid_output_cmd_type { + HID_OUTPUT_CMD_APP, + HID_OUTPUT_CMD_BL, +}; + +enum hid_output { + HID_OUTPUT_NULL, + HID_OUTPUT_START_BOOTLOADER, + HID_OUTPUT_GET_SYSINFO, + HID_OUTPUT_SUSPEND_SCANNING, + HID_OUTPUT_RESUME_SCANNING, + HID_OUTPUT_GET_PARAM, + HID_OUTPUT_SET_PARAM, + HID_OUTPUT_GET_NOISE_METRICS, + HID_OUTPUT_RESERVED, + HID_OUTPUT_ENTER_EASYWAKE_STATE, + HID_OUTPUT_VERIFY_CONFIG_BLOCK_CRC = 0x20, + HID_OUTPUT_GET_CONFIG_ROW_SIZE, + HID_OUTPUT_READ_CONF_BLOCK, + HID_OUTPUT_WRITE_CONF_BLOCK, + HID_OUTPUT_GET_DATA_STRUCTURE, + HID_OUTPUT_LOAD_SELF_TEST_PARAM, + HID_OUTPUT_RUN_SELF_TEST, + HID_OUTPUT_GET_SELF_TEST_RESULT, + HID_OUTPUT_CALIBRATE_IDACS, + HID_OUTPUT_INITIALIZE_BASELINES, + HID_OUTPUT_EXEC_PANEL_SCAN, + HID_OUTPUT_RETRIEVE_PANEL_SCAN, + HID_OUTPUT_START_SENSOR_DATA_MODE, + HID_OUTPUT_STOP_SENSOR_DATA_MODE, + HID_OUTPUT_START_TRACKING_HEATMAP_MODE, + HID_OUTPUT_INT_PIN_OVERRIDE = 0x40, + HID_OUTPUT_STORE_PANEL_SCAN = 0x60, + HID_OUTPUT_PROCESS_PANEL_SCAN, + HID_OUTPUT_DISCARD_INPUT_REPORT, + HID_OUTPUT_LAST, + HID_OUTPUT_USER_CMD, +}; + +enum hid_output_bl { + HID_OUTPUT_BL_VERIFY_APP_INTEGRITY = 0x31, + HID_OUTPUT_BL_GET_INFO = 0x38, + HID_OUTPUT_BL_PROGRAM_AND_VERIFY, + HID_OUTPUT_BL_LAUNCH_APP = 0x3B, + HID_OUTPUT_BL_GET_PANEL_ID = 0x3E, + HID_OUTPUT_BL_INITIATE_BL = 0x48, + HID_OUTPUT_BL_LAST, +}; + +#define HID_OUTPUT_BL_SOP 0x1 +#define HID_OUTPUT_BL_EOP 0x17 + +enum hid_output_bl_status { + ERROR_SUCCESS, + ERROR_KEY, + ERROR_VERIFICATION, + ERROR_LENGTH, + ERROR_DATA, + ERROR_COMMAND, + ERROR_CRC = 8, + ERROR_FLASH_ARRAY, + ERROR_FLASH_ROW, + ERROR_FLASH_PROTECTION, + ERROR_UKNOWN = 15, + ERROR_INVALID, +}; + +enum cyttsp5_mode { + CY_MODE_UNKNOWN, + CY_MODE_BOOTLOADER, + CY_MODE_OPERATIONAL, +}; + +enum cyttsp5_cmd_status { + CY_CMD_STATUS_SUCCESS, + CY_CMD_STATUS_FAILURE, +}; + +enum { + CY_IC_GRPNUM_RESERVED, + CY_IC_GRPNUM_CMD_REGS, + CY_IC_GRPNUM_TCH_REP, + CY_IC_GRPNUM_DATA_REC, + CY_IC_GRPNUM_TEST_REC, + CY_IC_GRPNUM_PCFG_REC, + CY_IC_GRPNUM_TCH_PARM_VAL, + CY_IC_GRPNUM_TCH_PARM_SIZE, + CY_IC_GRPNUM_RESERVED1, + CY_IC_GRPNUM_RESERVED2, + CY_IC_GRPNUM_OPCFG_REC, + CY_IC_GRPNUM_DDATA_REC, + CY_IC_GRPNUM_MDATA_REC, + CY_IC_GRPNUM_TEST_REGS, + CY_IC_GRPNUM_BTN_KEYS, + CY_IC_GRPNUM_TTHE_REGS, + CY_IC_GRPNUM_SENSING_CONF, + CY_IC_GRPNUM_NUM, +}; + +enum cyttsp5_event_id { + CY_EV_NO_EVENT, + CY_EV_TOUCHDOWN, + CY_EV_MOVE, /* significant displacement (> act dist) */ + CY_EV_LIFTOFF, /* record reports last position */ +}; + +enum cyttsp5_object_id { + CY_OBJ_STANDARD_FINGER, + CY_OBJ_PROXIMITY, + CY_OBJ_STYLUS, + CY_OBJ_HOVER, + CY_OBJ_GLOVE, +}; + +enum cyttsp5_self_test_result { + CY_ST_RESULT_PASS, + CY_ST_RESULT_FAIL, + CY_ST_RESULT_HOST_MUST_INTERPRET = 0xFF, +}; + +enum cyttsp5_self_test_id { + CY_ST_ID_NULL, + CY_ST_ID_BIST, + CY_ST_ID_SHORTS, + CY_ST_ID_OPENS, + CY_ST_ID_AUTOSHORTS, + CY_ST_ID_CM_PANEL, + CY_ST_ID_CP_PANEL, + CY_ST_ID_CM_BUTTON, + CY_ST_ID_CP_BUTTON, +}; + +#define CY_NUM_MFGID 8 + +/* System Information interface definitions */ +struct cyttsp5_cydata_dev { + u8 pip_ver_major; + u8 pip_ver_minor; + __le16 fw_pid; + u8 fw_ver_major; + u8 fw_ver_minor; + __le32 revctrl; + __le16 fw_ver_conf; + u8 bl_ver_major; + u8 bl_ver_minor; + __le16 jtag_si_id_l; + __le16 jtag_si_id_h; + u8 mfg_id[CY_NUM_MFGID]; + __le16 post_code; +} __packed; + +struct cyttsp5_sensing_conf_data_dev { + u8 electrodes_x; + u8 electrodes_y; + __le16 len_x; + __le16 len_y; + __le16 res_x; + __le16 res_y; + __le16 max_z; + u8 origin_x; + u8 origin_y; + u8 panel_id; + u8 btn; + u8 scan_mode; + u8 max_num_of_tch_per_refresh_cycle; +} __packed; + +struct cyttsp5_cydata { + u8 pip_ver_major; + u8 pip_ver_minor; + u8 bl_ver_major; + u8 bl_ver_minor; + u8 fw_ver_major; + u8 fw_ver_minor; + u16 fw_pid; + u16 fw_ver_conf; + u16 post_code; + u32 revctrl; + u16 jtag_id_l; + u16 jtag_id_h; + u8 mfg_id[CY_NUM_MFGID]; +}; + +struct cyttsp5_sensing_conf_data { + u16 res_x; + u16 res_y; + u16 max_z; + u16 len_x; + u16 len_y; + u8 electrodes_x; + u8 electrodes_y; + u8 origin_x; + u8 origin_y; + u8 panel_id; + u8 btn; + u8 scan_mode; + u8 max_tch; + u8 rx_num; + u8 tx_num; +}; + +enum cyttsp5_tch_abs { /* for ordering within the extracted touch data array */ + CY_TCH_X, /* X */ + CY_TCH_Y, /* Y */ + CY_TCH_P, /* P (Z) */ + CY_TCH_T, /* TOUCH ID */ + CY_TCH_E, /* EVENT ID */ + CY_TCH_O, /* OBJECT ID */ + CY_TCH_TIP, /* OBJECT ID */ + CY_TCH_MAJ, /* TOUCH_MAJOR */ + CY_TCH_MIN, /* TOUCH_MINOR */ + CY_TCH_OR, /* ORIENTATION */ + CY_TCH_NUM_ABS, +}; + +enum cyttsp5_tch_hdr { + CY_TCH_TIME, /* SCAN TIME */ + CY_TCH_NUM, /* NUMBER OF RECORDS */ + CY_TCH_LO, /* LARGE OBJECT */ + CY_TCH_NOISE, /* NOISE EFFECT */ + CY_TCH_COUNTER, /* REPORT_COUNTER */ + CY_TCH_NUM_HDR, +}; + +static const char * const cyttsp5_tch_abs_string[] = { + [CY_TCH_X] = "X", + [CY_TCH_Y] = "Y", + [CY_TCH_P] = "P", + [CY_TCH_T] = "T", + [CY_TCH_E] = "E", + [CY_TCH_O] = "O", + [CY_TCH_TIP] = "TIP", + [CY_TCH_MAJ] = "MAJ", + [CY_TCH_MIN] = "MIN", + [CY_TCH_OR] = "OR", + [CY_TCH_NUM_ABS] = "INVALID", +}; + +static const char * const cyttsp5_tch_hdr_string[] = { + [CY_TCH_TIME] = "SCAN TIME", + [CY_TCH_NUM] = "NUMBER OF RECORDS", + [CY_TCH_LO] = "LARGE OBJECT", + [CY_TCH_NOISE] = "NOISE EFFECT", + [CY_TCH_COUNTER] = "REPORT_COUNTER", + [CY_TCH_NUM_HDR] = "INVALID", +}; + +static const int cyttsp5_tch_abs_field_map[] = { + [CY_TCH_X] = 0x00010030 /* HID_GD_X */, + [CY_TCH_Y] = 0x00010031 /* HID_GD_Y */, + [CY_TCH_P] = HID_DI_PRESSURE, + [CY_TCH_T] = HID_DI_CONTACTID, + [CY_TCH_E] = HID_CY_EVENTID, + [CY_TCH_O] = HID_CY_TOUCHTYPE, + [CY_TCH_TIP] = HID_DI_TIP, + [CY_TCH_MAJ] = HID_CY_MAJORAXISLENGTH, + [CY_TCH_MIN] = HID_CY_MINORAXISLENGTH, + [CY_TCH_OR] = HID_CY_ORIENTATION, + [CY_TCH_NUM_ABS] = 0, +}; + +static const int cyttsp5_tch_hdr_field_map[] = { + [CY_TCH_TIME] = HID_DI_SCANTIME, + [CY_TCH_NUM] = HID_DI_CONTACTCOUNT, + [CY_TCH_LO] = HID_CY_LARGEOBJECT, + [CY_TCH_NOISE] = HID_CY_NOISEEFFECTS, + [CY_TCH_COUNTER] = HID_CY_REPORTCOUNTER, + [CY_TCH_NUM_HDR] = 0, +}; + +#define CY_NUM_EXT_TCH_FIELDS 3 + +struct cyttsp5_tch_abs_params { + size_t ofs; /* abs byte offset */ + size_t size; /* size in bits */ + size_t min; /* min value */ + size_t max; /* max value */ + size_t bofs; /* bit offset */ + u8 report; +}; + +struct cyttsp5_touch { + int hdr[CY_TCH_NUM_HDR]; + int abs[CY_TCH_NUM_ABS]; +}; + +/* button to keycode support */ +#define CY_BITS_PER_BTN 1 +#define CY_NUM_BTN_EVENT_ID ((1 << CY_BITS_PER_BTN) - 1) + +enum cyttsp5_btn_state { + CY_BTN_RELEASED = 0, + CY_BTN_PRESSED = 1, + CY_BTN_NUM_STATE +}; + +struct cyttsp5_btn { + bool enabled; + int state; /* CY_BTN_PRESSED, CY_BTN_RELEASED */ + int key_code; +}; + +enum cyttsp5_ic_ebid { + CY_TCH_PARM_EBID, + CY_MDATA_EBID, + CY_DDATA_EBID, +}; + +/* ttconfig block */ +#define CY_TTCONFIG_VERSION_OFFSET 8 +#define CY_TTCONFIG_VERSION_SIZE 2 +#define CY_TTCONFIG_VERSION_ROW 0 + +struct cyttsp5_ttconfig { + u16 version; + u16 crc; +}; + +struct cyttsp5_report_desc_data { + u16 tch_report_id; + u16 tch_record_size; + u16 tch_header_size; + u16 btn_report_id; +}; + +struct cyttsp5_sysinfo { + bool ready; + struct cyttsp5_cydata cydata; + struct cyttsp5_sensing_conf_data sensing_conf_data; + struct cyttsp5_report_desc_data desc; + int num_btns; + struct cyttsp5_btn *btn; + struct cyttsp5_ttconfig ttconfig; + struct cyttsp5_tch_abs_params tch_hdr[CY_TCH_NUM_HDR]; + struct cyttsp5_tch_abs_params tch_abs[CY_TCH_NUM_ABS]; + u8 *xy_mode; + u8 *xy_data; +}; + +enum cyttsp5_atten_type { + CY_ATTEN_IRQ, + CY_ATTEN_STARTUP, + CY_ATTEN_EXCLUSIVE, + CY_ATTEN_WAKE, + CY_ATTEN_LOADER, + CY_ATTEN_SUSPEND, + CY_ATTEN_RESUME, + CY_ATTEN_NUM_ATTEN, +}; + +enum cyttsp5_sleep_state { + SS_SLEEP_OFF, + SS_SLEEP_ON, + SS_SLEEPING, + SS_WAKING, +}; + +enum cyttsp5_fb_state { + FB_ON, + FB_OFF, +}; + +enum cyttsp5_startup_state { + STARTUP_NONE, + STARTUP_QUEUED, + STARTUP_RUNNING, + STARTUP_ILLEGAL, +}; + +struct cyttsp5_hid_desc { + __le16 hid_desc_len; + u8 packet_id; + u8 reserved_byte; + __le16 bcd_version; + __le16 report_desc_len; + __le16 report_desc_register; + __le16 input_register; + __le16 max_input_len; + __le16 output_register; + __le16 max_output_len; + __le16 command_register; + __le16 data_register; + __le16 vendor_id; + __le16 product_id; + __le16 version_id; + u8 reserved[4]; +} __packed; + +struct cyttsp5_hid_core { + u16 hid_vendor_id; + u16 hid_product_id; + __le16 hid_desc_register; + u16 hid_report_desc_len; + u16 hid_max_input_len; + u16 hid_max_output_len; +}; + +#define CY_HID_MAX_REPORTS 8 +#define CY_HID_MAX_FIELDS 128 +#define CY_HID_MAX_COLLECTIONS 3 +#define CY_HID_MAX_NESTED_COLLECTIONS CY_HID_MAX_COLLECTIONS + +#define CY_MAX_INPUT 512 +#define CY_PIP_1P7_EMPTY_BUF 0xFF00 + +enum cyttsp5_module_id { + CY_MODULE_MT, + CY_MODULE_BTN, + CY_MODULE_PROX, + CY_MODULE_LAST, +}; + +struct cyttsp5_mt_data; +struct cyttsp5_mt_function { + int (*mt_release)(struct device *dev); + int (*mt_probe)(struct device *dev, struct cyttsp5_mt_data *md); + void (*report_slot_liftoff)(struct cyttsp5_mt_data *md, int max_slots); + void (*input_sync)(struct input_dev *input); + void (*input_report)(struct input_dev *input, int sig, int t, int type); + void (*final_sync)(struct input_dev *input, int max_slots, + int mt_sync_count, unsigned long *ids); + int (*input_register_device)(struct input_dev *input, int max_slots); +}; + +struct cyttsp5_mt_data { + struct device *dev; + struct cyttsp5_mt_platform_data *pdata; + struct cyttsp5_sysinfo *si; + struct input_dev *input; + struct cyttsp5_mt_function mt_function; + struct mutex mt_lock; + bool is_suspended; + bool input_device_registered; + char phys[NAME_MAX]; + int num_prv_rec; + int or_min; + int or_max; + int t_min; + int t_max; +}; + +struct cyttsp5_btn_data { + struct device *dev; + struct cyttsp5_btn_platform_data *pdata; + struct cyttsp5_sysinfo *si; + struct input_dev *input; + struct mutex btn_lock; + bool is_suspended; + bool input_device_registered; + char phys[NAME_MAX]; +}; + +struct cyttsp5_proximity_data { + struct device *dev; + struct cyttsp5_proximity_platform_data *pdata; + struct cyttsp5_sysinfo *si; + struct input_dev *input; + struct mutex prox_lock; + struct mutex sysfs_lock; + int enable_count; + bool input_device_registered; + char phys[NAME_MAX]; +}; + +enum cyttsp5_calibrate_idacs_sensing_mode { + CY_CI_SM_MUTCAP_FINE, + CY_CI_SM_MUTCAP_BUTTON, + CY_CI_SM_SELFCAP, +}; + +enum cyttsp5_initialize_baselines_sensing_mode { + CY_IB_SM_MUTCAP = 1, + CY_IB_SM_BUTTON = 2, + CY_IB_SM_SELFCAP = 4, + CY_IB_SM_BALANCED = 8, +}; + +struct cyttsp5_core_nonhid_cmd { + int (*start_bl)(struct device *dev, int protect); + int (*suspend_scanning)(struct device *dev, int protect); + int (*resume_scanning)(struct device *dev, int protect); + int (*get_param)(struct device *dev, int protect, u8 param_id, + u32 *value); + int (*set_param)(struct device *dev, int protect, u8 param_id, + u32 value, u8 size); + int (*verify_config_block_crc)(struct device *dev, int protect, + u8 ebid, u8 *status, u16 *calculated_crc, + u16 *stored_crc); + int (*get_config_row_size)(struct device *dev, int protect, + u16 *row_size); + int (*get_data_structure)(struct device *dev, int protect, + u16 read_offset, u16 read_length, u8 data_id, + u8 *status, u8 *data_format, u16 *actual_read_len, + u8 *data); + int (*run_selftest)(struct device *dev, int protect, u8 test_id, + u8 write_idacs_to_flash, u8 *status, u8 *summary_result, + u8 *results_available); + int (*get_selftest_result)(struct device *dev, int protect, + u16 read_offset, u16 read_length, u8 test_id, u8 *status, + u16 *actual_read_len, u8 *data); + int (*calibrate_idacs)(struct device *dev, int protect, u8 mode, + u8 *status); + int (*initialize_baselines)(struct device *dev, int protect, + u8 test_id, u8 *status); + int (*exec_panel_scan)(struct device *dev, int protect); + int (*retrieve_panel_scan)(struct device *dev, int protect, + u16 read_offset, u16 read_count, u8 data_id, + u8 *response, u8 *config, u16 *actual_read_len, + u8 *read_buf); + int (*write_conf_block)(struct device *dev, int protect, + u16 row_number, u16 write_length, u8 ebid, + u8 *write_buf, u8 *security_key, u16 *actual_write_len); + int (*user_cmd)(struct device *dev, int protect, u16 read_len, + u8 *read_buf, u16 write_len, u8 *write_buf, + u16 *actual_read_len); + int (*get_bl_info)(struct device *dev, int protect, u8 *return_data); + int (*initiate_bl)(struct device *dev, int protect, u16 key_size, + u8 *key_buf, u16 row_size, u8 *metadata_row_buf); + int (*launch_app)(struct device *dev, int protect); + int (*prog_and_verify)(struct device *dev, int protect, u16 data_len, + u8 *data_buf); + int (*verify_app_integrity)(struct device *dev, int protect, + u8 *result); + int (*get_panel_id)(struct device *dev, int protect, u8 *panel_id); +}; + +typedef int (*cyttsp5_atten_func) (struct device *); + +struct cyttsp5_core_commands { + int (*subscribe_attention)(struct device *dev, + enum cyttsp5_atten_type type, char *id, + cyttsp5_atten_func func, int flags); + int (*unsubscribe_attention)(struct device *dev, + enum cyttsp5_atten_type type, char *id, + cyttsp5_atten_func func, int flags); + int (*request_exclusive)(struct device *dev, int timeout_ms); + int (*release_exclusive)(struct device *dev); + int (*request_reset)(struct device *dev); + int (*request_restart)(struct device *dev, bool wait); + struct cyttsp5_sysinfo * (*request_sysinfo)(struct device *dev); + struct cyttsp5_loader_platform_data + *(*request_loader_pdata)(struct device *dev); + int (*request_stop_wd)(struct device *dev); + int (*request_start_wd)(struct device *dev); + int (*request_get_hid_desc)(struct device *dev, int protect); + int (*request_get_mode)(struct device *dev, int protect, u8 *mode); + int (*request_enable_scan_type)(struct device *dev, u8 scan_type); + int (*request_disable_scan_type)(struct device *dev, u8 scan_type); + struct cyttsp5_core_nonhid_cmd *nonhid_cmd; +#ifdef TTHE_TUNER_SUPPORT + int (*request_tthe_print)(struct device *dev, u8 *buf, int buf_len, + const u8 *data_name); +#endif +}; + +struct cyttsp5_features { + uint8_t easywake; + uint8_t noise_metric; + uint8_t tracking_heatmap; + uint8_t sensor_data; +}; + +#define NEED_SUSPEND_NOTIFIER \ + ((LINUX_VERSION_CODE < KERNEL_VERSION(3, 3, 0)) \ + && defined(CONFIG_PM_SLEEP) && defined(CONFIG_PM_RUNTIME)) + +struct cyttsp5_module { + struct list_head node; + char *name; + int (*probe)(struct device *dev, void **data); + void (*release)(struct device *dev, void *data); +}; + +struct cyttsp5_core_data { + struct list_head node; + struct list_head module_list; /* List of probed modules */ + char core_id[20]; + struct device *dev; + struct list_head atten_list[CY_ATTEN_NUM_ATTEN]; + struct list_head param_list; + struct mutex module_list_lock; + struct mutex system_lock; + struct mutex adap_lock; + struct mutex hid_report_lock; + enum cyttsp5_mode mode; + spinlock_t spinlock; + struct cyttsp5_mt_data md; + struct cyttsp5_btn_data bd; + struct cyttsp5_proximity_data pd; + int phys_num; + int number_of_open_input_device; + int pm_runtime_usage_count; + void *cyttsp5_dynamic_data[CY_MODULE_LAST]; + struct cyttsp5_platform_data *pdata; + struct cyttsp5_core_platform_data *cpdata; + const struct cyttsp5_bus_ops *bus_ops; + wait_queue_head_t wait_q; + enum cyttsp5_sleep_state sleep_state; + enum cyttsp5_startup_state startup_state; + int irq; + bool irq_enabled; + bool irq_wake; + bool irq_disabled; + u8 easy_wakeup_gesture; +#ifdef EASYWAKE_TSG6 + u8 gesture_id; + u8 gesture_data_length; + u8 gesture_data[80]; +#endif + bool wake_initiated_by_device; + bool wait_until_wake; + u8 panel_id; +#if NEED_SUSPEND_NOTIFIER + /* + * This notifier is used to receive suspend prepare events + * When device is PM runtime suspended, pm_generic_suspend() + * does not call our PM suspend callback for kernels with + * version less than 3.3.0. + */ + struct notifier_block pm_notifier; +#endif + struct work_struct startup_work; + struct cyttsp5_sysinfo sysinfo; + void *exclusive_dev; + int exclusive_waits; + struct work_struct watchdog_work; + struct timer_list watchdog_timer; + u16 startup_retry_count; + struct cyttsp5_hid_core hid_core; + int hid_cmd_state; + int hid_reset_cmd_state; /* reset can happen any time */ + struct cyttsp5_hid_desc hid_desc; + struct cyttsp5_hid_report *hid_reports[CY_HID_MAX_REPORTS]; + int num_hid_reports; + struct cyttsp5_features features; +#define CYTTSP5_PREALLOCATED_CMD_BUFFER 32 + u8 cmd_buf[CYTTSP5_PREALLOCATED_CMD_BUFFER]; + u8 input_buf[CY_MAX_INPUT]; + u8 response_buf[CY_MAX_INPUT]; +#ifdef CONFIG_HAS_EARLYSUSPEND + struct early_suspend es; +#elif defined(CONFIG_FB) + struct notifier_block fb_notifier; + enum cyttsp5_fb_state fb_state; +#endif +#ifdef TTHE_TUNER_SUPPORT + struct dentry *tthe_debugfs; + u8 *tthe_buf; + u32 tthe_buf_len; + struct mutex tthe_lock; + u8 tthe_exit; +#endif + u8 pr_buf[CY_MAX_PRBUF_SIZE]; + struct regulator *vdd; +}; +struct gd_sensor { + int32_t cm_min; + int32_t cm_max; + int32_t cm_ave; + int32_t cm_min_exclude_edge; + int32_t cm_max_exclude_edge; + int32_t cm_ave_exclude_edge; + int32_t gradient_val; +}; + +#ifdef TTHE_TUNER_SUPPORT +#define CY_CMD_RET_PANEL_IN_DATA_OFFSET 0 +#define CY_CMD_RET_PANEL_ELMNT_SZ_MASK 0x07 +#define CY_CMD_RET_PANEL_HDR 0x0A +#define CY_CMD_RET_PANEL_ELMNT_SZ_MAX 0x2 + +enum scan_data_type_list { + CY_MUT_RAW, + CY_MUT_BASE, + CY_MUT_DIFF, + CY_SELF_RAW, + CY_SELF_BASE, + CY_SELF_DIFF, + CY_BAL_RAW, + CY_BAL_BASE, + CY_BAL_DIFF, +}; +#endif + +struct cyttsp5_bus_ops { + u16 bustype; + + int (*read_default)(struct device *dev, void *buf, int size); + int (*read_default_nosize)(struct device *dev, u8 *buf, u32 max); + int (*write_read_specific)(struct device *dev, u8 write_len, + u8 *write_buf, u8 *read_buf); +}; + +static inline int cyttsp5_adap_read_default(struct cyttsp5_core_data *cd, + void *buf, int size) +{ + return cd->bus_ops->read_default(cd->dev, buf, size); +} + +static inline int cyttsp5_adap_read_default_nosize(struct cyttsp5_core_data *cd, + void *buf, int max) +{ + return cd->bus_ops->read_default_nosize(cd->dev, buf, max); +} + +static inline int cyttsp5_adap_write_read_specific(struct cyttsp5_core_data *cd, + u8 write_len, u8 *write_buf, u8 *read_buf) +{ + return cd->bus_ops->write_read_specific(cd->dev, write_len, write_buf, + read_buf); +} + +static inline void *cyttsp5_get_dynamic_data(struct device *dev, int id) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + return cd->cyttsp5_dynamic_data[id]; +} + +int request_exclusive(struct cyttsp5_core_data *cd, void *ownptr, + int timeout_ms); +int release_exclusive(struct cyttsp5_core_data *cd, void *ownptr); +int _cyttsp5_request_hid_output_get_param(struct device *dev, + int protect, u8 param_id, u32 *value); +int _cyttsp5_request_hid_output_set_param(struct device *dev, + int protect, u8 param_id, u32 value, u8 size); + +static inline int cyttsp5_request_exclusive(struct device *dev, int timeout_ms) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + return request_exclusive(cd, dev, timeout_ms); +} + +static inline int cyttsp5_release_exclusive(struct device *dev) +{ + struct cyttsp5_core_data *cd = dev_get_drvdata(dev); + + return release_exclusive(cd, dev); +} + +static inline int cyttsp5_request_nonhid_get_param(struct device *dev, + int protect, u8 param_id, u32 *value) +{ + return _cyttsp5_request_hid_output_get_param(dev, protect, param_id, + value); +} + +static inline int cyttsp5_request_nonhid_set_param(struct device *dev, + int protect, u8 param_id, u32 value, u8 size) +{ + return _cyttsp5_request_hid_output_set_param(dev, protect, param_id, + value, size); +} + +#ifdef VERBOSE_DEBUG +void cyttsp5_pr_buf(struct device *dev, u8 *dptr, int size, + const char *data_name); +#else +#define cyttsp5_pr_buf(a, b, c, d) do { } while (0) +#endif + +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_DEVICETREE_SUPPORT +int cyttsp5_devtree_create_and_get_pdata(struct device *adap_dev); +int cyttsp5_devtree_clean_pdata(struct device *adap_dev); +#else +static inline int cyttsp5_devtree_create_and_get_pdata(struct device *adap_dev) +{ + return 0; +} + +static inline int cyttsp5_devtree_clean_pdata(struct device *adap_dev) +{ + return 0; +} +#endif + +int cyttsp5_probe(const struct cyttsp5_bus_ops *ops, struct device *dev, + u16 irq, size_t xfer_buf_size); +int cyttsp5_release(struct cyttsp5_core_data *cd); + +struct cyttsp5_core_commands *cyttsp5_get_commands(void); +struct cyttsp5_core_data *cyttsp5_get_core_data(char *id); + +int cyttsp5_mt_release(struct device *dev); +int cyttsp5_mt_probe(struct device *dev); + +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_BUTTON +int cyttsp5_btn_probe(struct device *dev); +int cyttsp5_btn_release(struct device *dev); +#else +static inline int cyttsp5_btn_probe(struct device *dev) { return 0; } +static inline int cyttsp5_btn_release(struct device *dev) { return 0; } +#endif + +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_PROXIMITY +int cyttsp5_proximity_probe(struct device *dev); +int cyttsp5_proximity_release(struct device *dev); +#else +static inline int cyttsp5_proximity_probe(struct device *dev) { return 0; } +static inline int cyttsp5_proximity_release(struct device *dev) { return 0; } +#endif + +void cyttsp5_init_function_ptrs(struct cyttsp5_mt_data *md); +int _cyttsp5_subscribe_attention(struct device *dev, + enum cyttsp5_atten_type type, char *id, int (*func)(struct device *), + int mode); +int _cyttsp5_unsubscribe_attention(struct device *dev, + enum cyttsp5_atten_type type, char *id, int (*func)(struct device *), + int mode); +struct cyttsp5_sysinfo *_cyttsp5_request_sysinfo(struct device *dev); + +extern const struct dev_pm_ops cyttsp5_pm_ops; + +int cyttsp5_register_module(struct cyttsp5_module *module); +void cyttsp5_unregister_module(struct cyttsp5_module *module); + +void *cyttsp5_get_module_data(struct device *dev, + struct cyttsp5_module *module); + +#endif /* _CYTTSP5_REGS_H */ diff --git a/drivers/input/touchscreen/cyttsp5_spi.c b/drivers/input/touchscreen/cyttsp5_spi.c new file mode 100644 index 000000000000..e1e338040986 --- /dev/null +++ b/drivers/input/touchscreen/cyttsp5_spi.c @@ -0,0 +1,254 @@ +/* + * cyttsp5_spi.c + * Parade TrueTouch(TM) Standard Product V5 SPI Module. + * For use with Parade touchscreen controllers. + * Supported parts include: + * CYTMA5XX + * CYTMA448 + * CYTMA445A + * CYTT21XXX + * CYTT31XXX + * + * Copyright (C) 2015 Parade Technologies + * Copyright (C) 2012-2015 Cypress Semiconductor + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Contact Parade Technologies at www.paradetech.com + * + */ + +#include "cyttsp5_regs.h" + +#include +#include + +#define CY_SPI_WR_OP 0x00 /* r/~w */ +#define CY_SPI_RD_OP 0x01 +#define CY_SPI_BITS_PER_WORD 8 +#define CY_SPI_SYNC_ACK 0x62 + +#define CY_SPI_CMD_BYTES 0 +#define CY_SPI_DATA_SIZE (2 * 256) +#define CY_SPI_DATA_BUF_SIZE (CY_SPI_CMD_BYTES + CY_SPI_DATA_SIZE) + +static void cyttsp5_spi_add_rw_msg(struct spi_message *msg, + struct spi_transfer *xfer, u8 *w_header, u8 *r_header, u8 op) +{ + xfer->tx_buf = w_header; + xfer->rx_buf = r_header; + w_header[0] = op; + xfer->len = 1; + spi_message_add_tail(xfer, msg); +} + +static int cyttsp5_spi_xfer(struct device *dev, u8 op, u8 *buf, int length) +{ + struct spi_device *spi = to_spi_device(dev); + struct spi_message msg; + struct spi_transfer xfer[2]; + u8 w_header[2]; + u8 r_header[2]; + int rc; + + memset(xfer, 0, sizeof(xfer)); + + spi_message_init(&msg); + cyttsp5_spi_add_rw_msg(&msg, &xfer[0], w_header, r_header, op); + + switch (op) { + case CY_SPI_RD_OP: + xfer[1].rx_buf = buf; + xfer[1].len = length; + spi_message_add_tail(&xfer[1], &msg); + break; + case CY_SPI_WR_OP: + xfer[1].tx_buf = buf; + xfer[1].len = length; + spi_message_add_tail(&xfer[1], &msg); + break; + default: + rc = -EIO; + goto exit; + } + + rc = spi_sync(spi, &msg); +exit: + if (rc < 0) + dev_vdbg(dev, "%s: spi_sync() error %d\n", __func__, rc); + + if (r_header[0] != CY_SPI_SYNC_ACK) + return -EIO; + + return rc; +} + +static int cyttsp5_spi_read_default(struct device *dev, void *buf, int size) +{ + if (!buf || !size) + return 0; + + return cyttsp5_spi_xfer(dev, CY_SPI_RD_OP, buf, size); +} + +static int cyttsp5_spi_read_default_nosize(struct device *dev, u8 *buf, u32 max) +{ + u32 size; + int rc; + + if (!buf) + return 0; + + rc = cyttsp5_spi_xfer(dev, CY_SPI_RD_OP, buf, 2); + if (rc < 0) + return rc; + + size = get_unaligned_le16(&buf[0]); + if (!size) + return rc; + + if (size > max) + return -EINVAL; + + return cyttsp5_spi_read_default(dev, buf, size); +} + +static int cyttsp5_spi_write_read_specific(struct device *dev, u8 write_len, + u8 *write_buf, u8 *read_buf) +{ + int rc; + + rc = cyttsp5_spi_xfer(dev, CY_SPI_WR_OP, write_buf, write_len); + if (rc < 0) + return rc; + + if (read_buf) + rc = cyttsp5_spi_read_default_nosize(dev, read_buf, + CY_SPI_DATA_SIZE); + + return rc; +} + +static struct cyttsp5_bus_ops cyttsp5_spi_bus_ops = { + .bustype = BUS_SPI, + .read_default = cyttsp5_spi_read_default, + .read_default_nosize = cyttsp5_spi_read_default_nosize, + .write_read_specific = cyttsp5_spi_write_read_specific, +}; + +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_DEVICETREE_SUPPORT +static const struct of_device_id cyttsp5_spi_of_match[] = { + { .compatible = "cy,cyttsp5_spi_adapter", }, + { } +}; +MODULE_DEVICE_TABLE(of, cyttsp5_spi_of_match); +#endif + +static int cyttsp5_spi_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_DEVICETREE_SUPPORT + const struct of_device_id *match; +#endif + int rc; + + /* Set up SPI*/ + spi->bits_per_word = CY_SPI_BITS_PER_WORD; + spi->mode = SPI_MODE_0; + rc = spi_setup(spi); + if (rc < 0) { + dev_err(dev, "%s: SPI setup error %d\n", __func__, rc); + return rc; + } + +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_DEVICETREE_SUPPORT + match = of_match_device(of_match_ptr(cyttsp5_spi_of_match), dev); + if (match) { + rc = cyttsp5_devtree_create_and_get_pdata(dev); + if (rc < 0) + return rc; + } +#endif + + rc = cyttsp5_probe(&cyttsp5_spi_bus_ops, &spi->dev, spi->irq, + CY_SPI_DATA_BUF_SIZE); + +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_DEVICETREE_SUPPORT + if (rc && match) + cyttsp5_devtree_clean_pdata(dev); +#endif + + return rc; +} + +static int cyttsp5_spi_remove(struct spi_device *spi) +{ +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_DEVICETREE_SUPPORT + struct device *dev = &spi->dev; + const struct of_device_id *match; +#endif + struct cyttsp5_core_data *cd = dev_get_drvdata(&spi->dev); + + cyttsp5_release(cd); + +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_DEVICETREE_SUPPORT + match = of_match_device(of_match_ptr(cyttsp5_spi_of_match), dev); + if (match) + cyttsp5_devtree_clean_pdata(dev); +#endif + + return 0; +} + +static const struct spi_device_id cyttsp5_spi_id[] = { + { CYTTSP5_SPI_NAME, 0 }, + { } +}; +MODULE_DEVICE_TABLE(spi, cyttsp5_spi_id); + +static struct spi_driver cyttsp5_spi_driver = { + .driver = { + .name = CYTTSP5_SPI_NAME, + .bus = &spi_bus_type, + .owner = THIS_MODULE, + .pm = &cyttsp5_pm_ops, +#ifdef CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_DEVICETREE_SUPPORT + .of_match_table = cyttsp5_spi_of_match, +#endif + }, + .probe = cyttsp5_spi_probe, + .remove = (cyttsp5_spi_remove), + .id_table = cyttsp5_spi_id, +}; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 3, 0)) +module_spi_driver(cyttsp5_spi_driver); +#else +static int __init cyttsp5_spi_init(void) +{ + int err = spi_register_driver(&cyttsp5_spi_driver); + + pr_info("%s: Parade TTSP SPI Driver (Built %s) rc=%d\n", + __func__, CY_DRIVER_VERSION, err); + return err; +} +module_init(cyttsp5_spi_init); + +static void __exit cyttsp5_spi_exit(void) +{ + spi_unregister_driver(&cyttsp5_spi_driver); +} +module_exit(cyttsp5_spi_exit); +#endif + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Parade TrueTouch(R) Standard Product SPI Driver"); +MODULE_AUTHOR("Parade Technologies "); diff --git a/drivers/input/touchscreen/cyttsp5_test_device_access_api.c b/drivers/input/touchscreen/cyttsp5_test_device_access_api.c new file mode 100644 index 000000000000..ce9c87810312 --- /dev/null +++ b/drivers/input/touchscreen/cyttsp5_test_device_access_api.c @@ -0,0 +1,442 @@ +/* + * cyttsp5_test_device_access_api.c + * Parade TrueTouch(TM) Standard Product V5 Device Access API test module. + * For use with Parade touchscreen controllers. + * Supported parts include: + * CYTMA5XX + * CYTMA448 + * CYTMA445A + * CYTT21XXX + * CYTT31XXX + * + * Copyright (C) 2015 Parade Technologies + * Copyright (C) 2012-2015 Cypress Semiconductor + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Contact Parade Technologies at www.paradetech.com + * + */ + +#include +#include +#include + +#define BUFFER_SIZE 256 + +#define COMMAND_GET_SYSTEM_INFO 2 +#define COMMAND_SUSPEND_SCANNING 3 +#define COMMAND_RESUME_SCANNING 4 +#define COMMAND_GET_PARAMETER 5 +#define COMMAND_SET_PARAMETER 6 + +#define PARAMETER_ACTIVE_DISTANCE_2 0x0B + +struct tt_output_report { + __le16 reg_address; + __le16 length; + u8 report_id; + u8 reserved; + u8 command; + u8 parameters[0]; +} __packed; + +struct tt_input_report { + __le16 length; + u8 report_id; + u8 reserved; + u8 command; + u8 return_data[0]; +} __packed; + +static int prepare_tt_output_report(struct tt_output_report *out, + u16 length, u8 command) +{ + put_unaligned_le16(0x04, &out->reg_address); + put_unaligned_le16(5 + length, &out->length); + + out->report_id = 0x2f; + out->reserved = 0x00; + out->command = command; + + return 7 + length; +} + +static int check_and_parse_tt_input_report(struct tt_input_report *in, + u16 *length, u8 *command) +{ + if (in->report_id != 0x1f) + return -EINVAL; + + *length = get_unaligned_le16(&in->length); + *command = in->command & 0x7f; + + return 0; +} + +static int prepare_get_system_info_report(u8 *buf) +{ + struct tt_output_report *out = (struct tt_output_report *)buf; + + return prepare_tt_output_report(out, 0, COMMAND_GET_SYSTEM_INFO); +} + +static int check_get_system_info_response(u8 *buf, u16 read_length) +{ + struct tt_input_report *in = (struct tt_input_report *)buf; + u16 length = 0; + u8 command = 0; + + if (read_length != 51) + return -EINVAL; + + if (check_and_parse_tt_input_report(in, &length, &command) + || command != COMMAND_GET_SYSTEM_INFO + || length != 51) + return -EINVAL; + + pr_info("PIP Major Version: %d\n", in->return_data[0]); + pr_info("PIP Minor Version: %d\n", in->return_data[1]); + pr_info("Touch Firmware Product Id: %d\n", + get_unaligned_le16(&in->return_data[2])); + pr_info("Touch Firmware Major Version: %d\n", in->return_data[4]); + pr_info("Touch Firmware Minor Version: %d\n", in->return_data[5]); + pr_info("Touch Firmware Internal Revision Control Number: %d\n", + get_unaligned_le32(&in->return_data[6])); + pr_info("Customer Specified Firmware/Configuration Version: %d\n", + get_unaligned_le16(&in->return_data[10])); + pr_info("Bootloader Major Version: %d\n", in->return_data[12]); + pr_info("Bootloader Minor Version: %d\n", in->return_data[13]); + pr_info("Family ID: 0x%02x\n", in->return_data[14]); + pr_info("Revision ID: 0x%02x\n", in->return_data[15]); + pr_info("Silicon ID: 0x%02x\n", + get_unaligned_le16(&in->return_data[16])); + pr_info("Parade Manufacturing ID[0]: 0x%02x\n", in->return_data[18]); + pr_info("Parade Manufacturing ID[1]: 0x%02x\n", in->return_data[19]); + pr_info("Parade Manufacturing ID[2]: 0x%02x\n", in->return_data[20]); + pr_info("Parade Manufacturing ID[3]: 0x%02x\n", in->return_data[21]); + pr_info("Parade Manufacturing ID[4]: 0x%02x\n", in->return_data[22]); + pr_info("Parade Manufacturing ID[5]: 0x%02x\n", in->return_data[23]); + pr_info("Parade Manufacturing ID[6]: 0x%02x\n", in->return_data[24]); + pr_info("Parade Manufacturing ID[7]: 0x%02x\n", in->return_data[25]); + pr_info("POST Result Code: 0x%02x\n", + get_unaligned_le16(&in->return_data[26])); + + pr_info("Number of X Electrodes: %d\n", in->return_data[28]); + pr_info("Number of Y Electrodes: %d\n", in->return_data[29]); + pr_info("Panel X Axis Length: %d\n", + get_unaligned_le16(&in->return_data[30])); + pr_info("Panel Y Axis Length: %d\n", + get_unaligned_le16(&in->return_data[32])); + pr_info("Panel X Axis Resolution: %d\n", + get_unaligned_le16(&in->return_data[34])); + pr_info("Panel Y Axis Resolution: %d\n", + get_unaligned_le16(&in->return_data[36])); + pr_info("Panel Pressure Resolution: %d\n", + get_unaligned_le16(&in->return_data[38])); + pr_info("X_ORG: %d\n", in->return_data[40]); + pr_info("Y_ORG: %d\n", in->return_data[41]); + pr_info("Panel ID: %d\n", in->return_data[42]); + pr_info("Buttons: 0x%02x\n", in->return_data[43]); + pr_info("BAL SELF MC: 0x%02x\n", in->return_data[44]); + pr_info("Max Number of Touch Records per Refresh Cycle: %d\n", + in->return_data[45]); + + return 0; +} + +static int prepare_get_parameter_report(u8 *buf, u8 parameter_id) +{ + struct tt_output_report *out = (struct tt_output_report *)buf; + + out->parameters[0] = parameter_id; + + return prepare_tt_output_report(out, 1, COMMAND_GET_PARAMETER); +} + +static int check_get_parameter_response(u8 *buf, u16 read_length, + u32 *parameter_value) +{ + struct tt_input_report *in = (struct tt_input_report *)buf; + u16 length = 0; + u8 command = 0; + u32 param_value = 0; + u8 param_id; + u8 param_size = 0; + + if (read_length != 8 && read_length != 9 && read_length != 11) + return -EINVAL; + + if (check_and_parse_tt_input_report(in, &length, &command) + || command != COMMAND_GET_PARAMETER + || (length != 8 && length != 9 && length != 11)) + return -EINVAL; + + param_id = in->return_data[0]; + + param_size = in->return_data[1]; + + if (param_size == 1) + param_value = in->return_data[2]; + else if (param_size == 2) + param_value = get_unaligned_le16(&in->return_data[2]); + else if (param_size == 4) + param_value = get_unaligned_le32(&in->return_data[2]); + else + return -EINVAL; + + pr_info("%s: Parameter ID: 0x%02x Value: 0x%02x\n", + __func__, param_id, param_value); + + if (parameter_value) + *parameter_value = param_value; + + return 0; +} + +static int prepare_set_parameter_report(u8 *buf, u8 parameter_id, + u8 parameter_size, u32 parameter_value) +{ + struct tt_output_report *out = (struct tt_output_report *)buf; + + out->parameters[0] = parameter_id; + out->parameters[1] = parameter_size; + + if (parameter_size == 1) + out->parameters[2] = (u8)parameter_value; + else if (parameter_size == 2) + put_unaligned_le16(parameter_value, &out->parameters[2]); + else if (parameter_size == 4) + put_unaligned_le32(parameter_value, &out->parameters[2]); + else + return -EINVAL; + + return prepare_tt_output_report(out, 2 + parameter_size, + COMMAND_SET_PARAMETER); +} + +static int check_set_parameter_response(u8 *buf, u16 read_length) +{ + struct tt_input_report *in = (struct tt_input_report *)buf; + u16 length = 0; + u8 command = 0; + u8 param_id; + u8 param_size = 0; + + if (read_length != 7) + return -EINVAL; + + if (check_and_parse_tt_input_report(in, &length, &command) + || command != COMMAND_SET_PARAMETER + || length != 7) + return -EINVAL; + + param_id = in->return_data[0]; + param_size = in->return_data[1]; + + pr_info("%s: Parameter ID: 0x%02x Size: 0x%02x\n", + __func__, param_id, param_size); + + return 0; +} + +static int prepare_suspend_scanning_report(u8 *buf) +{ + struct tt_output_report *out = (struct tt_output_report *)buf; + + return prepare_tt_output_report(out, 0, COMMAND_SUSPEND_SCANNING); +} + +static int check_suspend_scanning_response(u8 *buf, u16 read_length) +{ + struct tt_input_report *in = (struct tt_input_report *)buf; + u16 length = 0; + u8 command = 0; + + if (read_length != 5) + return -EINVAL; + + if (check_and_parse_tt_input_report(in, &length, &command) + || command != COMMAND_SUSPEND_SCANNING + || length != 5) + return -EINVAL; + + return 0; +} + +static int prepare_resume_scanning_report(u8 *buf) +{ + struct tt_output_report *out = (struct tt_output_report *)buf; + + return prepare_tt_output_report(out, 0, COMMAND_RESUME_SCANNING); +} + +static int check_resume_scanning_response(u8 *buf, u16 read_length) +{ + struct tt_input_report *in = (struct tt_input_report *)buf; + u16 length = 0; + u8 command = 0; + + if (read_length != 5) + return -EINVAL; + + if (check_and_parse_tt_input_report(in, &length, &command) + || command != COMMAND_RESUME_SCANNING + || length != 5) + return -EINVAL; + + return 0; +} + +void cyttsp5_user_command_async_cont(const char *core_name, + u16 read_len, u8 *read_buf, u16 write_len, u8 *write_buf, + u16 actual_read_length, int rc) +{ + if (rc) { + pr_err("%s: suspend scan fails\n", __func__); + goto exit; + } + + rc = check_suspend_scanning_response(read_buf, actual_read_length); + if (rc) { + pr_err("%s: check suspend scanning response fails\n", __func__); + goto exit; + } + + pr_info("%s: suspend scanning succeeds\n", __func__); +exit: + return; +} + +/* Read and write buffers */ +static u8 write_buf[BUFFER_SIZE]; +static u8 read_buf[BUFFER_SIZE]; + +static uint active_distance; +module_param(active_distance, uint, 0); + +static int __init cyttsp5_test_device_access_api_init(void) +{ + u32 initial_active_distance; + u16 actual_read_len; + int write_len; + int rc; + + pr_info("%s: Enter\n", __func__); + + /* CASE 1: Run get system information */ + write_len = prepare_get_system_info_report(write_buf); + + rc = cyttsp5_device_access_user_command(NULL, sizeof(read_buf), + read_buf, write_len, write_buf, + &actual_read_len); + if (rc) + goto exit; + + rc = check_get_system_info_response(read_buf, actual_read_len); + if (rc) + goto exit; + + /* CASE 2: Run get parameter (Active distance) */ + write_len = prepare_get_parameter_report(write_buf, + PARAMETER_ACTIVE_DISTANCE_2); + + rc = cyttsp5_device_access_user_command(NULL, sizeof(read_buf), + read_buf, write_len, write_buf, + &actual_read_len); + if (rc) + goto exit; + + rc = check_get_parameter_response(read_buf, actual_read_len, + &initial_active_distance); + if (rc) + goto exit; + + pr_info("%s: Initial Active Distance: %d\n", + __func__, initial_active_distance); + + /* CASE 3: Run set parameter (Active distance) */ + write_len = prepare_set_parameter_report(write_buf, + PARAMETER_ACTIVE_DISTANCE_2, 1, + active_distance); + + rc = cyttsp5_device_access_user_command(NULL, sizeof(read_buf), + read_buf, write_len, write_buf, + &actual_read_len); + if (rc) + goto exit; + + rc = check_set_parameter_response(read_buf, actual_read_len); + if (rc) + goto exit; + + pr_info("%s: Active Distance set to %d\n", __func__, active_distance); + + /* CASE 4: Run get parameter (Active distance) */ + write_len = prepare_get_parameter_report(write_buf, + PARAMETER_ACTIVE_DISTANCE_2); + + rc = cyttsp5_device_access_user_command(NULL, sizeof(read_buf), + read_buf, write_len, write_buf, + &actual_read_len); + if (rc) + goto exit; + + rc = check_get_parameter_response(read_buf, actual_read_len, + &active_distance); + if (rc) + goto exit; + + pr_info("%s: New Active Distance: %d\n", __func__, active_distance); + + /* CASE 5: Run suspend scanning asynchronously */ + write_len = prepare_suspend_scanning_report(write_buf); + + preempt_disable(); + rc = cyttsp5_device_access_user_command_async(NULL, + sizeof(read_buf), read_buf, write_len, write_buf, + cyttsp5_user_command_async_cont); + preempt_enable(); + if (rc) + goto exit; +exit: + return rc; +} +module_init(cyttsp5_test_device_access_api_init); + +static void __exit cyttsp5_test_device_access_api_exit(void) +{ + u16 actual_read_len; + int write_len; + int rc; + + /* CASE 6: Run resume scanning */ + write_len = prepare_resume_scanning_report(write_buf); + + rc = cyttsp5_device_access_user_command(NULL, sizeof(read_buf), + read_buf, write_len, write_buf, + &actual_read_len); + if (rc) + goto exit; + + rc = check_resume_scanning_response(read_buf, actual_read_len); + if (rc) + goto exit; + + pr_info("%s: resume scanning succeeds\n", __func__); +exit: + return; +} +module_exit(cyttsp5_test_device_access_api_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Parade TrueTouch(R) Standard Product Device Access Driver API Tester"); +MODULE_AUTHOR("Parade Technologies "); diff --git a/include/linux/input/cyttsp5.h b/include/linux/input/cyttsp5.h new file mode 100644 index 000000000000..00413816de40 --- /dev/null +++ b/include/linux/input/cyttsp5.h @@ -0,0 +1,60 @@ +/* + * cyttsp5_platform.h + * Parade TrueTouch(TM) Standard Product V5 Platform Module. + * For use with Parade touchscreen controllers. + * Supported parts include: + * CYTMA5XX + * CYTMA448 + * CYTMA445A + * CYTT21XXX + * CYTT31XXX + * + * Copyright (C) 2015 Parade Technologies + * Copyright (C) 2013-2015 Cypress Semiconductor + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Contact Parade Technologies at www.paradetech.com + * + */ + +#ifndef _LINUX_CYTTSP5_PLATFORM_H +#define _LINUX_CYTTSP5_PLATFORM_H + +#include + +#if defined(CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5) \ + || defined(CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5_MODULE) +extern struct cyttsp5_loader_platform_data _cyttsp5_loader_platform_data; + +int cyttsp5_xres(struct cyttsp5_core_platform_data *pdata, struct device *dev); +int cyttsp5_init(struct cyttsp5_core_platform_data *pdata, int on, + struct device *dev); +int cyttsp5_power(struct cyttsp5_core_platform_data *pdata, int on, + struct device *dev, atomic_t *ignore_irq); +#ifdef CYTTSP5_DETECT_HW +int cyttsp5_detect(struct cyttsp5_core_platform_data *pdata, + struct device *dev, cyttsp5_platform_read read); +#else +#define cyttsp5_detect NULL +#endif +int cyttsp5_irq_stat(struct cyttsp5_core_platform_data *pdata, + struct device *dev); +#else /* !CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5 */ +static struct cyttsp5_loader_platform_data _cyttsp5_loader_platform_data; +#define cyttsp5_xres NULL +#define cyttsp5_init NULL +#define cyttsp5_power NULL +#define cyttsp5_irq_stat NULL +#define cyttsp5_detect NULL +#endif /* CONFIG_TOUCHSCREEN_CYPRESS_CYTTSP5 */ + +#endif /* _LINUX_CYTTSP5_PLATFORM_H */ diff --git a/include/linux/platform_data/cyttsp5.h b/include/linux/platform_data/cyttsp5.h new file mode 100644 index 000000000000..bf5eb09d15b9 --- /dev/null +++ b/include/linux/platform_data/cyttsp5.h @@ -0,0 +1,180 @@ +/* + * cyttsp5_core.h + * Parade TrueTouch(TM) Standard Product V5 Core Module. + * For use with Parade touchscreen controllers. + * Supported parts include: + * CYTMA5XX + * CYTMA448 + * CYTMA445A + * CYTT21XXX + * CYTT31XXX + * + * Copyright (C) 2015 Parade Technologies + * Copyright (C) 2012-2015 Cypress Semiconductor + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Contact Parade Technologies at www.paradetech.com + * + */ + +#ifndef _LINUX_CYTTSP5_CORE_H +#define _LINUX_CYTTSP5_CORE_H + +#include + +#define CYTTSP5_I2C_NAME "cyttsp5_i2c_adapter" +#define CYTTSP5_SPI_NAME "cyttsp5_spi_adapter" + +#define CYTTSP5_CORE_NAME "cyttsp5_core" +#define CYTTSP5_MT_NAME "cyttsp5_mt" +#define CYTTSP5_BTN_NAME "cyttsp5_btn" +#define CYTTSP5_PROXIMITY_NAME "cyttsp5_proximity" + +#define CY_DRIVER_NAME TTDA +#define CY_DRIVER_MAJOR 03 +#define CY_DRIVER_MINOR 07 + +#define CY_DRIVER_REVCTRL 844339 + +#define CY_DRIVER_VERSION \ +__stringify(CY_DRIVER_NAME) \ +"." __stringify(CY_DRIVER_MAJOR) \ +"." __stringify(CY_DRIVER_MINOR) \ +"." __stringify(CY_DRIVER_REVCTRL) + +#define CY_DRIVER_DATE "20150715" /* YYYYMMDD */ + +/* abs settings */ +#define CY_IGNORE_VALUE -1 + +enum cyttsp5_core_platform_flags { + CY_CORE_FLAG_NONE, + CY_CORE_FLAG_POWEROFF_ON_SLEEP = 0x02, + CY_CORE_FLAG_RESTORE_PARAMETERS = 0x04, +}; + +enum cyttsp5_core_platform_easy_wakeup_gesture { + CY_CORE_EWG_NONE, + CY_CORE_EWG_TAP_TAP, + CY_CORE_EWG_TWO_FINGER_SLIDE, + CY_CORE_EWG_RESERVED, + CY_CORE_EWG_WAKE_ON_INT_FROM_HOST = 0xFF, +}; + +enum cyttsp5_loader_platform_flags { + CY_LOADER_FLAG_NONE, + CY_LOADER_FLAG_CALIBRATE_AFTER_FW_UPGRADE, + /* Use CONFIG_VER field in TT_CFG to decide TT_CFG update */ + CY_LOADER_FLAG_CHECK_TTCONFIG_VERSION, + CY_LOADER_FLAG_CALIBRATE_AFTER_TTCONFIG_UPGRADE, +}; + +struct touch_settings { + const uint8_t *data; + uint32_t size; + uint8_t tag; +}; + +struct cyttsp5_touch_firmware { + const uint8_t *img; + uint32_t size; + const uint8_t *ver; + uint8_t vsize; + uint8_t panel_id; +}; + +struct cyttsp5_touch_config { + struct touch_settings *param_regs; + struct touch_settings *param_size; + const uint8_t *fw_ver; + uint8_t fw_vsize; + uint8_t panel_id; +}; + +struct cyttsp5_loader_platform_data { + struct cyttsp5_touch_firmware *fw; + struct cyttsp5_touch_config *ttconfig; + struct cyttsp5_touch_firmware **fws; + struct cyttsp5_touch_config **ttconfigs; + u32 flags; +}; + +typedef int (*cyttsp5_platform_read) (struct device *dev, void *buf, int size); + +#define CY_TOUCH_SETTINGS_MAX 32 + +struct cyttsp5_core_platform_data { + int irq_gpio; + int rst_gpio; + int level_irq_udelay; + u16 hid_desc_register; + u16 vendor_id; + u16 product_id; + + int (*xres)(struct cyttsp5_core_platform_data *pdata, + struct device *dev); + int (*init)(struct cyttsp5_core_platform_data *pdata, + int on, struct device *dev); + int (*power)(struct cyttsp5_core_platform_data *pdata, + int on, struct device *dev, atomic_t *ignore_irq); + int (*detect)(struct cyttsp5_core_platform_data *pdata, + struct device *dev, cyttsp5_platform_read read); + int (*irq_stat)(struct cyttsp5_core_platform_data *pdata, + struct device *dev); + struct touch_settings *sett[CY_TOUCH_SETTINGS_MAX]; + u32 flags; + u8 easy_wakeup_gesture; + bool fb_blanking_disabled; +}; + +struct touch_framework { + const int16_t *abs; + uint8_t size; + uint8_t enable_vkeys; +} __packed; + +enum cyttsp5_mt_platform_flags { + CY_MT_FLAG_NONE, + CY_MT_FLAG_HOVER = 0x04, + CY_MT_FLAG_FLIP = 0x08, + CY_MT_FLAG_INV_X = 0x10, + CY_MT_FLAG_INV_Y = 0x20, + CY_MT_FLAG_VKEYS = 0x40, + CY_MT_FLAG_NO_TOUCH_ON_LO = 0x80, +}; + +struct cyttsp5_mt_platform_data { + struct touch_framework *frmwrk; + unsigned short flags; + char const *inp_dev_name; + int vkeys_x; + int vkeys_y; +}; + +struct cyttsp5_btn_platform_data { + char const *inp_dev_name; +}; + +struct cyttsp5_proximity_platform_data { + struct touch_framework *frmwrk; + char const *inp_dev_name; +}; + +struct cyttsp5_platform_data { + struct cyttsp5_core_platform_data *core_pdata; + struct cyttsp5_mt_platform_data *mt_pdata; + struct cyttsp5_btn_platform_data *btn_pdata; + struct cyttsp5_proximity_platform_data *prox_pdata; + struct cyttsp5_loader_platform_data *loader_pdata; +}; + +#endif /* _LINUX_CYTTSP5_CORE_H */ diff --git a/include/uapi/linux/cyttsp5.h b/include/uapi/linux/cyttsp5.h new file mode 100644 index 000000000000..cfaf7a342a46 --- /dev/null +++ b/include/uapi/linux/cyttsp5.h @@ -0,0 +1,44 @@ +/* + * cyttsp5_device_access-api.h + * Parade TrueTouch(TM) Standard Product V5 Device Access API module. + * For use with Parade touchscreen controllers. + * Supported parts include: + * CYTMA5XX + * CYTMA448 + * CYTMA445A + * CYTT21XXX + * CYTT31XXX + * + * Copyright (C) 2015 Parade Technologies + * Copyright (C) 2012-2015 Cypress Semiconductor + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Contact Parade Technologies at www.paradetech.com + * + */ + +#ifndef _LINUX_CYTTSP5_DEVICE_ACCESS_API_H +#define _LINUX_CYTTSP5_DEVICE_ACCESS_API_H + +#include +#include + +int cyttsp5_device_access_user_command(const char *core_name, u16 read_len, + u8 *read_buf, u16 write_len, u8 *write_buf, + u16 *actual_read_len); + +int cyttsp5_device_access_user_command_async(const char *core_name, + u16 read_len, u8 *read_buf, u16 write_len, u8 *write_buf, + void (*cont)(const char *core_name, u16 read_len, u8 *read_buf, + u16 write_len, u8 *write_buf, u16 actual_read_length, + int rc)); +#endif /* _LINUX_CYTTSP5_DEVICE_ACCESS_API_H */